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