1package sourcemap
2
3import (
4	"encoding/json"
5	"fmt"
6	"net/url"
7	"path"
8	"sort"
9)
10
11type sourceMap struct {
12	Version        int               `json:"version"`
13	File           string            `json:"file"`
14	SourceRoot     string            `json:"sourceRoot"`
15	Sources        []string          `json:"sources"`
16	SourcesContent []string          `json:"sourcesContent"`
17	Names          []json.RawMessage `json:"names,string"`
18	Mappings       string            `json:"mappings"`
19
20	mappings []mapping
21}
22
23type v3 struct {
24	sourceMap
25	Sections []section `json:"sections"`
26}
27
28func (m *sourceMap) parse(sourcemapURL string) error {
29	if err := checkVersion(m.Version); err != nil {
30		return err
31	}
32
33	var sourceRootURL *url.URL
34	if m.SourceRoot != "" {
35		u, err := url.Parse(m.SourceRoot)
36		if err != nil {
37			return err
38		}
39		if u.IsAbs() {
40			sourceRootURL = u
41		}
42	} else if sourcemapURL != "" {
43		u, err := url.Parse(sourcemapURL)
44		if err != nil {
45			return err
46		}
47		if u.IsAbs() {
48			u.Path = path.Dir(u.Path)
49			sourceRootURL = u
50		}
51	}
52
53	for i, src := range m.Sources {
54		m.Sources[i] = m.absSource(sourceRootURL, src)
55	}
56
57	mappings, err := parseMappings(m.Mappings)
58	if err != nil {
59		return err
60	}
61
62	m.mappings = mappings
63	// Free memory.
64	m.Mappings = ""
65
66	return nil
67}
68
69func (m *sourceMap) absSource(root *url.URL, source string) string {
70	if path.IsAbs(source) {
71		return source
72	}
73
74	if u, err := url.Parse(source); err == nil && u.IsAbs() {
75		return source
76	}
77
78	if root != nil {
79		u := *root
80		u.Path = path.Join(u.Path, source)
81		return u.String()
82	}
83
84	if m.SourceRoot != "" {
85		return path.Join(m.SourceRoot, source)
86	}
87
88	return source
89}
90
91func (m *sourceMap) name(idx int) string {
92	if idx >= len(m.Names) {
93		return ""
94	}
95
96	raw := m.Names[idx]
97	if len(raw) == 0 {
98		return ""
99	}
100
101	if raw[0] == '"' && raw[len(raw)-1] == '"' {
102		var str string
103		if err := json.Unmarshal(raw, &str); err == nil {
104			return str
105		}
106	}
107
108	return string(raw)
109}
110
111type section struct {
112	Offset struct {
113		Line   int `json:"line"`
114		Column int `json:"column"`
115	} `json:"offset"`
116	Map *sourceMap `json:"map"`
117}
118
119type Consumer struct {
120	sourcemapURL string
121	file         string
122	sections     []section
123}
124
125func Parse(sourcemapURL string, b []byte) (*Consumer, error) {
126	v3 := new(v3)
127	err := json.Unmarshal(b, v3)
128	if err != nil {
129		return nil, err
130	}
131
132	if err := checkVersion(v3.Version); err != nil {
133		return nil, err
134	}
135
136	if len(v3.Sections) == 0 {
137		v3.Sections = append(v3.Sections, section{
138			Map: &v3.sourceMap,
139		})
140	}
141
142	for _, s := range v3.Sections {
143		err := s.Map.parse(sourcemapURL)
144		if err != nil {
145			return nil, err
146		}
147	}
148
149	reverse(v3.Sections)
150	return &Consumer{
151		sourcemapURL: sourcemapURL,
152		file:         v3.File,
153		sections:     v3.Sections,
154	}, nil
155}
156
157func (c *Consumer) SourcemapURL() string {
158	return c.sourcemapURL
159}
160
161// File returns an optional name of the generated code
162// that this source map is associated with.
163func (c *Consumer) File() string {
164	return c.file
165}
166
167// Source returns the original source, name, line, and column information
168// for the generated source's line and column positions.
169func (c *Consumer) Source(
170	genLine, genColumn int,
171) (source, name string, line, column int, ok bool) {
172	for i := range c.sections {
173		s := &c.sections[i]
174		if s.Offset.Line < genLine ||
175			(s.Offset.Line+1 == genLine && s.Offset.Column <= genColumn) {
176			genLine -= s.Offset.Line
177			genColumn -= s.Offset.Column
178			return c.source(s.Map, genLine, genColumn)
179		}
180	}
181	return
182}
183
184func (c *Consumer) source(
185	m *sourceMap, genLine, genColumn int,
186) (source, name string, line, column int, ok bool) {
187	i := sort.Search(len(m.mappings), func(i int) bool {
188		m := &m.mappings[i]
189		if int(m.genLine) == genLine {
190			return int(m.genColumn) >= genColumn
191		}
192		return int(m.genLine) >= genLine
193	})
194
195	// Mapping not found.
196	if i == len(m.mappings) {
197		return
198	}
199
200	match := &m.mappings[i]
201
202	// Fuzzy match.
203	if int(match.genLine) > genLine || int(match.genColumn) > genColumn {
204		if i == 0 {
205			return
206		}
207		match = &m.mappings[i-1]
208	}
209
210	if match.sourcesInd >= 0 {
211		source = m.Sources[match.sourcesInd]
212	}
213	if match.namesInd >= 0 {
214		name = m.name(int(match.namesInd))
215	}
216	line = int(match.sourceLine)
217	column = int(match.sourceColumn)
218	ok = true
219	return
220}
221
222// SourceContent returns the original source content for the source.
223func (c *Consumer) SourceContent(source string) string {
224	for i := range c.sections {
225		s := &c.sections[i]
226		for i, src := range s.Map.Sources {
227			if src == source {
228				if i < len(s.Map.SourcesContent) {
229					return s.Map.SourcesContent[i]
230				}
231				break
232			}
233		}
234	}
235	return ""
236}
237
238func checkVersion(version int) error {
239	if version == 3 || version == 0 {
240		return nil
241	}
242	return fmt.Errorf(
243		"sourcemap: got version=%d, but only 3rd version is supported",
244		version,
245	)
246}
247
248func reverse(ss []section) {
249	last := len(ss) - 1
250	for i := 0; i < len(ss)/2; i++ {
251		ss[i], ss[last-i] = ss[last-i], ss[i]
252	}
253}
254