github.com/evanw/esbuild@v0.21.4/internal/js_parser/sourcemap_parser.go (about) 1 package js_parser 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/evanw/esbuild/internal/ast" 8 "github.com/evanw/esbuild/internal/helpers" 9 "github.com/evanw/esbuild/internal/js_ast" 10 "github.com/evanw/esbuild/internal/logger" 11 "github.com/evanw/esbuild/internal/sourcemap" 12 ) 13 14 // Specification: https://sourcemaps.info/spec.html 15 func ParseSourceMap(log logger.Log, source logger.Source) *sourcemap.SourceMap { 16 expr, ok := ParseJSON(log, source, JSONOptions{ErrorSuffix: " in source map"}) 17 if !ok { 18 return nil 19 } 20 21 obj, ok := expr.Data.(*js_ast.EObject) 22 tracker := logger.MakeLineColumnTracker(&source) 23 if !ok { 24 log.AddError(&tracker, logger.Range{Loc: expr.Loc}, "Invalid source map") 25 return nil 26 } 27 28 var sources []string 29 var sourcesContent []sourcemap.SourceContent 30 var names []string 31 var mappingsRaw []uint16 32 var mappingsStart int32 33 hasVersion := false 34 35 for _, prop := range obj.Properties { 36 keyRange := source.RangeOfString(prop.Key.Loc) 37 38 switch helpers.UTF16ToString(prop.Key.Data.(*js_ast.EString).Value) { 39 case "sections": 40 log.AddID(logger.MsgID_SourceMap_SectionsInSourceMap, logger.Warning, &tracker, keyRange, "Source maps with \"sections\" are not supported") 41 return nil 42 43 case "version": 44 if value, ok := prop.ValueOrNil.Data.(*js_ast.ENumber); ok && value.Value == 3 { 45 hasVersion = true 46 } 47 48 case "mappings": 49 if value, ok := prop.ValueOrNil.Data.(*js_ast.EString); ok { 50 mappingsRaw = value.Value 51 mappingsStart = prop.ValueOrNil.Loc.Start + 1 52 } 53 54 case "sources": 55 if value, ok := prop.ValueOrNil.Data.(*js_ast.EArray); ok { 56 sources = []string{} 57 for _, item := range value.Items { 58 if element, ok := item.Data.(*js_ast.EString); ok { 59 sources = append(sources, helpers.UTF16ToString(element.Value)) 60 } else { 61 sources = append(sources, "") 62 } 63 } 64 } 65 66 case "sourcesContent": 67 if value, ok := prop.ValueOrNil.Data.(*js_ast.EArray); ok { 68 sourcesContent = []sourcemap.SourceContent{} 69 for _, item := range value.Items { 70 if element, ok := item.Data.(*js_ast.EString); ok { 71 sourcesContent = append(sourcesContent, sourcemap.SourceContent{ 72 Value: element.Value, 73 Quoted: source.TextForRange(source.RangeOfString(item.Loc)), 74 }) 75 } else { 76 sourcesContent = append(sourcesContent, sourcemap.SourceContent{}) 77 } 78 } 79 } 80 81 case "names": 82 if value, ok := prop.ValueOrNil.Data.(*js_ast.EArray); ok { 83 names = []string{} 84 for _, item := range value.Items { 85 if element, ok := item.Data.(*js_ast.EString); ok { 86 names = append(names, helpers.UTF16ToString(element.Value)) 87 } else { 88 names = append(names, "") 89 } 90 } 91 } 92 } 93 } 94 95 // Silently fail if the version was missing or incorrect 96 if !hasVersion { 97 return nil 98 } 99 100 // Silently fail if the source map is pointless (i.e. empty) 101 if len(sources) == 0 || len(mappingsRaw) == 0 { 102 return nil 103 } 104 105 var mappings mappingArray 106 mappingsLen := len(mappingsRaw) 107 sourcesLen := len(sources) 108 namesLen := len(names) 109 var generatedLine int32 110 var generatedColumn int32 111 var sourceIndex int32 112 var originalLine int32 113 var originalColumn int32 114 var originalName int32 115 current := 0 116 errorText := "" 117 errorLen := 0 118 needSort := false 119 120 // Parse the mappings 121 for current < mappingsLen { 122 // Handle a line break 123 if mappingsRaw[current] == ';' { 124 generatedLine++ 125 generatedColumn = 0 126 current++ 127 continue 128 } 129 130 // Read the generated column 131 generatedColumnDelta, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:]) 132 if !ok { 133 errorText = "Missing generated column" 134 errorLen = i 135 break 136 } 137 if generatedColumnDelta < 0 { 138 // This would mess up binary search 139 needSort = true 140 } 141 generatedColumn += generatedColumnDelta 142 if generatedColumn < 0 { 143 errorText = fmt.Sprintf("Invalid generated column value: %d", generatedColumn) 144 errorLen = i 145 break 146 } 147 current += i 148 149 // According to the specification, it's valid for a mapping to have 1, 150 // 4, or 5 variable-length fields. Having one field means there's no 151 // original location information, which is pretty useless. Just ignore 152 // those entries. 153 if current == mappingsLen { 154 break 155 } 156 switch mappingsRaw[current] { 157 case ',': 158 current++ 159 continue 160 case ';': 161 continue 162 } 163 164 // Read the original source 165 sourceIndexDelta, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:]) 166 if !ok { 167 errorText = "Missing source index" 168 errorLen = i 169 break 170 } 171 sourceIndex += sourceIndexDelta 172 if sourceIndex < 0 || sourceIndex >= int32(sourcesLen) { 173 errorText = fmt.Sprintf("Invalid source index value: %d", sourceIndex) 174 errorLen = i 175 break 176 } 177 current += i 178 179 // Read the original line 180 originalLineDelta, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:]) 181 if !ok { 182 errorText = "Missing original line" 183 errorLen = i 184 break 185 } 186 originalLine += originalLineDelta 187 if originalLine < 0 { 188 errorText = fmt.Sprintf("Invalid original line value: %d", originalLine) 189 errorLen = i 190 break 191 } 192 current += i 193 194 // Read the original column 195 originalColumnDelta, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:]) 196 if !ok { 197 errorText = "Missing original column" 198 errorLen = i 199 break 200 } 201 originalColumn += originalColumnDelta 202 if originalColumn < 0 { 203 errorText = fmt.Sprintf("Invalid original column value: %d", originalColumn) 204 errorLen = i 205 break 206 } 207 current += i 208 209 // Read the original name 210 var optionalName ast.Index32 211 if originalNameDelta, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:]); ok { 212 originalName += originalNameDelta 213 if originalName < 0 || originalName >= int32(namesLen) { 214 errorText = fmt.Sprintf("Invalid name index value: %d", originalName) 215 errorLen = i 216 break 217 } 218 optionalName = ast.MakeIndex32(uint32(originalName)) 219 current += i 220 } 221 222 // Handle the next character 223 if current < mappingsLen { 224 if c := mappingsRaw[current]; c == ',' { 225 current++ 226 } else if c != ';' { 227 errorText = fmt.Sprintf("Invalid character after mapping: %q", 228 helpers.UTF16ToString(mappingsRaw[current:current+1])) 229 errorLen = 1 230 break 231 } 232 } 233 234 mappings = append(mappings, sourcemap.Mapping{ 235 GeneratedLine: generatedLine, 236 GeneratedColumn: generatedColumn, 237 SourceIndex: sourceIndex, 238 OriginalLine: originalLine, 239 OriginalColumn: originalColumn, 240 OriginalName: optionalName, 241 }) 242 } 243 244 if errorText != "" { 245 r := logger.Range{Loc: logger.Loc{Start: mappingsStart + int32(current)}, Len: int32(errorLen)} 246 log.AddID(logger.MsgID_SourceMap_InvalidSourceMappings, logger.Warning, &tracker, r, 247 fmt.Sprintf("Bad \"mappings\" data in source map at character %d: %s", current, errorText)) 248 return nil 249 } 250 251 if needSort { 252 // If we get here, some mappings are out of order. Lines can't be out of 253 // order by construction but columns can. This is a pretty rare situation 254 // because almost all source map generators always write out mappings in 255 // order as they write the output instead of scrambling the order. 256 sort.Stable(mappings) 257 } 258 259 return &sourcemap.SourceMap{ 260 Sources: sources, 261 SourcesContent: sourcesContent, 262 Mappings: mappings, 263 Names: names, 264 } 265 } 266 267 // This type is just so we can use Go's native sort function 268 type mappingArray []sourcemap.Mapping 269 270 func (a mappingArray) Len() int { return len(a) } 271 func (a mappingArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] } 272 273 func (a mappingArray) Less(i int, j int) bool { 274 ai := a[i] 275 aj := a[j] 276 return ai.GeneratedLine < aj.GeneratedLine || (ai.GeneratedLine == aj.GeneratedLine && ai.GeneratedColumn <= aj.GeneratedColumn) 277 }