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.Number `json:"names"` 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 91type section struct { 92 Offset struct { 93 Line int `json:"line"` 94 Column int `json:"column"` 95 } `json:"offset"` 96 Map *sourceMap `json:"map"` 97} 98 99type Consumer struct { 100 sourcemapURL string 101 file string 102 sections []section 103} 104 105func Parse(sourcemapURL string, b []byte) (*Consumer, error) { 106 v3 := new(v3) 107 err := json.Unmarshal(b, v3) 108 if err != nil { 109 return nil, err 110 } 111 112 if err := checkVersion(v3.Version); err != nil { 113 return nil, err 114 } 115 116 if len(v3.Sections) == 0 { 117 v3.Sections = append(v3.Sections, section{ 118 Map: &v3.sourceMap, 119 }) 120 } 121 122 for _, s := range v3.Sections { 123 err := s.Map.parse(sourcemapURL) 124 if err != nil { 125 return nil, err 126 } 127 } 128 129 reverse(v3.Sections) 130 return &Consumer{ 131 sourcemapURL: sourcemapURL, 132 file: v3.File, 133 sections: v3.Sections, 134 }, nil 135} 136 137func (c *Consumer) SourcemapURL() string { 138 return c.sourcemapURL 139} 140 141// File returns an optional name of the generated code 142// that this source map is associated with. 143func (c *Consumer) File() string { 144 return c.file 145} 146 147// Source returns the original source, name, line, and column information 148// for the generated source's line and column positions. 149func (c *Consumer) Source( 150 genLine, genColumn int, 151) (source, name string, line, column int, ok bool) { 152 for i := range c.sections { 153 s := &c.sections[i] 154 if s.Offset.Line < genLine || 155 (s.Offset.Line+1 == genLine && s.Offset.Column <= genColumn) { 156 genLine -= s.Offset.Line 157 genColumn -= s.Offset.Column 158 return c.source(s.Map, genLine, genColumn) 159 } 160 } 161 return 162} 163 164func (c *Consumer) source( 165 m *sourceMap, genLine, genColumn int, 166) (source, name string, line, column int, ok bool) { 167 i := sort.Search(len(m.mappings), func(i int) bool { 168 m := &m.mappings[i] 169 if int(m.genLine) == genLine { 170 return int(m.genColumn) >= genColumn 171 } 172 return int(m.genLine) >= genLine 173 }) 174 175 // Mapping not found. 176 if i == len(m.mappings) { 177 return 178 } 179 180 match := &m.mappings[i] 181 182 // Fuzzy match. 183 if int(match.genLine) > genLine || int(match.genColumn) > genColumn { 184 if i == 0 { 185 return 186 } 187 match = &m.mappings[i-1] 188 } 189 190 if match.sourcesInd >= 0 { 191 source = m.Sources[match.sourcesInd] 192 } 193 if match.namesInd >= 0 { 194 name = string(m.Names[match.namesInd]) 195 } 196 line = int(match.sourceLine) 197 column = int(match.sourceColumn) 198 ok = true 199 return 200} 201 202// SourceContent returns the original source content for the source. 203func (c *Consumer) SourceContent(source string) string { 204 for i := range c.sections { 205 s := &c.sections[i] 206 for i, src := range s.Map.Sources { 207 if src == source { 208 if i < len(s.Map.SourcesContent) { 209 return s.Map.SourcesContent[i] 210 } 211 break 212 } 213 } 214 } 215 return "" 216} 217 218func checkVersion(version int) error { 219 if version == 3 || version == 0 { 220 return nil 221 } 222 return fmt.Errorf( 223 "sourcemap: got version=%d, but only 3rd version is supported", 224 version, 225 ) 226} 227 228func reverse(ss []section) { 229 last := len(ss) - 1 230 for i := 0; i < len(ss)/2; i++ { 231 ss[i], ss[last-i] = ss[last-i], ss[i] 232 } 233} 234