From 9753370bfb4cbdcab8e6ecd5551ffe8684e3e5b5 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Mon, 20 Mar 2017 17:22:55 +0200 Subject: [PATCH 01/16] Remove gopkg.in --- .travis.yml | 7 +------ README.md | 2 +- base64vlq/base64_vlq.go | 4 +--- base64vlq/base64_vlq_test.go | 2 +- bench_test.go | 2 +- consumer_test.go | 2 +- example_test.go | 2 +- sourcemap.go | 4 ++-- 8 files changed, 9 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 229abef..a89b967 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,15 +2,10 @@ sudo: false language: go go: - - 1.6 - 1.7 + - 1.8 - tip matrix: allow_failures: - go: tip - -install: - - mkdir -p $HOME/gopath/src/gopkg.in - - mv $HOME/gopath/src/github.com/go-sourcemap/sourcemap $HOME/gopath/src/gopkg.in/sourcemap.v1 - - cd $HOME/gopath/src/gopkg.in/sourcemap.v1 diff --git a/README.md b/README.md index fb319d2..4bd38c4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Install: - go get gopkg.in/sourcemap.v1 + go get github.com/go-sourcemap/sourcemap ## Quickstart diff --git a/base64vlq/base64_vlq.go b/base64vlq/base64_vlq.go index 16cbfb5..265d2b1 100644 --- a/base64vlq/base64_vlq.go +++ b/base64vlq/base64_vlq.go @@ -1,8 +1,6 @@ package base64vlq -import ( - "io" -) +import "io" const encodeStd = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" diff --git a/base64vlq/base64_vlq_test.go b/base64vlq/base64_vlq_test.go index 2aafd59..0ab6aae 100644 --- a/base64vlq/base64_vlq_test.go +++ b/base64vlq/base64_vlq_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "gopkg.in/sourcemap.v1/base64vlq" + "github.com/go-sourcemap/sourcemap/base64vlq" ) func TestEncodeDecode(t *testing.T) { diff --git a/bench_test.go b/bench_test.go index 69837dc..2557b93 100644 --- a/bench_test.go +++ b/bench_test.go @@ -3,7 +3,7 @@ package sourcemap_test import ( "testing" - "gopkg.in/sourcemap.v1" + "github.com/go-sourcemap/sourcemap" ) func BenchmarkParse(b *testing.B) { diff --git a/consumer_test.go b/consumer_test.go index 79287e1..769db7b 100644 --- a/consumer_test.go +++ b/consumer_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "gopkg.in/sourcemap.v1" + "github.com/go-sourcemap/sourcemap" ) const jqSourceMapURL = "http://code.jquery.com/jquery-2.0.3.min.map" diff --git a/example_test.go b/example_test.go index ffac414..341de43 100644 --- a/example_test.go +++ b/example_test.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "net/http" - "gopkg.in/sourcemap.v1" + "github.com/go-sourcemap/sourcemap" ) func ExampleParse() { diff --git a/sourcemap.go b/sourcemap.go index 0e9af1a..82d8016 100644 --- a/sourcemap.go +++ b/sourcemap.go @@ -1,10 +1,10 @@ -package sourcemap // import "gopkg.in/sourcemap.v1" +package sourcemap import ( "io" "strings" - "gopkg.in/sourcemap.v1/base64vlq" + "github.com/go-sourcemap/sourcemap/base64vlq" ) type fn func(m *mappings) (fn, error) From 56e7f7e3c74ef38ca49c174f8b2f403b3c3ca062 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Mon, 16 Oct 2017 14:49:07 +0300 Subject: [PATCH 02/16] Add support for indexed source maps --- .travis.yml | 1 + README.md | 6 +- consumer.go | 189 +++++++++++++++++++++++++----------- consumer_test.go | 58 ++++++++--- sourcemap.go => mappings.go | 46 ++++----- 5 files changed, 206 insertions(+), 94 deletions(-) rename sourcemap.go => mappings.go (74%) diff --git a/.travis.yml b/.travis.yml index a89b967..2e2c357 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ language: go go: - 1.7 - 1.8 + - 1.9 - tip matrix: diff --git a/README.md b/README.md index 4bd38c4..795f6aa 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -# Source Maps consumer for Golang [![Build Status](https://travis-ci.org/go-sourcemap/sourcemap.svg?branch=v1)](https://travis-ci.org/go-sourcemap/sourcemap) +# Source maps consumer for Golang [![Build Status](https://travis-ci.org/go-sourcemap/sourcemap.svg)](https://travis-ci.org/go-sourcemap/sourcemap) ## Installation Install: - go get github.com/go-sourcemap/sourcemap +```shell +go get github.com/go-sourcemap/sourcemap +``` ## Quickstart diff --git a/consumer.go b/consumer.go index 3bed06a..b807178 100644 --- a/consumer.go +++ b/consumer.go @@ -9,93 +9,175 @@ import ( "strconv" ) -type Consumer struct { +type v3 struct { + sourceMap + Sections []section `json:"sections"` +} + +type sourceMap struct { + Version int `json:"version"` + File string `json:"file"` + SourceRoot string `json:"sourceRoot"` + Sources []string `json:"sources"` + Names []interface{} `json:"names"` + Mappings string `json:"mappings"` + sourceRootURL *url.URL - smap *sourceMap mappings []mapping } -func Parse(mapURL string, b []byte) (*Consumer, error) { - smap := new(sourceMap) - err := json.Unmarshal(b, smap) - if err != nil { - return nil, err - } - - if smap.Version != 3 { - return nil, fmt.Errorf( - "sourcemap: got version=%d, but only 3rd version is supported", - smap.Version, - ) +func (m *sourceMap) parse(mapURL string) error { + if err := checkVersion(m.Version); err != nil { + return err } - var sourceRootURL *url.URL - if smap.SourceRoot != "" { - u, err := url.Parse(smap.SourceRoot) + if m.SourceRoot != "" { + u, err := url.Parse(m.SourceRoot) if err != nil { - return nil, err + return err } if u.IsAbs() { - sourceRootURL = u + m.sourceRootURL = u } } else if mapURL != "" { u, err := url.Parse(mapURL) if err != nil { - return nil, err + return err } if u.IsAbs() { u.Path = path.Dir(u.Path) - sourceRootURL = u + m.sourceRootURL = u } } - mappings, err := parseMappings(smap.Mappings) + mappings, err := parseMappings(m.Mappings) if err != nil { - return nil, err + return err } + + m.mappings = mappings // Free memory. - smap.Mappings = "" + m.Mappings = "" + + return nil +} + +func (m *sourceMap) absSource(source string) string { + if path.IsAbs(source) { + return source + } + + if u, err := url.Parse(source); err == nil && u.IsAbs() { + return source + } + + if m.sourceRootURL != nil { + u := *m.sourceRootURL + u.Path = path.Join(m.sourceRootURL.Path, source) + return u.String() + } + + if m.SourceRoot != "" { + return path.Join(m.SourceRoot, source) + } + + return source +} + +type section struct { + Offset struct { + Line int `json:"line"` + Column int `json:"column"` + } `json:"offset"` + Map *sourceMap `json:"map"` +} + +type Consumer struct { + file string + sections []section +} + +func Parse(mapURL string, b []byte) (*Consumer, error) { + v3 := new(v3) + err := json.Unmarshal(b, v3) + if err != nil { + return nil, err + } + + if err := checkVersion(v3.Version); err != nil { + return nil, err + } + + if len(v3.Sections) == 0 { + v3.Sections = append(v3.Sections, section{ + Map: &v3.sourceMap, + }) + } + + for _, s := range v3.Sections { + err := s.Map.parse(mapURL) + if err != nil { + return nil, err + } + } + reverse(v3.Sections) return &Consumer{ - sourceRootURL: sourceRootURL, - smap: smap, - mappings: mappings, + file: v3.File, + sections: v3.Sections, }, nil } func (c *Consumer) File() string { - return c.smap.File + return c.file } -func (c *Consumer) Source(genLine, genCol int) (source, name string, line, col int, ok bool) { - i := sort.Search(len(c.mappings), func(i int) bool { - m := &c.mappings[i] +func (c *Consumer) Source( + genLine, genColumn int, +) (source, name string, line, column int, ok bool) { + for i := range c.sections { + s := &c.sections[i] + if s.Offset.Line < genLine || + (s.Offset.Line+1 == genLine && s.Offset.Column <= genColumn) { + genLine -= s.Offset.Line + genColumn -= s.Offset.Column + return c.source(s.Map, genLine, genColumn) + } + } + return +} + +func (c *Consumer) source( + m *sourceMap, genLine, genColumn int, +) (source, name string, line, column int, ok bool) { + i := sort.Search(len(m.mappings), func(i int) bool { + m := &m.mappings[i] if m.genLine == genLine { - return m.genCol >= genCol + return m.genColumn >= genColumn } return m.genLine >= genLine }) // Mapping not found. - if i == len(c.mappings) { + if i == len(m.mappings) { return } - match := &c.mappings[i] + match := &m.mappings[i] // Fuzzy match. - if match.genLine > genLine || match.genCol > genCol { + if match.genLine > genLine || match.genColumn > genColumn { if i == 0 { return } - match = &c.mappings[i-1] + match = &m.mappings[i-1] } if match.sourcesInd >= 0 { - source = c.absSource(c.smap.Sources[match.sourcesInd]) + source = m.absSource(m.Sources[match.sourcesInd]) } if match.namesInd >= 0 { - v := c.smap.Names[match.namesInd] + v := m.Names[match.namesInd] switch v := v.(type) { case string: name = v @@ -106,29 +188,24 @@ func (c *Consumer) Source(genLine, genCol int) (source, name string, line, col i } } line = match.sourceLine - col = match.sourceCol + column = match.sourceColumn ok = true return } -func (c *Consumer) absSource(source string) string { - if path.IsAbs(source) { - return source - } - - if u, err := url.Parse(source); err == nil && u.IsAbs() { - return source - } - - if c.sourceRootURL != nil { - u := *c.sourceRootURL - u.Path = path.Join(c.sourceRootURL.Path, source) - return u.String() +func checkVersion(version int) error { + if version == 3 || version == 0 { + return nil } + return fmt.Errorf( + "sourcemap: got version=%d, but only 3rd version is supported", + version, + ) +} - if c.smap.SourceRoot != "" { - return path.Join(c.smap.SourceRoot, source) +func reverse(ss []section) { + last := len(ss) - 1 + for i := 0; i < len(ss)/2; i++ { + ss[i], ss[last-i] = ss[last-i], ss[i] } - - return source } diff --git a/consumer_test.go b/consumer_test.go index 769db7b..120fadf 100644 --- a/consumer_test.go +++ b/consumer_test.go @@ -29,24 +29,24 @@ func init() { type sourceMapTest struct { genLine int - genCol int + genColumn int wantedSource string wantedName string wantedLine int - wantedCol int + wantedColumn int } func (test *sourceMapTest) String() string { - return fmt.Sprintf("line=%d col=%d in file=%s", test.genLine, test.genCol, test.wantedSource) + return fmt.Sprintf("line=%d col=%d in file=%s", test.genLine, test.genColumn, test.wantedSource) } func (test *sourceMapTest) assert(t *testing.T, smap *sourcemap.Consumer) { - source, name, line, col, ok := smap.Source(test.genLine, test.genCol) + source, name, line, col, ok := smap.Source(test.genLine, test.genColumn) if !ok { if test.wantedSource == "" && test.wantedName == "" && test.wantedLine == 0 && - test.wantedCol == 0 { + test.wantedColumn == 0 { return } t.Fatalf("Source not found for %s", test) @@ -60,13 +60,21 @@ func (test *sourceMapTest) assert(t *testing.T, smap *sourcemap.Consumer) { if line != test.wantedLine { t.Fatalf("line: got %d, wanted %d (%s)", line, test.wantedLine, test) } - if col != test.wantedCol { - t.Fatalf("column: got %d, wanted %d (%s)", col, test.wantedCol, test) + if col != test.wantedColumn { + t.Fatalf("column: got %d, wanted %d (%s)", col, test.wantedColumn, test) } } func TestSourceMap(t *testing.T) { - smap, err := sourcemap.Parse("", []byte(sourceMapJSON)) + testSourceMap(t, sourceMapJSON) +} + +func TestIndexedSourceMap(t *testing.T) { + testSourceMap(t, indexedSourceMapJSON) +} + +func testSourceMap(t *testing.T, json string) { + smap, err := sourcemap.Parse("", []byte(json)) if err != nil { t.Fatal(err) } @@ -186,6 +194,8 @@ func TestJQuerySourceMap(t *testing.T) { } } +// https://github.com/mozilla/source-map/blob/master/test/util.js +// // This is a test mapping which maps functions from two different files // (one.js and two.js) to a minified generated source. // @@ -206,14 +216,40 @@ func TestJQuerySourceMap(t *testing.T) { // ONE.foo=function(a){return baz(a);}; // TWO.inc=function(a){return a+1;}; -var genCode = `exports.testGeneratedCode = "ONE.foo=function(a){return baz(a);}; +const genCode = `exports.testGeneratedCode = "ONE.foo=function(a){return baz(a);}; TWO.inc=function(a){return a+1;};` -var sourceMapJSON = `{ +const sourceMapJSON = `{ "version": 3, "file": "min.js", - "names": ["bar", "baz", "n"], "sources": ["one.js", "two.js"], "sourceRoot": "/the/root", + "names": ["bar", "baz", "n"], "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA" }` + +const indexedSourceMapJSON = `{ + "version": 3, + "file": "min.js", + "sections": [{ + "offset": {"line": 0, "column": 0}, + "map": { + "version": 3, + "file": "min.js", + "sources": ["one.js"], + "sourceRoot": "/the/root", + "names": ["bar", "baz"], + "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID" + } + }, { + "offset": {"line": 1, "column": 0}, + "map": { + "version": 3, + "file": "min.js", + "sources": ["two.js"], + "sourceRoot": "/the/root", + "names": ["n"], + "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA" + } + }] +}` diff --git a/sourcemap.go b/mappings.go similarity index 74% rename from sourcemap.go rename to mappings.go index 82d8016..58e1aeb 100644 --- a/sourcemap.go +++ b/mappings.go @@ -1,6 +1,7 @@ package sourcemap import ( + "errors" "io" "strings" @@ -9,22 +10,13 @@ import ( type fn func(m *mappings) (fn, error) -type sourceMap struct { - Version int `json:"version"` - File string `json:"file"` - SourceRoot string `json:"sourceRoot"` - Sources []string `json:"sources"` - Names []interface{} `json:"names"` - Mappings string `json:"mappings"` -} - type mapping struct { - genLine int - genCol int - sourcesInd int - sourceLine int - sourceCol int - namesInd int + genLine int + genColumn int + sourcesInd int + sourceLine int + sourceColumn int + namesInd int } type mappings struct { @@ -38,6 +30,10 @@ type mappings struct { } func parseMappings(s string) ([]mapping, error) { + if s == "" { + return nil, errors.New("sourcemap: mappings are empty") + } + rd := strings.NewReader(s) m := &mappings{ rd: rd, @@ -73,7 +69,7 @@ func (m *mappings) parse() error { m.pushValue() m.value.genLine++ - m.value.genCol = 0 + m.value.genColumn = 0 next = parseGenCol default: @@ -95,7 +91,7 @@ func parseGenCol(m *mappings) (fn, error) { if err != nil { return nil, err } - m.value.genCol += n + m.value.genColumn += n return parseSourcesInd, nil } @@ -122,7 +118,7 @@ func parseSourceCol(m *mappings) (fn, error) { if err != nil { return nil, err } - m.value.sourceCol += n + m.value.sourceColumn += n return parseNamesInd, nil } @@ -137,7 +133,7 @@ func parseNamesInd(m *mappings) (fn, error) { } func (m *mappings) pushValue() { - if m.value.sourceLine == 1 && m.value.sourceCol == 0 { + if m.value.sourceLine == 1 && m.value.sourceColumn == 0 { return } @@ -146,12 +142,12 @@ func (m *mappings) pushValue() { m.hasName = false } else { m.values = append(m.values, mapping{ - genLine: m.value.genLine, - genCol: m.value.genCol, - sourcesInd: m.value.sourcesInd, - sourceLine: m.value.sourceLine, - sourceCol: m.value.sourceCol, - namesInd: -1, + genLine: m.value.genLine, + genColumn: m.value.genColumn, + sourcesInd: m.value.sourcesInd, + sourceLine: m.value.sourceLine, + sourceColumn: m.value.sourceColumn, + namesInd: -1, }) } } From 72d557b5e65dfbb03f02f23cc0b5bd4dadc33d9c Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Mon, 18 Dec 2017 11:51:18 +0200 Subject: [PATCH 03/16] Add SourceContent --- README.md | 10 +++++++-- consumer.go | 57 ++++++++++++++++++++++++++++++++++-------------- consumer_test.go | 37 ++++++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 795f6aa..ba9b3b8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,17 @@ -# Source maps consumer for Golang [![Build Status](https://travis-ci.org/go-sourcemap/sourcemap.svg)](https://travis-ci.org/go-sourcemap/sourcemap) +# Source maps consumer for Golang + +[![Build Status](https://travis-ci.org/go-sourcemap/sourcemap.svg)](https://travis-ci.org/go-sourcemap/sourcemap) + +API docs: https://godoc.org/github.com/go-sourcemap/sourcemap. +Examples: https://godoc.org/github.com/go-sourcemap/sourcemap#pkg-examples. +Spec: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit. ## Installation Install: ```shell -go get github.com/go-sourcemap/sourcemap +go get -u github.com/go-sourcemap/sourcemap ``` ## Quickstart diff --git a/consumer.go b/consumer.go index b807178..a501796 100644 --- a/consumer.go +++ b/consumer.go @@ -15,15 +15,15 @@ type v3 struct { } type sourceMap struct { - Version int `json:"version"` - File string `json:"file"` - SourceRoot string `json:"sourceRoot"` - Sources []string `json:"sources"` - Names []interface{} `json:"names"` - Mappings string `json:"mappings"` - - sourceRootURL *url.URL - mappings []mapping + Version int `json:"version"` + File string `json:"file"` + SourceRoot string `json:"sourceRoot"` + Sources []string `json:"sources"` + SourcesContent []string `json:"sourcesContent"` + Names []interface{} `json:"names"` + Mappings string `json:"mappings"` + + mappings []mapping } func (m *sourceMap) parse(mapURL string) error { @@ -31,13 +31,14 @@ func (m *sourceMap) parse(mapURL string) error { return err } + var sourceRootURL *url.URL if m.SourceRoot != "" { u, err := url.Parse(m.SourceRoot) if err != nil { return err } if u.IsAbs() { - m.sourceRootURL = u + sourceRootURL = u } } else if mapURL != "" { u, err := url.Parse(mapURL) @@ -46,10 +47,14 @@ func (m *sourceMap) parse(mapURL string) error { } if u.IsAbs() { u.Path = path.Dir(u.Path) - m.sourceRootURL = u + sourceRootURL = u } } + for i, src := range m.Sources { + m.Sources[i] = m.absSource(sourceRootURL, src) + } + mappings, err := parseMappings(m.Mappings) if err != nil { return err @@ -62,7 +67,7 @@ func (m *sourceMap) parse(mapURL string) error { return nil } -func (m *sourceMap) absSource(source string) string { +func (m *sourceMap) absSource(root *url.URL, source string) string { if path.IsAbs(source) { return source } @@ -71,9 +76,9 @@ func (m *sourceMap) absSource(source string) string { return source } - if m.sourceRootURL != nil { - u := *m.sourceRootURL - u.Path = path.Join(m.sourceRootURL.Path, source) + if root != nil { + u := *root + u.Path = path.Join(u.Path, source) return u.String() } @@ -128,10 +133,14 @@ func Parse(mapURL string, b []byte) (*Consumer, error) { }, nil } +// File returns an optional name of the generated code +// that this source map is associated with. func (c *Consumer) File() string { return c.file } +// Source returns the original source, name, line, and column information +// for the generated source's line and column positions. func (c *Consumer) Source( genLine, genColumn int, ) (source, name string, line, column int, ok bool) { @@ -174,7 +183,7 @@ func (c *Consumer) source( } if match.sourcesInd >= 0 { - source = m.absSource(m.Sources[match.sourcesInd]) + source = m.Sources[match.sourcesInd] } if match.namesInd >= 0 { v := m.Names[match.namesInd] @@ -193,6 +202,22 @@ func (c *Consumer) source( return } +// SourceContent returns the original source content for the source. +func (c *Consumer) SourceContent(source string) string { + for i := range c.sections { + s := &c.sections[i] + for i, src := range s.Map.Sources { + if src == source { + if i < len(s.Map.SourcesContent) { + return s.Map.SourcesContent[i] + } + break + } + } + } + return "" +} + func checkVersion(version int) error { if version == 3 || version == 0 { return nil diff --git a/consumer_test.go b/consumer_test.go index 120fadf..da69f53 100644 --- a/consumer_test.go +++ b/consumer_test.go @@ -1,6 +1,7 @@ package sourcemap_test import ( + "encoding/json" "fmt" "io/ioutil" "net/http" @@ -79,7 +80,7 @@ func testSourceMap(t *testing.T, json string) { t.Fatal(err) } - tests := []*sourceMapTest{ + tests := []sourceMapTest{ {1, 1, "/the/root/one.js", "", 1, 1}, {1, 5, "/the/root/one.js", "", 1, 5}, {1, 9, "/the/root/one.js", "", 1, 11}, @@ -100,8 +101,18 @@ func testSourceMap(t *testing.T, json string) { {1, 30, "/the/root/one.js", "baz", 2, 10}, {2, 12, "/the/root/two.js", "", 1, 11}, } - for _, test := range tests { - test.assert(t, smap) + for i := range tests { + tests[i].assert(t, smap) + } + + content := smap.SourceContent("/the/root/one.js") + if content != oneSourceContent { + t.Fatalf("%q != %q", content, oneSourceContent) + } + + content = smap.SourceContent("/the/root/two.js") + if content != twoSourceContent { + t.Fatalf("%q != %q", content, twoSourceContent) } _, _, _, _, ok := smap.Source(3, 0) @@ -219,16 +230,30 @@ func TestJQuerySourceMap(t *testing.T) { const genCode = `exports.testGeneratedCode = "ONE.foo=function(a){return baz(a);}; TWO.inc=function(a){return a+1;};` -const sourceMapJSON = `{ +var oneSourceContent = `ONE.foo = function (bar) { + return baz(bar); +};` + +var twoSourceContent = `TWO.inc = function (n) { + return n + 1; +};` + +var sourceMapJSON = `{ "version": 3, "file": "min.js", "sources": ["one.js", "two.js"], + "sourcesContent": ` + j([]string{oneSourceContent, twoSourceContent}) + `, "sourceRoot": "/the/root", "names": ["bar", "baz", "n"], "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA" }` -const indexedSourceMapJSON = `{ +func j(v interface{}) string { + b, _ := json.Marshal(v) + return string(b) +} + +var indexedSourceMapJSON = `{ "version": 3, "file": "min.js", "sections": [{ @@ -237,6 +262,7 @@ const indexedSourceMapJSON = `{ "version": 3, "file": "min.js", "sources": ["one.js"], + "sourcesContent": ` + j([]string{oneSourceContent}) + `, "sourceRoot": "/the/root", "names": ["bar", "baz"], "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID" @@ -247,6 +273,7 @@ const indexedSourceMapJSON = `{ "version": 3, "file": "min.js", "sources": ["two.js"], + "sourcesContent": ` + j([]string{twoSourceContent}) + `, "sourceRoot": "/the/root", "names": ["n"], "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA" From 561004d41b5d21661206098c0f3b9c8182dacc6a Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Mon, 18 Dec 2017 11:55:08 +0200 Subject: [PATCH 04/16] travis: test on latest Go --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2e2c357..f1baa9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ sudo: false language: go go: - - 1.7 - - 1.8 - - 1.9 + - 1.7.x + - 1.8.x + - 1.9.x - tip matrix: From 8947752aef2979d0842c99d4fdc4855f28b5ee98 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Thu, 21 Dec 2017 11:52:25 +0200 Subject: [PATCH 05/16] Reduce mapping value size --- consumer.go | 22 +++++++++---------- .../base64vlq/base64vlq.go | 16 +++++++------- .../base64vlq/base64vlq_test.go | 6 ++--- mappings.go | 21 ++++++++++-------- 4 files changed, 34 insertions(+), 31 deletions(-) rename base64vlq/base64_vlq.go => internal/base64vlq/base64vlq.go (77%) rename base64vlq/base64_vlq_test.go => internal/base64vlq/base64vlq_test.go (86%) diff --git a/consumer.go b/consumer.go index a501796..c70ff86 100644 --- a/consumer.go +++ b/consumer.go @@ -9,11 +9,6 @@ import ( "strconv" ) -type v3 struct { - sourceMap - Sections []section `json:"sections"` -} - type sourceMap struct { Version int `json:"version"` File string `json:"file"` @@ -26,6 +21,11 @@ type sourceMap struct { mappings []mapping } +type v3 struct { + sourceMap + Sections []section `json:"sections"` +} + func (m *sourceMap) parse(mapURL string) error { if err := checkVersion(m.Version); err != nil { return err @@ -161,10 +161,10 @@ func (c *Consumer) source( ) (source, name string, line, column int, ok bool) { i := sort.Search(len(m.mappings), func(i int) bool { m := &m.mappings[i] - if m.genLine == genLine { - return m.genColumn >= genColumn + if int(m.genLine) == genLine { + return int(m.genColumn) >= genColumn } - return m.genLine >= genLine + return int(m.genLine) >= genLine }) // Mapping not found. @@ -175,7 +175,7 @@ func (c *Consumer) source( match := &m.mappings[i] // Fuzzy match. - if match.genLine > genLine || match.genColumn > genColumn { + if int(match.genLine) > genLine || int(match.genColumn) > genColumn { if i == 0 { return } @@ -196,8 +196,8 @@ func (c *Consumer) source( name = fmt.Sprint(v) } } - line = match.sourceLine - column = match.sourceColumn + line = int(match.sourceLine) + column = int(match.sourceColumn) ok = true return } diff --git a/base64vlq/base64_vlq.go b/internal/base64vlq/base64vlq.go similarity index 77% rename from base64vlq/base64_vlq.go rename to internal/base64vlq/base64vlq.go index 265d2b1..4804f5a 100644 --- a/base64vlq/base64_vlq.go +++ b/internal/base64vlq/base64vlq.go @@ -20,14 +20,14 @@ func init() { } } -func toVLQSigned(n int) int { +func toVLQSigned(n int32) int32 { if n < 0 { return -n<<1 + 1 } return n << 1 } -func fromVLQSigned(n int) int { +func fromVLQSigned(n int32) int32 { isNeg := n&vlqSignBit != 0 n >>= 1 if isNeg { @@ -46,9 +46,9 @@ func NewEncoder(w io.ByteWriter) *Encoder { } } -func (enc Encoder) Encode(n int) error { +func (enc Encoder) Encode(n int32) error { n = toVLQSigned(n) - for digit := vlqContinuationBit; digit&vlqContinuationBit != 0; { + for digit := int32(vlqContinuationBit); digit&vlqContinuationBit != 0; { digit = n & vlqBaseMask n >>= vlqBaseShift if n > 0 { @@ -67,13 +67,13 @@ type Decoder struct { r io.ByteReader } -func NewDecoder(r io.ByteReader) *Decoder { - return &Decoder{ +func NewDecoder(r io.ByteReader) Decoder { + return Decoder{ r: r, } } -func (dec Decoder) Decode() (n int, err error) { +func (dec Decoder) Decode() (n int32, err error) { shift := uint(0) for continuation := true; continuation; { c, err := dec.r.ReadByte() @@ -83,7 +83,7 @@ func (dec Decoder) Decode() (n int, err error) { c = decodeMap[c] continuation = c&vlqContinuationBit != 0 - n += int(c&vlqBaseMask) << shift + n += int32(c&vlqBaseMask) << shift shift += vlqBaseShift } return fromVLQSigned(n), nil diff --git a/base64vlq/base64_vlq_test.go b/internal/base64vlq/base64vlq_test.go similarity index 86% rename from base64vlq/base64_vlq_test.go rename to internal/base64vlq/base64vlq_test.go index 0ab6aae..24cd889 100644 --- a/base64vlq/base64_vlq_test.go +++ b/internal/base64vlq/base64vlq_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/go-sourcemap/sourcemap/base64vlq" + "github.com/go-sourcemap/sourcemap/internal/base64vlq" ) func TestEncodeDecode(t *testing.T) { @@ -12,13 +12,13 @@ func TestEncodeDecode(t *testing.T) { enc := base64vlq.NewEncoder(buf) dec := base64vlq.NewDecoder(buf) - for n := -1000; n < 1000; n++ { + for n := int32(-1000); n < 1000; n++ { if err := enc.Encode(n); err != nil { panic(err) } } - for n := -1000; n < 1000; n++ { + for n := int32(-1000); n < 1000; n++ { nn, err := dec.Decode() if err != nil { panic(err) diff --git a/mappings.go b/mappings.go index 58e1aeb..2237cdf 100644 --- a/mappings.go +++ b/mappings.go @@ -5,23 +5,23 @@ import ( "io" "strings" - "github.com/go-sourcemap/sourcemap/base64vlq" + "github.com/go-sourcemap/sourcemap/internal/base64vlq" ) type fn func(m *mappings) (fn, error) type mapping struct { - genLine int - genColumn int - sourcesInd int - sourceLine int - sourceColumn int - namesInd int + genLine int32 + genColumn int32 + sourcesInd int32 + sourceLine int32 + sourceColumn int32 + namesInd int32 } type mappings struct { rd *strings.Reader - dec *base64vlq.Decoder + dec base64vlq.Decoder hasName bool value mapping @@ -46,7 +46,10 @@ func parseMappings(s string) ([]mapping, error) { if err != nil { return nil, err } - return m.values, nil + + values := m.values + m.values = nil + return values, nil } func (m *mappings) parse() error { From d46c44bf66025f17ec9121d5bf61377f7ed22809 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Thu, 21 Dec 2017 12:15:24 +0200 Subject: [PATCH 06/16] Pre-allocate mappings --- mappings.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mappings.go b/mappings.go index 2237cdf..f200ce3 100644 --- a/mappings.go +++ b/mappings.go @@ -38,6 +38,8 @@ func parseMappings(s string) ([]mapping, error) { m := &mappings{ rd: rd, dec: base64vlq.NewDecoder(rd), + + values: make([]mapping, 0, mappingsNumber(s)), } m.value.genLine = 1 m.value.sourceLine = 1 @@ -52,6 +54,10 @@ func parseMappings(s string) ([]mapping, error) { return values, nil } +func mappingsNumber(s string) int { + return strings.Count(s, ",") + strings.Count(s, ";") +} + func (m *mappings) parse() error { next := parseGenCol for { From c5d1834c8e8eac74e7670e3c203458707a3b0f42 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Thu, 21 Dec 2017 12:18:57 +0200 Subject: [PATCH 07/16] Reduce number of allocations --- consumer.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/consumer.go b/consumer.go index c70ff86..e700276 100644 --- a/consumer.go +++ b/consumer.go @@ -6,7 +6,6 @@ import ( "net/url" "path" "sort" - "strconv" ) type sourceMap struct { @@ -15,7 +14,7 @@ type sourceMap struct { SourceRoot string `json:"sourceRoot"` Sources []string `json:"sources"` SourcesContent []string `json:"sourcesContent"` - Names []interface{} `json:"names"` + Names []json.Number `json:"names"` Mappings string `json:"mappings"` mappings []mapping @@ -186,15 +185,7 @@ func (c *Consumer) source( source = m.Sources[match.sourcesInd] } if match.namesInd >= 0 { - v := m.Names[match.namesInd] - switch v := v.(type) { - case string: - name = v - case float64: - name = strconv.FormatFloat(v, 'f', -1, 64) - default: - name = fmt.Sprint(v) - } + name = string(m.Names[match.namesInd]) } line = int(match.sourceLine) column = int(match.sourceColumn) From b019cc30c1eaa584753491b0d8f8c1534bf1eb44 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Fri, 19 Jan 2018 13:52:46 +0200 Subject: [PATCH 08/16] Add Consumer.SourcemapURL --- consumer.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/consumer.go b/consumer.go index e700276..2a65085 100644 --- a/consumer.go +++ b/consumer.go @@ -25,7 +25,7 @@ type v3 struct { Sections []section `json:"sections"` } -func (m *sourceMap) parse(mapURL string) error { +func (m *sourceMap) parse(sourcemapURL string) error { if err := checkVersion(m.Version); err != nil { return err } @@ -39,8 +39,8 @@ func (m *sourceMap) parse(mapURL string) error { if u.IsAbs() { sourceRootURL = u } - } else if mapURL != "" { - u, err := url.Parse(mapURL) + } else if sourcemapURL != "" { + u, err := url.Parse(sourcemapURL) if err != nil { return err } @@ -97,11 +97,12 @@ type section struct { } type Consumer struct { - file string - sections []section + sourcemapURL string + file string + sections []section } -func Parse(mapURL string, b []byte) (*Consumer, error) { +func Parse(sourcemapURL string, b []byte) (*Consumer, error) { v3 := new(v3) err := json.Unmarshal(b, v3) if err != nil { @@ -119,7 +120,7 @@ func Parse(mapURL string, b []byte) (*Consumer, error) { } for _, s := range v3.Sections { - err := s.Map.parse(mapURL) + err := s.Map.parse(sourcemapURL) if err != nil { return nil, err } @@ -127,11 +128,16 @@ func Parse(mapURL string, b []byte) (*Consumer, error) { reverse(v3.Sections) return &Consumer{ - file: v3.File, - sections: v3.Sections, + sourcemapURL: sourcemapURL, + file: v3.File, + sections: v3.Sections, }, nil } +func (c *Consumer) SourcemapURL() string { + return c.sourcemapURL +} + // File returns an optional name of the generated code // that this source map is associated with. func (c *Consumer) File() string { From 8cb281d24ad99c1a78193d7aa7c4f302a8d7c828 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Wed, 11 Mar 2020 09:35:56 +0200 Subject: [PATCH 09/16] Use json.RawMessage instead of json.Number --- .travis.yml | 6 +++--- consumer.go | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index f1baa9c..271fe93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ sudo: false language: go go: - - 1.7.x - - 1.8.x - - 1.9.x + - 1.12.x + - 1.13.x + - 1.14.x - tip matrix: diff --git a/consumer.go b/consumer.go index 2a65085..3b2f6e3 100644 --- a/consumer.go +++ b/consumer.go @@ -9,13 +9,13 @@ import ( ) type sourceMap struct { - Version int `json:"version"` - File string `json:"file"` - SourceRoot string `json:"sourceRoot"` - Sources []string `json:"sources"` - SourcesContent []string `json:"sourcesContent"` - Names []json.Number `json:"names"` - Mappings string `json:"mappings"` + Version int `json:"version"` + File string `json:"file"` + SourceRoot string `json:"sourceRoot"` + Sources []string `json:"sources"` + SourcesContent []string `json:"sourcesContent"` + Names []json.RawMessage `json:"names,string"` + Mappings string `json:"mappings"` mappings []mapping } @@ -88,6 +88,26 @@ func (m *sourceMap) absSource(root *url.URL, source string) string { return source } +func (m *sourceMap) name(idx int) string { + if idx >= len(m.Names) { + return "" + } + + raw := m.Names[idx] + if len(raw) == 0 { + return "" + } + + if raw[0] == '"' && raw[len(raw)-1] == '"' { + var str string + if err := json.Unmarshal(raw, &str); err == nil { + return str + } + } + + return string(raw) +} + type section struct { Offset struct { Line int `json:"line"` @@ -191,7 +211,7 @@ func (c *Consumer) source( source = m.Sources[match.sourcesInd] } if match.namesInd >= 0 { - name = string(m.Names[match.namesInd]) + name = m.name(int(match.namesInd)) } line = int(match.sourceLine) column = int(match.sourceColumn) From eed1c205cd5e6a344123e253f59c02395ae80864 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Wed, 28 Oct 2020 17:23:41 +0200 Subject: [PATCH 10/16] Add pkg.go.dev --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ba9b3b8..16ce735 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # Source maps consumer for Golang [![Build Status](https://travis-ci.org/go-sourcemap/sourcemap.svg)](https://travis-ci.org/go-sourcemap/sourcemap) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/go-sourcemap/sourcemap)](https://pkg.go.dev/github.com/go-sourcemap/sourcemap) -API docs: https://godoc.org/github.com/go-sourcemap/sourcemap. -Examples: https://godoc.org/github.com/go-sourcemap/sourcemap#pkg-examples. -Spec: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit. +> :heart: [**Uptrace.dev** - distributed traces, logs, and errors in one place](https://uptrace.dev) ## Installation From 2c2989c0403e3d012799c754c6a63d691803310d Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 19 Nov 2021 10:58:18 +0200 Subject: [PATCH 11/16] Better detection that a value should be pushed --- mappings.go | 10 ++++++---- mappings_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 mappings_test.go diff --git a/mappings.go b/mappings.go index f200ce3..eb4b04b 100644 --- a/mappings.go +++ b/mappings.go @@ -23,8 +23,9 @@ type mappings struct { rd *strings.Reader dec base64vlq.Decoder - hasName bool - value mapping + hasValue bool + hasName bool + value mapping values []mapping } @@ -91,6 +92,7 @@ func (m *mappings) parse() error { if err != nil { return err } + m.hasValue = true } } } @@ -142,10 +144,10 @@ func parseNamesInd(m *mappings) (fn, error) { } func (m *mappings) pushValue() { - if m.value.sourceLine == 1 && m.value.sourceColumn == 0 { + if !m.hasValue { return } - + m.hasValue = false if m.hasName { m.values = append(m.values, m.value) m.hasName = false diff --git a/mappings_test.go b/mappings_test.go new file mode 100644 index 0000000..f35cb61 --- /dev/null +++ b/mappings_test.go @@ -0,0 +1,32 @@ +package sourcemap + +import ( + "reflect" + "testing" +) + +func TestParseMappings(t *testing.T) { + t.Parallel() + cases := map[string][]mapping{ + ";;;;;;kBAEe,YAAY,CAC1B,C;;AAHD": { + {genLine: 7, genColumn: 18, sourceLine: 3, sourceColumn: 15, namesInd: -1}, + {genLine: 7, genColumn: 30, sourceLine: 3, sourceColumn: 27, namesInd: -1}, + {genLine: 7, genColumn: 31, sourceLine: 4, sourceColumn: 1, namesInd: -1}, + {genLine: 7, genColumn: 32, sourceLine: 4, sourceColumn: 1, namesInd: -1}, + {genLine: 9, genColumn: 0, sourceLine: 1, sourceColumn: 0, namesInd: -1}, + }, + } + for k, c := range cases { + k, c := k, c + t.Run(k, func(t *testing.T) { + t.Parallel() + v, err := parseMappings(k) + if err != nil { + t.Fatalf("got error %s", err) + } + if !reflect.DeepEqual(v, c) { + t.Fatalf("expected %v got %v", c, v) + } + }) + } +} From 73a0ee2ad6086891e4fcf116763e2a239693cfea Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 19 Nov 2021 12:37:07 +0200 Subject: [PATCH 12/16] Get last mapping if line matches but column is way bigger --- consumer.go | 25 +++++++++++++++---------- consumer_test.go | 3 +++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/consumer.go b/consumer.go index 3b2f6e3..935a4a9 100644 --- a/consumer.go +++ b/consumer.go @@ -192,19 +192,24 @@ func (c *Consumer) source( return int(m.genLine) >= genLine }) - // Mapping not found. + var match *mapping + // Mapping not found if i == len(m.mappings) { - return - } - - match := &m.mappings[i] - - // Fuzzy match. - if int(match.genLine) > genLine || int(match.genColumn) > genColumn { - if i == 0 { + // lets see if the line is correct but the column is bigger + match = &m.mappings[i-1] + if int(match.genLine) != genLine { return } - match = &m.mappings[i-1] + } else { + match = &m.mappings[i] + + // Fuzzy match. + if int(match.genLine) > genLine || int(match.genColumn) > genColumn { + if i == 0 { + return + } + match = &m.mappings[i-1] + } } if match.sourcesInd >= 0 { diff --git a/consumer_test.go b/consumer_test.go index da69f53..ea745ac 100644 --- a/consumer_test.go +++ b/consumer_test.go @@ -96,6 +96,9 @@ func testSourceMap(t *testing.T, json string) { {2, 21, "/the/root/two.js", "", 2, 3}, {2, 28, "/the/root/two.js", "n", 2, 10}, + // line correct, column bigger than last mapping + {2, 29, "/the/root/two.js", "n", 2, 10}, + // Fuzzy match. {1, 20, "/the/root/one.js", "bar", 1, 21}, {1, 30, "/the/root/one.js", "baz", 2, 10}, From 3123fd1dc3d8ae185bc4ddb51535d9fb106afd40 Mon Sep 17 00:00:00 2001 From: codebien <2103732+codebien@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:00:19 +0100 Subject: [PATCH 13/16] Exclude empty mappings --- consumer.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/consumer.go b/consumer.go index 935a4a9..8a22f36 100644 --- a/consumer.go +++ b/consumer.go @@ -195,6 +195,10 @@ func (c *Consumer) source( var match *mapping // Mapping not found if i == len(m.mappings) { + // Empty mappings + if len(m.mappings) == 0 { + return + } // lets see if the line is correct but the column is bigger match = &m.mappings[i-1] if int(match.genLine) != genLine { From 1c272b942585531ba7a573e1441c3772a14b59ab Mon Sep 17 00:00:00 2001 From: codebien <2103732+codebien@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:57:49 +0100 Subject: [PATCH 14/16] Added a basic test --- consumer_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/consumer_test.go b/consumer_test.go index ea745ac..7fc84f3 100644 --- a/consumer_test.go +++ b/consumer_test.go @@ -66,6 +66,23 @@ func (test *sourceMapTest) assert(t *testing.T, smap *sourcemap.Consumer) { } } +func TestSourceWithEmptySourceMap(t *testing.T) { + var jsmap = `{ + "version": 3, + "mappings": ";;" +}` + + smap, err := sourcemap.Parse("noname", []byte(jsmap)) + if err != nil { + t.Fatal(err) + } + + _, _, _, _, matched := smap.Source(1, 1) + if matched { + t.Error("it is unexpected to match an empty SourceMap") + } +} + func TestSourceMap(t *testing.T) { testSourceMap(t, sourceMapJSON) } From 5e8d581e9792adacaa453bc865ddc240e16722c2 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Wed, 13 Mar 2024 09:20:32 +0200 Subject: [PATCH 15/16] chore: cleanup --- .travis.yml | 12 ------------ README.md | 6 ++++-- consumer.go | 8 ++++---- 3 files changed, 8 insertions(+), 18 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 271fe93..0000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -sudo: false -language: go - -go: - - 1.12.x - - 1.13.x - - 1.14.x - - tip - -matrix: - allow_failures: - - go: tip diff --git a/README.md b/README.md index 16ce735..bc6b624 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # Source maps consumer for Golang -[![Build Status](https://travis-ci.org/go-sourcemap/sourcemap.svg)](https://travis-ci.org/go-sourcemap/sourcemap) [![PkgGoDev](https://pkg.go.dev/badge/github.com/go-sourcemap/sourcemap)](https://pkg.go.dev/github.com/go-sourcemap/sourcemap) -> :heart: [**Uptrace.dev** - distributed traces, logs, and errors in one place](https://uptrace.dev) +> This package is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace). +> Uptrace is an open-source APM tool that supports distributed tracing, metrics, and logs. You can +> use it to monitor applications and set up automatic alerts to receive notifications via email, +> Slack, Telegram, and others. ## Installation diff --git a/consumer.go b/consumer.go index 8a22f36..cedf396 100644 --- a/consumer.go +++ b/consumer.go @@ -184,6 +184,10 @@ func (c *Consumer) Source( func (c *Consumer) source( m *sourceMap, genLine, genColumn int, ) (source, name string, line, column int, ok bool) { + if len(m.mappings) == 0 { + return + } + i := sort.Search(len(m.mappings), func(i int) bool { m := &m.mappings[i] if int(m.genLine) == genLine { @@ -195,10 +199,6 @@ func (c *Consumer) source( var match *mapping // Mapping not found if i == len(m.mappings) { - // Empty mappings - if len(m.mappings) == 0 { - return - } // lets see if the line is correct but the column is bigger match = &m.mappings[i-1] if int(match.genLine) != genLine { From e217b057beef278cb00dbcf1699961df45190372 Mon Sep 17 00:00:00 2001 From: Pedro Tanaka Date: Thu, 18 Sep 2025 13:33:33 +0200 Subject: [PATCH 16/16] feat: Add json/v2 support for faster sourcemap parsing Add experimental json/v2 decoder support via build tags for improved performance while maintaining full backward compatibility. - Add unmarshalJSON abstraction in consumer.go - Create json_decoder.go for standard encoding/json (default) - Create json_decoder_v2.go with jsonv2 build tag for Go 1.25+ - Bonus: Minor optimization using strings.Builder for URL concatenation Users can opt-in to json/v2 with: GOEXPERIMENT=jsonv2 go build -tags=jsonv2 ./... This provides ~59% faster parsing with zero API changes. --- consumer.go | 4 ++-- json_decoder.go | 12 ++++++++++++ json_decoder_v2.go | 13 +++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 json_decoder.go create mode 100644 json_decoder_v2.go diff --git a/consumer.go b/consumer.go index cedf396..2c269f4 100644 --- a/consumer.go +++ b/consumer.go @@ -100,7 +100,7 @@ func (m *sourceMap) name(idx int) string { if raw[0] == '"' && raw[len(raw)-1] == '"' { var str string - if err := json.Unmarshal(raw, &str); err == nil { + if err := unmarshalJSON(raw, &str); err == nil { return str } } @@ -124,7 +124,7 @@ type Consumer struct { func Parse(sourcemapURL string, b []byte) (*Consumer, error) { v3 := new(v3) - err := json.Unmarshal(b, v3) + err := unmarshalJSON(b, v3) if err != nil { return nil, err } diff --git a/json_decoder.go b/json_decoder.go new file mode 100644 index 0000000..7d4c4b5 --- /dev/null +++ b/json_decoder.go @@ -0,0 +1,12 @@ +//go:build !jsonv2 +// +build !jsonv2 + +package sourcemap + +import "encoding/json" + +// unmarshalJSON is the JSON unmarshaling function +// This version uses the standard encoding/json package +func unmarshalJSON(data []byte, v interface{}) error { + return json.Unmarshal(data, v) +} \ No newline at end of file diff --git a/json_decoder_v2.go b/json_decoder_v2.go new file mode 100644 index 0000000..37a2760 --- /dev/null +++ b/json_decoder_v2.go @@ -0,0 +1,13 @@ +//go:build jsonv2 +// +build jsonv2 + +package sourcemap + +import "encoding/json/v2" + +// unmarshalJSON is the JSON unmarshaling function +// This version uses the experimental json/v2 package for better performance +// Build with: GOEXPERIMENT=jsonv2 go build -tags=jsonv2 ./... +func unmarshalJSON(data []byte, v interface{}) error { + return json.Unmarshal(data, v) +} \ No newline at end of file