234 lines
4.5 KiB
Go
234 lines
4.5 KiB
Go
package sourcemap
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"path"
|
|
"sort"
|
|
)
|
|
|
|
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"`
|
|
|
|
mappings []mapping
|
|
}
|
|
|
|
type v3 struct {
|
|
sourceMap
|
|
Sections []section `json:"sections"`
|
|
}
|
|
|
|
func (m *sourceMap) parse(sourcemapURL string) error {
|
|
if err := checkVersion(m.Version); err != nil {
|
|
return err
|
|
}
|
|
|
|
var sourceRootURL *url.URL
|
|
if m.SourceRoot != "" {
|
|
u, err := url.Parse(m.SourceRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if u.IsAbs() {
|
|
sourceRootURL = u
|
|
}
|
|
} else if sourcemapURL != "" {
|
|
u, err := url.Parse(sourcemapURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if u.IsAbs() {
|
|
u.Path = path.Dir(u.Path)
|
|
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
|
|
}
|
|
|
|
m.mappings = mappings
|
|
// Free memory.
|
|
m.Mappings = ""
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *sourceMap) absSource(root *url.URL, source string) string {
|
|
if path.IsAbs(source) {
|
|
return source
|
|
}
|
|
|
|
if u, err := url.Parse(source); err == nil && u.IsAbs() {
|
|
return source
|
|
}
|
|
|
|
if root != nil {
|
|
u := *root
|
|
u.Path = path.Join(u.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 {
|
|
sourcemapURL string
|
|
file string
|
|
sections []section
|
|
}
|
|
|
|
func Parse(sourcemapURL 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(sourcemapURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
reverse(v3.Sections)
|
|
return &Consumer{
|
|
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 {
|
|
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) {
|
|
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 int(m.genLine) == genLine {
|
|
return int(m.genColumn) >= genColumn
|
|
}
|
|
return int(m.genLine) >= genLine
|
|
})
|
|
|
|
// 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 {
|
|
return
|
|
}
|
|
match = &m.mappings[i-1]
|
|
}
|
|
|
|
if match.sourcesInd >= 0 {
|
|
source = m.Sources[match.sourcesInd]
|
|
}
|
|
if match.namesInd >= 0 {
|
|
name = string(m.Names[match.namesInd])
|
|
}
|
|
line = int(match.sourceLine)
|
|
column = int(match.sourceColumn)
|
|
ok = true
|
|
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
|
|
}
|
|
return fmt.Errorf(
|
|
"sourcemap: got version=%d, but only 3rd version is supported",
|
|
version,
|
|
)
|
|
}
|
|
|
|
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]
|
|
}
|
|
}
|