1 package sourcemap
2
3 import (
4 "encoding/json"
5 "fmt"
6 "net/url"
7 "path"
8 "sort"
9 )
10
11 type 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
23 type v3 struct {
24 sourceMap
25 Sections []section `json:"sections"`
26 }
27
28 func (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
64 m.Mappings = ""
65
66 return nil
67 }
68
69 func (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
91 func (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
111 type 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
119 type Consumer struct {
120 sourcemapURL string
121 file string
122 sections []section
123 }
124
125 func 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
157 func (c *Consumer) SourcemapURL() string {
158 return c.sourcemapURL
159 }
160
161
162
163 func (c *Consumer) File() string {
164 return c.file
165 }
166
167
168
169 func (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
184 func (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
196 if i == len(m.mappings) {
197 return
198 }
199
200 match := &m.mappings[i]
201
202
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
223 func (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
238 func 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
248 func 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
View as plain text