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.RawMessage `json:"names,string"` 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 } 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"` 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 = m.name(int(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] } }