├── lib
├── sourcemap.rb
├── source_map
│ ├── version.rb
│ ├── mapping.rb
│ ├── offset.rb
│ ├── vlq.rb
│ └── map.rb
└── source_map.rb
├── .gitignore
├── Gemfile
├── .travis.yml
├── examples
├── foobar.html
├── bar.coffee
├── bar.map
├── foo.coffee
├── foo.min.js
├── foo.map
├── bar.js
├── foo.min.map
├── foobar.map
├── foo.js
└── foobar.js
├── Rakefile
├── sourcemap.gemspec
├── LICENSE
├── test
├── test_mapping.rb
├── test_offset.rb
├── test_vlq.rb
└── test_map.rb
└── README.md
/lib/sourcemap.rb:
--------------------------------------------------------------------------------
1 | require 'source_map'
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Gemfile.lock
2 | .DS_Store
3 | pkg
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gemspec
3 |
--------------------------------------------------------------------------------
/lib/source_map/version.rb:
--------------------------------------------------------------------------------
1 | module SourceMap
2 | VERSION = "0.1.1"
3 | end
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 |
3 | rvm:
4 | - 1.9.3
5 | - 2.0.0
6 | - 2.1
7 |
8 | notifications:
9 | email: false
10 |
--------------------------------------------------------------------------------
/examples/foobar.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/lib/source_map.rb:
--------------------------------------------------------------------------------
1 | require 'source_map/map'
2 | require 'source_map/mapping'
3 | require 'source_map/offset'
4 | require 'source_map/version'
5 | require 'source_map/vlq'
6 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rake/testtask'
3 |
4 | task :default => :test
5 |
6 | Rake::TestTask.new do |t|
7 | t.libs << 'test'
8 | t.warning = true
9 | end
--------------------------------------------------------------------------------
/examples/bar.coffee:
--------------------------------------------------------------------------------
1 | # Eat lunch.
2 | eat food for food in ['toast', 'cheese', 'wine']
3 |
4 | # Fine five course dining.
5 | courses = ['greens', 'caviar', 'truffles', 'roast', 'cake']
6 | menu i + 1, dish for dish, i in courses
7 |
8 | # Health conscious meal.
9 | foods = ['broccoli', 'spinach', 'chocolate']
10 | eat food for food in foods when food isnt 'chocolate'
11 |
--------------------------------------------------------------------------------
/examples/bar.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "file": "bar.js",
4 | "sourceRoot": "",
5 | "sources": [
6 | "bar.coffee"
7 | ],
8 | "names": [],
9 | "mappings": ";AACA;CAAA,KAAA,6DAAA;;CAAA;CAAA,MAAA,oCAAA;qBAAA;CAAA,EAAA,CAAA;CAAA,EAAA;;CAAA,CAGA,CAAU,GAAA,CAAV,CAAU,EAAA;;AACV,CAAA,MAAA,iDAAA;uBAAA;CAAA,CAAY,CAAH,CAAT;CAAA,EAJA;;CAAA,CAOA,CAAQ,EAAR,IAAQ,CAAA,CAAA;;AACR,CAAA,MAAA,uCAAA;sBAAA;IAAgC,CAAU;CAA1C,EAAA,CAAA,EAAA;MAAA;CAAA,EARA;CAAA"
10 | }
--------------------------------------------------------------------------------
/examples/foo.coffee:
--------------------------------------------------------------------------------
1 | # Assignment:
2 | number = 42
3 | opposite = true
4 |
5 | # Conditions:
6 | number = -42 if opposite
7 |
8 | # Functions:
9 | square = (x) -> x * x
10 |
11 | # Arrays:
12 | list = [1, 2, 3, 4, 5]
13 |
14 | # Objects:
15 | math =
16 | root: Math.sqrt
17 | square: square
18 | cube: (x) -> x * square x
19 |
20 | # Splats:
21 | race = (winner, runners...) ->
22 | print winner, runners
23 |
24 | # Existence:
25 | alert "I knew it!" if elvis?
26 |
27 | # Array comprehensions:
28 | cubes = (math.cube num for num in list)
29 |
--------------------------------------------------------------------------------
/examples/foo.min.js:
--------------------------------------------------------------------------------
1 | !function(){var cubes,list,math,num,number,opposite,race,square,__slice=[].slice;number=42;opposite=true;if(opposite){number=-42}square=function(x){return x*x};list=[1,2,3,4,5];math={root:Math.sqrt,square:square,cube:function(x){return x*square(x)}};race=function(){var runners,winner;winner=arguments[0],runners=2<=arguments.length?__slice.call(arguments,1):[];return print(winner,runners)};if(typeof elvis!=="undefined"&&elvis!==null){alert("I knew it!")}cubes=function(){var _i,_len,_results;_results=[];for(_i=0,_len=list.length;_i<_len;_i++){num=list[_i];_results.push(math.cube(num))}return _results}()}.call(this);
2 | //@ sourceMappingURL=foo.min.map
3 |
--------------------------------------------------------------------------------
/examples/foo.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "file": "foo.js",
4 | "sourceRoot": "",
5 | "sources": [
6 | "foo.coffee"
7 | ],
8 | "names": [],
9 | "mappings": ";AACA;CAAA,KAAA,gDAAA;KAAA,aAAA;;CAAA,CAAA,CAAW,GAAX;;CAAA,CACA,CAAW,CADX,IACA;;CAGA,CAAA,EAAgB,IAAhB;AAAU,CAAV,CAAA,CAAS,CAAT,EAAA;IAJA;;CAAA,CAOA,CAAS,GAAT,GAAU;CAAM,EAAI,QAAJ;CAPhB,EAOS;;CAPT,CAUA,CAAO,CAAP;;CAVA,CAaA,CACE,CADF;CACE,CAAQ,EAAR;CAAA,CACQ,EAAR,EAAA;CADA,CAEQ,CAAA,CAAR,KAAS;CAAM,EAAI,GAAA,OAAJ;CAFf,IAEQ;CAhBV,GAAA;;CAAA,CAmBA,CAAO,CAAP,KAAO;CACL,OAAA,OAAA;CAAA,CADc,EAAR,mDACN;CAAM,CAAQ,GAAd,CAAA,CAAA,IAAA;CApBF,EAmBO;;CAIP,CAAA,EAAsB,0CAAtB;CAAA,GAAA,CAAA,OAAA;IAvBA;;CAAA,CA0BA,GAAA;;AAAS,CAAA;UAAA,iCAAA;sBAAA;CAAA,EAAA,CAAI;CAAJ;;CA1BT;CAAA"
10 | }
--------------------------------------------------------------------------------
/examples/bar.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.6.3
2 | (function() {
3 | var courses, dish, food, foods, i, _i, _j, _k, _len, _len1, _len2, _ref;
4 |
5 | _ref = ['toast', 'cheese', 'wine'];
6 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
7 | food = _ref[_i];
8 | eat(food);
9 | }
10 |
11 | courses = ['greens', 'caviar', 'truffles', 'roast', 'cake'];
12 |
13 | for (i = _j = 0, _len1 = courses.length; _j < _len1; i = ++_j) {
14 | dish = courses[i];
15 | menu(i + 1, dish);
16 | }
17 |
18 | foods = ['broccoli', 'spinach', 'chocolate'];
19 |
20 | for (_k = 0, _len2 = foods.length; _k < _len2; _k++) {
21 | food = foods[_k];
22 | if (food !== 'chocolate') {
23 | eat(food);
24 | }
25 | }
26 |
27 | }).call(this);
28 |
--------------------------------------------------------------------------------
/lib/source_map/mapping.rb:
--------------------------------------------------------------------------------
1 | require 'source_map/offset'
2 |
3 | module SourceMap
4 | class Mapping < Struct.new(:source, :generated, :original, :name)
5 | # Public: Get a simple string representation of the mapping.
6 | #
7 | # Returns a String.
8 | def to_s
9 | str = "#{generated.line}:#{generated.column}"
10 | str << "->#{source}@#{original.line}:#{original.column}"
11 | str << "##{name}" if name
12 | str
13 | end
14 |
15 | # Public: Get a pretty inspect output for debugging purposes.
16 | #
17 | # Returns a String.
18 | def inspect
19 | str = "#<#{self.class} source=#{source.inspect}"
20 | str << " generated=#{generated}, original=#{original}"
21 | str << " name=#{name.inspect}" if name
22 | str << ">"
23 | str
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/examples/foo.min.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["foo.js"],"names":["cubes","list","math","num","number","opposite","race","square","__slice","slice","x","root","Math","sqrt","cube","runners","winner","arguments","length","call","print","elvis","alert","_i","_len","_results","push","this"],"mappings":"CACA,WACE,GAAIA,OAAOC,KAAMC,KAAMC,IAAKC,OAAQC,SAAUC,KAAMC,OAClDC,WAAaC,KAEfL,QAAS,EAETC,UAAW,IAEX,IAAIA,SAAU,CACZD,QAAU,GAGZG,OAAS,SAASG,GAChB,MAAOA,GAAIA,EAGbT,OAAQ,EAAG,EAAG,EAAG,EAAG,EAEpBC,OACES,KAAMC,KAAKC,KACXN,OAAQA,OACRO,KAAM,SAASJ,GACb,MAAOA,GAAIH,OAAOG,IAItBJ,MAAO,WACL,GAAIS,SAASC,MACbA,QAASC,UAAU,GAAIF,QAAU,GAAKE,UAAUC,OAASV,QAAQW,KAAKF,UAAW,KACjF,OAAOG,OAAMJ,OAAQD,SAGvB,UAAWM,SAAU,aAAeA,QAAU,KAAM,CAClDC,MAAM,cAGRtB,MAAQ,WACN,GAAIuB,IAAIC,KAAMC,QACdA,YACA,KAAKF,GAAK,EAAGC,KAAOvB,KAAKiB,OAAQK,GAAKC,KAAMD,KAAM,CAChDpB,IAAMF,KAAKsB,GACXE,UAASC,KAAKxB,KAAKY,KAAKX,MAE1B,MAAOsB,cAGRN,KAAKQ"}
2 |
--------------------------------------------------------------------------------
/examples/foobar.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":null,"mappings":";AACA;CAAA,KAAA,gDAAA;KAAA,aAAA;;CAAA,CAAA,CAAW,GAAX;;CAAA,CACA,CAAW,CADX,IACA;;CAGA,CAAA,EAAgB,IAAhB;AAAU,CAAV,CAAA,CAAS,CAAT,EAAA;IAJA;;CAAA,CAOA,CAAS,GAAT,GAAU;CAAM,EAAI,QAAJ;CAPhB,EAOS;;CAPT,CAUA,CAAO,CAAP;;CAVA,CAaA,CACE,CADF;CACE,CAAQ,EAAR;CAAA,CACQ,EAAR,EAAA;CADA,CAEQ,CAAA,CAAR,KAAS;CAAM,EAAI,GAAA,OAAJ;CAFf,IAEQ;CAhBV,GAAA;;CAAA,CAmBA,CAAO,CAAP,KAAO;CACL,OAAA,OAAA;CAAA,CADc,EAAR,mDACN;CAAM,CAAQ,GAAd,CAAA,CAAA,IAAA;CApBF,EAmBO;;CAIP,CAAA,EAAsB,0CAAtB;CAAA,GAAA,CAAA,OAAA;IAvBA;;CAAA,CA0BA,GAAA;;AAAS,CAAA;UAAA,iCAAA;sBAAA;CAAA,EAAA,CAAI;CAAJ;;CA1BT;CAAA;;;ACAA;CAAA,KAAA,6DAAA;;CAAA;CAAA,MAAA,oCAAA;qBAAA;CAAA,EAAA,CAAA;CAAA,EAAA;;CAAA,CAGA,CAAU,GAAA,CAAV,CAAU,EAAA;;AACV,CAAA,MAAA,iDAAA;uBAAA;CAAA,CAAY,CAAH,CAAT;CAAA,EAJA;;CAAA,CAOA,CAAQ,EAAR,IAAQ,CAAA,CAAA;;AACR,CAAA,MAAA,uCAAA;sBAAA;IAAgC,CAAU;CAA1C,EAAA,CAAA,EAAA;MAAA;CAAA,EARA;CAAA;","sources":["foo.coffee","bar.coffee"],"names":[]}
2 |
--------------------------------------------------------------------------------
/sourcemap.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'source_map/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "sourcemap"
8 | spec.version = SourceMap::VERSION
9 | spec.authors = ["Josh Peek", "Alex MacCaw"]
10 | spec.email = ["alex@alexmaccaw.com"]
11 | spec.description = %q{Ruby source maps}
12 | spec.summary = %q{Ruby source maps}
13 | spec.homepage = "http://github.com/maccman/sourcemap"
14 | spec.license = "MIT"
15 |
16 | spec.files = `git ls-files`.split($/)
17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19 | spec.require_paths = ["lib"]
20 |
21 | spec.add_development_dependency "bundler", "~> 1.3"
22 | spec.add_development_dependency "rake"
23 | spec.add_development_dependency "minitest"
24 | end
25 |
--------------------------------------------------------------------------------
/examples/foo.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.6.3
2 | (function() {
3 | var cubes, list, math, num, number, opposite, race, square,
4 | __slice = [].slice;
5 |
6 | number = 42;
7 |
8 | opposite = true;
9 |
10 | if (opposite) {
11 | number = -42;
12 | }
13 |
14 | square = function(x) {
15 | return x * x;
16 | };
17 |
18 | list = [1, 2, 3, 4, 5];
19 |
20 | math = {
21 | root: Math.sqrt,
22 | square: square,
23 | cube: function(x) {
24 | return x * square(x);
25 | }
26 | };
27 |
28 | race = function() {
29 | var runners, winner;
30 | winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
31 | return print(winner, runners);
32 | };
33 |
34 | if (typeof elvis !== "undefined" && elvis !== null) {
35 | alert("I knew it!");
36 | }
37 |
38 | cubes = (function() {
39 | var _i, _len, _results;
40 | _results = [];
41 | for (_i = 0, _len = list.length; _i < _len; _i++) {
42 | num = list[_i];
43 | _results.push(math.cube(num));
44 | }
45 | return _results;
46 | })();
47 |
48 | }).call(this);
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Alex MacCaw, Joshua Peek
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/test/test_mapping.rb:
--------------------------------------------------------------------------------
1 | require 'minitest/autorun'
2 | require 'source_map/offset'
3 |
4 | class TestMapping < MiniTest::Test
5 | include SourceMap
6 |
7 | def setup
8 | @mapping = Mapping.new("a.js", Offset.new(1, 5), Offset.new(2, 0), "foo")
9 | end
10 |
11 | def test_equal
12 | assert @mapping.dup == @mapping
13 | assert Mapping.new("b.js", Offset.new(1, 5), Offset.new(2, 0), "foo") != @mapping
14 | assert Mapping.new("a.js", Offset.new(1, 5), Offset.new(2, 0), "bar") != @mapping
15 | assert Mapping.new("a.js", Offset.new(1, 6), Offset.new(2, 0), "foo") != @mapping
16 | assert Mapping.new("a.js", Offset.new(1, 5), Offset.new(3, 0), "foo") != @mapping
17 | end
18 |
19 | def test_source
20 | assert_equal "a.js", @mapping.source
21 | end
22 |
23 | def test_generated
24 | assert_equal Offset.new(1, 5), @mapping.generated
25 | end
26 |
27 | def test_original
28 | assert_equal Offset.new(2, 0), @mapping.original
29 | end
30 |
31 | def test_name
32 | assert_equal "foo", @mapping.name
33 | end
34 |
35 | def test_to_s
36 | assert_equal "1:5->a.js@2:0#foo", @mapping.to_s
37 | end
38 |
39 | def test_inspect
40 | assert_equal "#", @mapping.inspect
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/test/test_offset.rb:
--------------------------------------------------------------------------------
1 | require 'minitest/autorun'
2 | require 'source_map/offset'
3 |
4 | class TestOffset < MiniTest::Test
5 | include SourceMap
6 |
7 | def setup
8 | @offset = Offset.new(1, 5)
9 | end
10 |
11 | def test_equal
12 | assert Offset.new(1, 5) == @offset
13 | assert Offset.new(1, 6) != @offset
14 | assert Offset.new(2, 5) != @offset
15 | end
16 |
17 | def test_from_array
18 | assert Offset.new(1, 5) == Offset.new([1, 5])
19 | end
20 |
21 | def test_from_offset
22 | assert @offset == Offset.new(@offset)
23 | end
24 |
25 | def test_line
26 | assert_equal 1, @offset.line
27 | end
28 |
29 | def test_column
30 | assert_equal 5, @offset.column
31 | end
32 |
33 | def test_to_s
34 | assert_equal "0", Offset.new(0, 0).to_s
35 | assert_equal "1", Offset.new(1, 0).to_s
36 | assert_equal "1:5", Offset.new(1, 5).to_s
37 | end
38 |
39 | def test_inspect
40 | assert_equal "#", @offset.inspect
41 | end
42 |
43 | def test_add_offset
44 | offset = @offset + Offset.new(2, 1)
45 | assert_equal 3, offset.line
46 | assert_equal 6, offset.column
47 | end
48 |
49 | def test_add_line
50 | offset = @offset + 5
51 | assert_equal 6, offset.line
52 | assert_equal 5, offset.column
53 | end
54 |
55 | def test_compare
56 | assert @offset < Offset.new(2, 0)
57 | assert @offset < Offset.new(1, 6)
58 | assert @offset > Offset.new(1, 4)
59 | assert @offset >= Offset.new(1, 5)
60 | assert @offset <= Offset.new(1, 5)
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/examples/foobar.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.6.3
2 | (function() {
3 | var cubes, list, math, num, number, opposite, race, square,
4 | __slice = [].slice;
5 |
6 | number = 42;
7 |
8 | opposite = true;
9 |
10 | if (opposite) {
11 | number = -42;
12 | }
13 |
14 | square = function(x) {
15 | return x * x;
16 | };
17 |
18 | list = [1, 2, 3, 4, 5];
19 |
20 | math = {
21 | root: Math.sqrt,
22 | square: square,
23 | cube: function(x) {
24 | return x * square(x);
25 | }
26 | };
27 |
28 | race = function() {
29 | var runners, winner;
30 | winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
31 | return print(winner, runners);
32 | };
33 |
34 | if (typeof elvis !== "undefined" && elvis !== null) {
35 | alert("I knew it!");
36 | }
37 |
38 | cubes = (function() {
39 | var _i, _len, _results;
40 | _results = [];
41 | for (_i = 0, _len = list.length; _i < _len; _i++) {
42 | num = list[_i];
43 | _results.push(math.cube(num));
44 | }
45 | return _results;
46 | })();
47 |
48 | }).call(this);
49 | // Generated by CoffeeScript 1.6.3
50 | (function() {
51 | var courses, dish, food, foods, i, _i, _j, _k, _len, _len1, _len2, _ref;
52 |
53 | _ref = ['toast', 'cheese', 'wine'];
54 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
55 | food = _ref[_i];
56 | eat(food);
57 | }
58 |
59 | courses = ['greens', 'caviar', 'truffles', 'roast', 'cake'];
60 |
61 | for (i = _j = 0, _len1 = courses.length; _j < _len1; i = ++_j) {
62 | dish = courses[i];
63 | menu(i + 1, dish);
64 | }
65 |
66 | foods = ['broccoli', 'spinach', 'chocolate'];
67 |
68 | for (_k = 0, _len2 = foods.length; _k < _len2; _k++) {
69 | food = foods[_k];
70 | if (food !== 'chocolate') {
71 | eat(food);
72 | }
73 | }
74 |
75 | }).call(this);
76 |
77 | //@ sourceMappingURL=foobar.map
78 |
--------------------------------------------------------------------------------
/lib/source_map/offset.rb:
--------------------------------------------------------------------------------
1 | module SourceMap
2 | # Public: Offset is an immutable structure representing a position in
3 | # a source file.
4 | class Offset
5 | include Comparable
6 |
7 | # Public: Construct Offset value.
8 | #
9 | # Returns Offset instance.
10 | def self.new(*args)
11 | case args.first
12 | when Offset
13 | args.first
14 | when Array
15 | super(*args.first)
16 | else
17 | super(*args)
18 | end
19 | end
20 |
21 | # Public: Initialize an Offset.
22 | #
23 | # line - Integer line number
24 | # column - Integer column number
25 | def initialize(line, column)
26 | @line, @column = line, column
27 | end
28 |
29 | # Public: Gets Integer line of offset
30 | attr_reader :line
31 |
32 | # Public: Get Integer column of offset
33 | attr_reader :column
34 |
35 | # Public: Shift the offset by some value.
36 | #
37 | # other - An Offset to add by its line and column
38 | # Or an Integer to add by line
39 | #
40 | # Returns a new Offset instance.
41 | def +(other)
42 | case other
43 | when Offset
44 | Offset.new(self.line + other.line, self.column + other.column)
45 | when Integer
46 | Offset.new(self.line + other, self.column)
47 | else
48 | raise ArgumentError, "can't convert #{other} into #{self.class}"
49 | end
50 | end
51 |
52 | # Public: Compare Offset to another.
53 | #
54 | # Useful for determining if a position in a few is between two offsets.
55 | #
56 | # other - Another Offset
57 | #
58 | # Returns a negative number when other is smaller and a positive number
59 | # when its greater. Implements the Comparable#<=> protocol.
60 | def <=>(other)
61 | case other
62 | when Offset
63 | diff = self.line - other.line
64 | diff.zero? ? self.column - other.column : diff
65 | else
66 | raise ArgumentError, "can't convert #{other.class} into #{self.class}"
67 | end
68 | end
69 |
70 | # Public: Get a simple string representation of the offset
71 | #
72 | # Returns a String.
73 | def to_s
74 | if column == 0
75 | "#{line}"
76 | else
77 | "#{line}:#{column}"
78 | end
79 | end
80 |
81 | # Public: Get a pretty inspect output for debugging purposes.
82 | #
83 | # Returns a String.
84 | def inspect
85 | "#<#{self.class} line=#{line}, column=#{column}>"
86 | end
87 | end
88 | end
89 |
--------------------------------------------------------------------------------
/lib/source_map/vlq.rb:
--------------------------------------------------------------------------------
1 | module SourceMap
2 | # Public: Base64 VLQ encoding
3 | #
4 | # Adopted from ConradIrwin/ruby-source_map
5 | # https://github.com/ConradIrwin/ruby-source_map/blob/master/lib/source_map/vlq.rb
6 | #
7 | # Resources
8 | #
9 | # http://en.wikipedia.org/wiki/Variable-length_quantity
10 | # https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
11 | # https://github.com/mozilla/source-map/blob/master/lib/source-map/base64-vlq.js
12 | #
13 | module VLQ
14 | VLQ_BASE_SHIFT = 5
15 | VLQ_BASE = 1 << VLQ_BASE_SHIFT
16 | VLQ_BASE_MASK = VLQ_BASE - 1
17 | VLQ_CONTINUATION_BIT = VLQ_BASE
18 |
19 | BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('')
20 | BASE64_VALUES = (0...64).inject({}) { |h, i| h[BASE64_DIGITS[i]] = i; h }
21 |
22 | # Public: Encode a list of numbers into a compact VLQ string.
23 | #
24 | # ary - An Array of Integers
25 | #
26 | # Returns a VLQ String.
27 | def self.encode(ary)
28 | result = []
29 | ary.each do |n|
30 | vlq = n < 0 ? ((-n) << 1) + 1 : n << 1
31 | loop do
32 | digit = vlq & VLQ_BASE_MASK
33 | vlq >>= VLQ_BASE_SHIFT
34 | digit |= VLQ_CONTINUATION_BIT if vlq > 0
35 | result << BASE64_DIGITS[digit]
36 |
37 | break unless vlq > 0
38 | end
39 | end
40 | result.join
41 | end
42 |
43 | # Public: Decode a VLQ string.
44 | #
45 | # str - VLQ encoded String
46 | #
47 | # Returns an Array of Integers.
48 | def self.decode(str)
49 | result = []
50 | chars = str.split('')
51 | while chars.any?
52 | vlq = 0
53 | shift = 0
54 | continuation = true
55 | while continuation
56 | char = chars.shift
57 | raise ArgumentError unless char
58 | digit = BASE64_VALUES[char]
59 | continuation = false if (digit & VLQ_CONTINUATION_BIT) == 0
60 | digit &= VLQ_BASE_MASK
61 | vlq += digit << shift
62 | shift += VLQ_BASE_SHIFT
63 | end
64 | result << (vlq & 1 == 1 ? -(vlq >> 1) : vlq >> 1)
65 | end
66 | result
67 | end
68 |
69 | # Public: Encode a mapping array into a compact VLQ string.
70 | #
71 | # ary - Two dimensional Array of Integers.
72 | #
73 | # Returns a VLQ encoded String seperated by , and ;.
74 | def self.encode_mappings(ary)
75 | ary.map { |group|
76 | group.map { |segment|
77 | encode(segment)
78 | }.join(',')
79 | }.join(';')
80 | end
81 |
82 | # Public: Decode a VLQ string into mapping numbers.
83 | #
84 | # str - VLQ encoded String
85 | #
86 | # Returns an two dimensional Array of Integers.
87 | def self.decode_mappings(str)
88 | mappings = []
89 |
90 | str.split(';').each_with_index do |group, index|
91 | mappings[index] = []
92 | group.split(',').each do |segment|
93 | mappings[index] << decode(segment)
94 | end
95 | end
96 |
97 | mappings
98 | end
99 | end
100 | end
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ruby Source Maps
2 |
3 | A Ruby library to read, create and manipulate Source Maps.
4 |
5 | [Source Maps](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/) allow easy debugging and development of CoffeeScript and minifed JavaScript.
6 |
7 | # Installation
8 |
9 | gem install sourcemap
10 | # or Bundler:
11 | gem 'sourcemap'
12 |
13 | # Usage
14 |
15 | ## Concatenation
16 |
17 | Join multiple source maps together.
18 |
19 | ``` ruby
20 | foo = File.read("examples/foo.js")
21 | bar = File.read("examples/bar.js")
22 | foobar = foo + bar
23 |
24 | foo_map = SourceMap::Map.from_json File.read("examples/foo.map")
25 | bar_map = SourceMap::Map.from_json File.read("examples/bar.map")
26 | foobar_map = foo_map + bar_map
27 | foobar_map.to_json
28 | ```
29 |
30 | ## Piping
31 |
32 | Base one source map of another.
33 |
34 | ``` ruby
35 | cs_map = SourceMap::Map.from_json File.read("examples/index.map")
36 | min_map = SourceMap::Map.from_json File.read("examples/index.min.map")
37 | combined_map = cs_map | min_map
38 | combined_map.to_json
39 | ```
40 |
41 | ## Map
42 |
43 | ### Map.from_json(json)
44 |
45 | Create a new `Map` instance from a JSON map string.
46 |
47 | SourceMap::Map.from_json(%{
48 | {
49 | "version": 3,
50 | "file": "index.js",
51 | "sourceRoot": "",
52 | "sources": [
53 | "index.coffee"
54 | ],
55 | "names": [],
56 | "mappings": ";AAAA;AAAA,MAAA,IAAA;;AAAA"
57 | }
58 | })
59 |
60 | ### Map.from_hash(hash)
61 |
62 | Create a new `Map` instance from a hash.
63 |
64 | hash = {
65 | 'version' => 3,
66 | 'file' => "script.min.js",
67 | 'mappings' => "AAEAA,QAASA,MAAK,EAAG,CACfC,OAAAC,IAAA,CAAY,eAAZ,CADe",
68 | 'sources' => ["script.js"],
69 | 'names' => ["hello", "console", "log"]
70 | }
71 | map = SourceMap::Map.from_hash(hash)
72 |
73 | ### Map.new(mappings = [], filename = nil)
74 |
75 | Instantiate a `Map` instance, passing in an optional array of `Mapping`s and file name.
76 |
77 | @mappings = SourceMap::Map.new([
78 | SourceMap::Mapping.new('a.js', SourceMap::Offset.new(0, 0), SourceMap::Offset.new(0, 0)),
79 | SourceMap::Mapping.new('b.js', SourceMap::Offset.new(1, 0), SourceMap::Offset.new(20, 0)),
80 | SourceMap::Mapping.new('c.js', SourceMap::Offset.new(2, 0), SourceMap::Offset.new(30, 0))
81 | ])
82 |
83 | ### Map#size
84 |
85 | Returns the amount of mappings
86 |
87 | ### Map#[]
88 |
89 | Lookup a mapping by integer
90 |
91 | map = SourceMap::Map.from_json(json)
92 | map[5] #=>
93 |
94 | ### Map#each
95 |
96 | Iterate over each mapping.
97 |
98 | ### Map#to_s
99 |
100 | Returns a VLQ representation of the source map.
101 |
102 | mapping = SourceMap::Map.from_hash(hash)
103 | mappings.to_s #=> "ACmBA;ACUA"
104 |
105 | ### Map#sources
106 |
107 | Returns an array of the original file names referenced in each mapping.
108 |
109 | ### Map#names
110 |
111 | Returns an array of 'names', which are referenced in the mappings (in case the original source file is not available).
112 |
113 | ### Map#+
114 |
115 | Concatenates Maps together, so you can serve mappings from multiple sources as one combined map.
116 |
117 | foo_map = SourceMap::Map.from_json File.read("examples/foo.map")
118 | bar_map = SourceMap::Map.from_json File.read("examples/bar.map")
119 | foobar_map = foo_map + bar_map
120 | foobar_map.to_json
121 |
122 | ### Map#|
123 |
124 | Pipes map files together, so for example you could pipe a CoffeeScript map of `index.coffee` and an uglifier map of `index.js` together. In other words, one mapping will be based of the other.
125 |
126 | coffeescript_map = SourceMap::Map.from_json(cs_map_json)
127 | uglifier_map = SourceMap::Map.from_json(min_map_json)
128 |
129 | combined_map = coffeescript_map | uglifier_map
130 |
131 | ### Map#bsearch(offset)
132 |
133 | Find the closest generated mapping to any given offset using a binary tree search.
134 |
135 | foo_map = SourceMap::Map.from_json File.read("examples/foo.map")
136 | foo_map.bsearch(SourceMap::Offset.new(1,1)) #=>
137 |
138 | The method will return `nil` if an offset can't be found.
139 |
140 | ### Map#as_json
141 |
142 | Convert a `Map` instance back to JSON.
143 |
144 | map = Map.new([
145 | Mapping.new('a.js', Offset.new(0, 0), Offset.new(0, 0)),
146 | Mapping.new('b.js', Offset.new(1, 0), Offset.new(20, 0)),
147 | Mapping.new('c.js', Offset.new(2, 0), Offset.new(30, 0))
148 | ])
149 |
150 | map.to_json #=> "{...}"
151 |
152 | ## Offset
153 |
154 | ### Offset.new(line, column)
155 |
156 | Instantiate an `Offset`, passing in a line and column integer.
157 |
158 | ### Offset#+
159 |
160 | Add two offsets together.
161 |
162 | ### Offset#<=>
163 |
164 | Compare the position of two offsets, first the line than the column.
165 |
166 | ## Offset#to_s
167 |
168 | Get a pretty representation of an offset.
169 |
--------------------------------------------------------------------------------
/test/test_vlq.rb:
--------------------------------------------------------------------------------
1 | require 'minitest/autorun'
2 | require 'source_map/vlq'
3 |
4 | class TestVLQ < MiniTest::Test
5 | include SourceMap
6 |
7 | TESTS = {
8 | 'A' => [0],
9 | 'C' => [1],
10 | 'D' => [-1],
11 | 'E' => [2],
12 | 'F' => [-2],
13 | 'K' => [5],
14 | 'L' => [-5],
15 | 'w+B' => [1000],
16 | 'x+B' => [-1000],
17 | 'gqjG' => [100000],
18 | 'hqjG' => [-100000],
19 | 'AAgBC' => [0, 0, 16, 1],
20 | 'AAgCgBC' => [0, 0, 32, 16, 1],
21 | 'DFLx+BhqjG' => [-1, -2, -5, -1000, -100000],
22 | 'CEKw+BgqjG' => [1, 2, 5, 1000, 100000]
23 | }
24 |
25 | MAP_TESTS = {
26 | 'AA,AA;;AACDE' => [[[0, 0], [0, 0]], [], [[0, 0, 1, -1, 2]]],
27 | ';;;;EAEE,EAAE,EAAC,CAAE;ECQY,UACC' => [[], [], [], [], [[2, 0, 2, 2], [2, 0, 0, 2], [2, 0, 0, 1], [1, 0, 0, 2]], [[2, 1, 8, 12], [10, 0, 1, 1]]],
28 | 'AAEAA,QAASA,MAAK,EAAG,CACfC,OAAAC,IAAA,CAAY,eAAZ,CADe' => [[[0, 0, 2, 0, 0], [8, 0, 0, 9, 0], [6, 0, 0, 5], [2, 0, 0, 3], [1, 0, 1, -15, 1], [7, 0, 0, 0, 1], [4, 0, 0, 0], [1, 0, 0, 12], [15, 0, 0, -12], [1, 0, -1, 15]]],
29 | ';;;;;EACAA;;EACAC;;EAGA;IAAA;;;EAGAC;IAAS;;;EAGTC;;EAGAC;IACE;IACA;IACA;MAAQ;;;;EAGVC;;;IACE;;;EAGF;IAAA;;;EAGAC;;;IAAQ;;MAAA' => [[], [], [], [], [], [[2, 0, 1, 0, 0]], [], [[2, 0, 1, 0, 1]], [], [[2, 0, 3, 0]], [[4, 0, 0, 0]], [], [], [[2, 0, 3, 0, 1]], [[4, 0, 0, 9]], [], [], [[2, 0, 3, -9, 1]], [], [[2, 0, 3, 0, 1]], [[4, 0, 1, 2]], [[4, 0, 1, 0]], [[4, 0, 1, 0]], [[6, 0, 0, 8]], [], [], [], [[2, 0, 3, -10, 1]], [], [], [[4, 0, 1, 2]], [], [], [[2, 0, 3, -2]], [[4, 0, 0, 0]], [], [], [[2, 0, 3, 0, 1]], [], [], [[4, 0, 0, 8]], [], [[6, 0, 0, 0]]],
30 | 'AACC,SAAQ,EAAG,CAAA,IACCA,CADD,CACOC,CADP,CACaC,CADb,CAC0CC,CAWpDA,EAAA,CAASA,QAAQ,CAACC,CAAD,CAAI,CACnB,MAAOA,EAAP,CAAWA,CADQ,CAIrBJ,EAAA,CAAO,CAAC,CAAD,CAAI,CAAJ,CAAO,CAAP,CAAU,CAAV,CAAa,CAAb,CAEPC,EAAA,CAAO,MACCI,IAAAC,KADD,QAEGH,CAFH,MAGCI,QAAQ,CAACH,CAAD,CAAI,CAChB,MAAOA,EAAP,CAAWD,CAAA,CAAOC,CAAP,CADK,CAHb,CAcc,YAArB,GAAI,MAAOI,MAAX,EAA8C,IAA9C,GAAoCA,KAApC,EACEC,KAAA,CAAM,YAAN,CAGO,UAAQ,EAAG,CAAA,IACdC,CADc,CACVC,CADU,CACJC,CACdA,EAAA,CAAW,EACNF,EAAA,CAAK,CAAV,KAAaC,CAAb,CAAoBX,CAAAa,OAApB,CAAiCH,CAAjC,CAAsCC,CAAtC,CAA4CD,CAAA,EAA5C,CACER,CACA,CADMF,CAAA,CAAKU,CAAL,CACN,CAAAE,CAAAE,KAAA,CAAcb,CAAAM,KAAA,CAAUL,CAAV,CAAd,CAEF,OAAOU,EAPW,CAAX,CAAA,EApCC,CAAX,CAAAG,KAAA,CA8CO,IA9CP' => [[[0, 0, 1, 1], [9, 0, 0, 8], [2, 0, 0, 3], [1, 0, 0, 0], [4, 0, 1, 1, 0], [1, 0, -1, -1], [1, 0, 1, 7, 1], [1, 0, -1, -7], [1, 0, 1, 13, 1], [1, 0, -1, -13], [1, 0, 1, 42, 1], [1, 0, 11, -52, 0], [2, 0, 0, 0], [1, 0, 0, 9, 0], [8, 0, 0, 8], [1, 0, 0, 1, 1], [1, 0, 0, -1], [1, 0, 0, 4], [1, 0, 1, -19], [6, 0, 0, 7, 0], [2, 0, 0, -7], [1, 0, 0, 11, 0], [1, 0, -1, 8], [1, 0, 4, -21, -4], [2, 0, 0, 0], [1, 0, 0, 7], [1, 0, 0, 1], [1, 0, 0, -1], [1, 0, 0, 4], [1, 0, 0, -4], [1, 0, 0, 7], [1, 0, 0, -7], [1, 0, 0, 10], [1, 0, 0, -10], [1, 0, 0, 13], [1, 0, 0, -13], [1, 0, 2, -7, 1], [2, 0, 0, 0], [1, 0, 0, 7], [6, 0, 1, 1, 4], [4, 0, 0, 0, 1], [5, 0, -1, -1], [8, 0, 2, 3, -3], [1, 0, -2, -3], [6, 0, 3, 1, 4], [8, 0, 0, 8], [1, 0, 0, 1, -3], [1, 0, 0, -1], [1, 0, 0, 4], [1, 0, 1, -16], [6, 0, 0, 7, 0], [2, 0, 0, -7], [1, 0, 0, 11, -1], [1, 0, 0, 0], [1, 0, 0, 7, 1], [1, 0, 0, -7], [1, 0, -1, 5], [1, 0, -3, -13], [1, 0, 14, 14], [12, 0, 0, -21], [3, 0, 0, 4], [6, 0, 0, 7, 4], [6, 0, 0, -11], [2, 0, 0, 46], [4, 0, 0, -46], [3, 0, 0, 36, 0], [5, 0, 0, -36], [2, 0, 1, 2, 1], [5, 0, 0, 0], [1, 0, 0, 6], [12, 0, 0, -6], [1, 0, 3, 7], [10, 0, 0, 8], [2, 0, 0, 3], [1, 0, 0, 0], [4, 0, 1, -14, 1], [1, 0, -1, 14], [1, 0, 1, -10, 1], [1, 0, -1, 10], [1, 0, 1, -4, 1], [1, 0, 1, -14, 0], [2, 0, 0, 0], [1, 0, 0, 11], [2, 0, 1, -6, -2], [2, 0, 0, 0], [1, 0, 0, 5], [1, 0, 0, -10], [5, 0, 0, 13, 1], [1, 0, 0, -13], [1, 0, 0, 20, -11], [1, 0, 0, 0, 13], [7, 0, 0, -20], [1, 0, 0, 33, -3], [1, 0, 0, -33], [1, 0, 0, 38, 1], [1, 0, 0, -38], [1, 0, 0, 44, -1], [1, 0, 0, 0], [2, 0, 0, -44], [1, 0, 1, 2, -8], [1, 0, 1, 0], [1, 0, -1, 6, -2], [1, 0, 0, 0], [1, 0, 0, 5, 10], [1, 0, 0, -5], [1, 0, 1, -6], [1, 0, 0, 0, 2], [1, 0, 0, 0, 2], [5, 0, 0, 0], [1, 0, 0, 14, -13], [1, 0, 0, 0, 6], [5, 0, 0, 0], [1, 0, 0, 10, -5], [1, 0, 0, -10], [1, 0, 0, -14], [1, 0, 2, -2], [7, 0, 0, 7, 10], [2, 0, -7, 11], [1, 0, 0, -11], [1, 0, 0, 0], [2, 0, -36, 1], [1, 0, 0, -11], [1, 0, 0, 0, 3], [5, 0, 0, 0], [1, 0, 46, 7], [4, 0, -46, -7]]]
31 | }
32 |
33 | def test_encode
34 | TESTS.each do |str, int|
35 | assert_equal str, VLQ.encode(int)
36 | end
37 | end
38 |
39 | def test_decode
40 | TESTS.each do |str, int|
41 | assert_equal int, VLQ.decode(str)
42 | end
43 | end
44 |
45 | def test_encode_decode
46 | (-255..255).each do |int|
47 | assert_equal [int], VLQ.decode(VLQ.encode([int]))
48 | end
49 | end
50 |
51 | def test_encode_mappings
52 | MAP_TESTS.each do |str, ary|
53 | assert_equal str, VLQ.encode_mappings(ary)
54 | end
55 | end
56 |
57 | def test_decode_mappings
58 | MAP_TESTS.each do |str, ary|
59 | assert_equal ary, VLQ.decode_mappings(str)
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/lib/source_map/map.rb:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | require 'source_map/offset'
4 | require 'source_map/mapping'
5 | require 'source_map/vlq'
6 |
7 | module SourceMap
8 | class Map
9 | include Enumerable
10 |
11 | def self.from_json(json)
12 | from_hash JSON.parse(json)
13 | end
14 |
15 | def self.from_hash(hash)
16 | str = hash['mappings']
17 | sources = hash['sources']
18 | names = hash['names']
19 |
20 | mappings = decode_vlq_mappings(str, sources, names)
21 | new(mappings, hash['file'])
22 | end
23 |
24 | # Internal: Decode VLQ mappings and match up sources and symbol names.
25 | #
26 | # str - VLQ string from 'mappings' attribute
27 | # sources - Array of Strings from 'sources' attribute
28 | # names - Array of Strings from 'names' attribute
29 | #
30 | # Returns an Array of Mappings.
31 | def self.decode_vlq_mappings(str, sources = [], names = [])
32 | mappings = []
33 |
34 | source_id = 0
35 | original_line = 1
36 | original_column = 0
37 | name_id = 0
38 |
39 | VLQ.decode_mappings(str).each_with_index do |group, index|
40 | generated_column = 0
41 | generated_line = index + 1
42 |
43 | group.each do |segment|
44 | generated_column += segment[0]
45 | generated = Offset.new(generated_line, generated_column)
46 |
47 | if segment.size >= 4
48 | source_id += segment[1]
49 | original_line += segment[2]
50 | original_column += segment[3]
51 |
52 | source = sources[source_id]
53 | original = Offset.new(original_line, original_column)
54 | else
55 | # TODO: Research this case
56 | next
57 | end
58 |
59 | if segment[4]
60 | name_id += segment[4]
61 | name = names[name_id]
62 | end
63 |
64 | mappings << Mapping.new(source, generated, original, name)
65 | end
66 | end
67 |
68 | mappings
69 | end
70 |
71 | def initialize(mappings = [], filename = nil)
72 | @mappings, @filename = mappings, filename
73 | end
74 |
75 | attr_reader :filename
76 |
77 | def size
78 | @mappings.size
79 | end
80 |
81 | def [](i)
82 | @mappings[i]
83 | end
84 |
85 | def each(&block)
86 | @mappings.each(&block)
87 | end
88 |
89 | def to_s
90 | @string ||= build_vlq_string
91 | end
92 |
93 | def sources
94 | @sources ||= @mappings.map(&:source).uniq.compact
95 | end
96 |
97 | def names
98 | @names ||= @mappings.map(&:name).uniq.compact
99 | end
100 |
101 | def ==(other)
102 | eql?(other)
103 | end
104 |
105 | def eql?(other)
106 | other.is_a?(self.class) &&
107 | self.mappings == other.mappings &&
108 | self.filename == other.filename
109 | end
110 |
111 | def +(other)
112 | mappings = @mappings.dup
113 | offset = @mappings.any? ? @mappings.last.generated.line+1 : 0
114 | other.each do |m|
115 | mappings << Mapping.new(
116 | m.source, m.generated + offset,
117 | m.original, m.name
118 | )
119 | end
120 | self.class.new(mappings, other.filename)
121 | end
122 |
123 | def |(other)
124 | return other.dup if self.mappings.empty?
125 |
126 | mappings = []
127 |
128 | other.each do |m|
129 | om = bsearch(m.original)
130 | next unless om
131 |
132 | mappings << Mapping.new(
133 | om.source, m.generated,
134 | om.original, om.name
135 | )
136 | end
137 |
138 | self.class.new(mappings, other.filename)
139 | end
140 |
141 | def bsearch(offset, from = 0, to = size - 1)
142 | mid = (from + to) / 2
143 |
144 | # We haven't found a match
145 | if from > to
146 | return from < 1 ? nil : self[from-1]
147 | end
148 |
149 | # We found an exact match
150 | if offset == self[mid].generated
151 | self[mid]
152 |
153 | # We need to filter more
154 | elsif offset < self[mid].generated
155 | bsearch(offset, from, mid - 1)
156 | elsif offset > self[mid].generated
157 | bsearch(offset, mid + 1, to)
158 | end
159 | end
160 |
161 | def as_json(*)
162 | {
163 | "version" => 3,
164 | "file" => filename,
165 | "mappings" => to_s,
166 | "sources" => sources,
167 | "names" => names
168 | }
169 | end
170 |
171 | def to_json(*a)
172 | as_json.to_json(*a)
173 | end
174 |
175 | # Public: Get a pretty inspect output for debugging purposes.
176 | #
177 | # Returns a String.
178 | def inspect
179 | str = "#<#{self.class}"
180 | str << " filename=#{filename.inspect}" if filename
181 | str << " mappings=#{mappings.map(&:to_s).inspect}" if mappings.any?
182 | str << ">"
183 | str
184 | end
185 |
186 | protected
187 | attr_reader :mappings
188 |
189 | def build_vlq_string
190 | source_id = 0
191 | source_line = 1
192 | source_column = 0
193 | name_id = 0
194 |
195 | by_lines = @mappings.group_by { |m| m.generated.line }
196 |
197 | sources_index = Hash[sources.each_with_index.to_a]
198 | names_index = Hash[names.each_with_index.to_a]
199 |
200 | ary = (1..(by_lines.keys.max || 1)).map do |line|
201 | generated_column = 0
202 |
203 | (by_lines[line] || []).map do |mapping|
204 | group = []
205 | group << mapping.generated.column - generated_column
206 | group << sources_index[mapping.source] - source_id
207 | group << mapping.original.line - source_line
208 | group << mapping.original.column - source_column
209 | group << names_index[mapping.name] - name_id if mapping.name
210 |
211 | generated_column = mapping.generated.column
212 | source_id = sources_index[mapping.source]
213 | source_line = mapping.original.line
214 | source_column = mapping.original.column
215 | name_id = names_index[mapping.name] if mapping.name
216 |
217 | group
218 | end
219 | end
220 |
221 | VLQ.encode_mappings(ary)
222 | end
223 | end
224 | end
225 |
--------------------------------------------------------------------------------
/test/test_map.rb:
--------------------------------------------------------------------------------
1 | require 'minitest/autorun'
2 | require 'source_map/map'
3 |
4 | class TestMap < MiniTest::Test
5 | include SourceMap
6 |
7 | def setup
8 | @mappings = Map.new([
9 | Mapping.new('a.js', Offset.new(0, 0), Offset.new(0, 0)),
10 | Mapping.new('b.js', Offset.new(1, 0), Offset.new(20, 0)),
11 | Mapping.new('c.js', Offset.new(2, 0), Offset.new(30, 0))
12 | ])
13 | end
14 |
15 | def test_map
16 | hash = {
17 | 'version' => 3,
18 | 'file' => "script.min.js",
19 | 'mappings' => "AAEAA,QAASA,MAAK,EAAG,CACfC,OAAAC,IAAA,CAAY,eAAZ,CADe",
20 | 'sources' => ["script.js"],
21 | 'names' => ["hello", "console", "log"]
22 | }
23 | map = Map.from_hash(hash)
24 |
25 | assert mapping = map[0]
26 | assert_equal 1, mapping.generated.line
27 | assert_equal 0, mapping.generated.column
28 | assert_equal 3, mapping.original.line
29 | assert_equal 0, mapping.original.column
30 | assert_equal 'script.js', mapping.source
31 | assert_equal 'hello', mapping.name
32 |
33 | assert mapping = map[-1]
34 | assert_equal 1, mapping.generated.line
35 | assert_equal 45, mapping.generated.column
36 | assert_equal 3, mapping.original.line
37 | assert_equal 17, mapping.original.column
38 | assert_equal 'script.js', mapping.source
39 | assert_equal nil, mapping.name
40 |
41 | assert_equal hash['sources'], map.sources
42 | assert_equal hash['names'], map.names
43 | assert_equal hash['mappings'], map.to_s
44 |
45 | assert_equal hash, map.as_json
46 | assert_equal hash.to_json, map.to_json
47 | assert_equal hash.to_json, JSON.generate(map)
48 | end
49 |
50 | def test_map2
51 | hash = {
52 | 'version' => 3,
53 | 'file' => "example.js",
54 | 'mappings' => ";;;;;EACAA;;EACAC;;EAGA;IAAA;;;EAGAC;IAAS;;;EAGTC;;EAGAC;IACE;IACA;IACA;MAAQ;;;;EAGVC;;;IACE;;;EAGF;IAAA;;;EAGAC;;;IAAQ;;MAAA",
55 | 'sources' => ["example.coffee"],
56 | 'names' => ["number", "opposite", "square", "list", "math", "race", "cubes"]
57 | }
58 | map = Map.from_hash(hash)
59 |
60 | assert mapping = map[0]
61 | assert_equal 6, mapping.generated.line
62 | assert_equal 2, mapping.generated.column
63 | assert_equal 2, mapping.original.line
64 | assert_equal 0, mapping.original.column
65 | assert_equal 'example.coffee', mapping.source
66 | assert_equal 'number', mapping.name
67 |
68 | assert mapping = map[-1]
69 | assert_equal 43, mapping.generated.line
70 | assert_equal 6, mapping.generated.column
71 | assert_equal 28, mapping.original.line
72 | assert_equal 8, mapping.original.column
73 | assert_equal 'example.coffee', mapping.source
74 | assert_equal nil, mapping.name
75 |
76 | assert_equal hash['sources'], map.sources
77 | assert_equal hash['names'], map.names
78 | assert_equal hash['mappings'], map.to_s
79 | end
80 |
81 | def test_map3
82 | hash = {
83 | 'version' => 3,
84 | 'file' => "example.min.js",
85 | 'mappings' => "AACC,SAAQ,EAAG,CAAA,IACCA,CADD,CACOC,CADP,CACaC,CADb,CAC0CC,CAWpDA,EAAA,CAASA,QAAQ,CAACC,CAAD,CAAI,CACnB,MAAOA,EAAP,CAAWA,CADQ,CAIrBJ,EAAA,CAAO,CAAC,CAAD,CAAI,CAAJ,CAAO,CAAP,CAAU,CAAV,CAAa,CAAb,CAEPC,EAAA,CAAO,MACCI,IAAAC,KADD,QAEGH,CAFH,MAGCI,QAAQ,CAACH,CAAD,CAAI,CAChB,MAAOA,EAAP,CAAWD,CAAA,CAAOC,CAAP,CADK,CAHb,CAcc,YAArB,GAAI,MAAOI,MAAX,EAA8C,IAA9C,GAAoCA,KAApC,EACEC,KAAA,CAAM,YAAN,CAGO,UAAQ,EAAG,CAAA,IACdC,CADc,CACVC,CADU,CACJC,CACdA,EAAA,CAAW,EACNF,EAAA,CAAK,CAAV,KAAaC,CAAb,CAAoBX,CAAAa,OAApB,CAAiCH,CAAjC,CAAsCC,CAAtC,CAA4CD,CAAA,EAA5C,CACER,CACA,CADMF,CAAA,CAAKU,CAAL,CACN,CAAAE,CAAAE,KAAA,CAAcb,CAAAM,KAAA,CAAUL,CAAV,CAAd,CAEF,OAAOU,EAPW,CAAX,CAAA,EApCC,CAAX,CAAAG,KAAA,CA8CO,IA9CP",
86 | 'sources' => ["example.js"],
87 | 'names' => ["list","math","num","square","x","Math","sqrt","cube","elvis","alert","_i","_len","_results","length","push","call"]
88 | }
89 | map = Map.from_hash(hash)
90 |
91 | assert mapping = map[0]
92 | assert_equal 1, mapping.generated.line
93 | assert_equal 0, mapping.generated.column
94 | assert_equal 2, mapping.original.line
95 | assert_equal 1, mapping.original.column
96 | assert_equal 'example.js', mapping.source
97 | assert_equal nil, mapping.name
98 |
99 | assert mapping = map[-1]
100 | assert_equal 1, mapping.generated.line
101 | assert_equal 289, mapping.generated.column
102 | assert_equal 2, mapping.original.line
103 | assert_equal 1, mapping.original.column
104 | assert_equal 'example.js', mapping.source
105 | assert_equal nil, mapping.name
106 |
107 | assert_equal hash['sources'], map.sources
108 | assert_equal hash['names'], map.names
109 | assert_equal hash['mappings'], map.to_s
110 | end
111 |
112 | def test_to_s
113 | assert_equal "ACmBA;ACUA", @mappings.to_s
114 |
115 | empty_map = Map.new([])
116 | assert_equal "", empty_map.to_s
117 | end
118 |
119 | def test_sources
120 | assert_equal ["a.js", "b.js", "c.js"], @mappings.sources
121 | end
122 |
123 | def test_names
124 | assert_equal [], @mappings.names
125 | end
126 |
127 | def test_eql
128 | map1 = @mappings
129 | map2 = @mappings.dup
130 | map3 = Map.new([
131 | Mapping.new('a.js', Offset.new(0, 0), Offset.new(0, 0)),
132 | Mapping.new('b.js', Offset.new(1, 0), Offset.new(20, 0)),
133 | Mapping.new('c.js', Offset.new(2, 0), Offset.new(30, 0))
134 | ])
135 | map4 = Map.new
136 | map5 = Map.new([
137 | Mapping.new('a.js', Offset.new(0, 0), Offset.new(0, 0))
138 | ])
139 | map6 = Map.new([
140 | Mapping.new('a.js', Offset.new(0, 0), Offset.new(0, 0)),
141 | Mapping.new('b.js', Offset.new(1, 0), Offset.new(20, 0)),
142 | Mapping.new('z.js', Offset.new(2, 0), Offset.new(30, 0))
143 | ])
144 | map7 = Map.new([
145 | Mapping.new('a.js', Offset.new(0, 0), Offset.new(0, 0)),
146 | Mapping.new('b.js', Offset.new(1, 0), Offset.new(20, 0)),
147 | Mapping.new('c.js', Offset.new(2, 0), Offset.new(30, 0))
148 | ], 'bar.js')
149 |
150 | assert map1.eql?(map1)
151 | assert map1.eql?(map2)
152 | assert map1.eql?(map3)
153 |
154 | refute map1.eql?(true)
155 | refute map1.eql?(map4)
156 | refute map1.eql?(map5)
157 | refute map1.eql?(map6)
158 | refute map1.eql?(map7)
159 | end
160 |
161 | def test_add
162 | mappings2 = Map.new([
163 | Mapping.new('d.js', Offset.new(0, 0), Offset.new(0, 0))
164 | ])
165 | mappings3 = @mappings + mappings2
166 | assert_equal 0, mappings3[0].generated.line
167 | assert_equal 1, mappings3[1].generated.line
168 | assert_equal 2, mappings3[2].generated.line
169 | assert_equal 3, mappings3[3].generated.line
170 | end
171 |
172 | def test_add_identity
173 | identity_map = Map.new
174 |
175 | assert_equal @mappings, identity_map + @mappings
176 | assert_equal @mappings, @mappings + identity_map
177 | end
178 |
179 | def test_pipe
180 | mappings1 = Map.from_json(%{
181 | {
182 | "version": 3,
183 | "file": "index.js",
184 | "sourceRoot": "",
185 | "sources": [
186 | "index.coffee"
187 | ],
188 | "names": [],
189 | "mappings": ";AAAA;AAAA,MAAA,IAAA;;AAAA,EAAA,IAAA,GAAO,SAAA,GAAA;WACL,KAAA,CAAM,aAAN,EADK;EAAA,CAAP,CAAA;;AAGA,EAAA,IAAW,IAAX;AAAA,IAAG,IAAH,CAAA,CAAA,CAAA;GAHA;AAAA"
190 | }
191 | })
192 |
193 | mappings2 = Map.from_json(%{
194 | {
195 | "version":3,
196 | "file":"index.min.js",
197 | "sources":["index.js"],
198 | "names":["test","alert","call","this"],
199 | "mappings":"CACA,WACE,GAAIA,KAEJA,MAAO,WACL,MAAOC,OAAM,eAGf,IAAI,KAAM,CACRD,SAGDE,KAAKC"
200 | }
201 | })
202 |
203 | mappings3 = mappings1 | mappings2
204 | assert_equal 'CAAA,WAAA,GAAA,KAAA,MAAO,WAAA,MACL,OAAM,eAER,IAAW,KAAX,CAAG,SAHH,KAAA', mappings3.to_s
205 | end
206 |
207 | def test_pipe_identity
208 | identity_map = Map.new
209 |
210 | assert_equal @mappings, identity_map | @mappings
211 | end
212 |
213 | def test_bsearch
214 | assert_equal Offset.new(0, 0), @mappings.bsearch(Offset.new(0, 0)).original
215 | assert_equal Offset.new(0, 0), @mappings.bsearch(Offset.new(0, 5)).original
216 | assert_equal Offset.new(20, 0), @mappings.bsearch(Offset.new(1, 0)).original
217 | assert_equal Offset.new(20, 0), @mappings.bsearch(Offset.new(1, 0)).original
218 | assert_equal Offset.new(30, 0), @mappings.bsearch(Offset.new(2, 0)).original
219 | end
220 |
221 | def test_inspect
222 | assert_equal "#a.js@0:0\", \"1:0->b.js@20:0\", \"2:0->c.js@30:0\"]>", @mappings.inspect
223 | end
224 | end
225 |
--------------------------------------------------------------------------------