mirror of
https://github.com/zhigang1992/esbuild.git
synced 2026-04-24 21:26:11 +08:00
247 lines
6.3 KiB
Go
247 lines
6.3 KiB
Go
package js_parser
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/evanw/esbuild/internal/js_ast"
|
|
"github.com/evanw/esbuild/internal/js_lexer"
|
|
"github.com/evanw/esbuild/internal/logger"
|
|
"github.com/evanw/esbuild/internal/sourcemap"
|
|
)
|
|
|
|
// Specification: https://sourcemaps.info/spec.html
|
|
func ParseSourceMap(log logger.Log, source logger.Source) *sourcemap.SourceMap {
|
|
expr, ok := ParseJSON(log, source, ParseJSONOptions{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
obj, ok := expr.Data.(*js_ast.EObject)
|
|
if !ok {
|
|
log.AddError(&source, expr.Loc, "Invalid source map")
|
|
return nil
|
|
}
|
|
|
|
var sourcesContent []*string
|
|
var sources []string
|
|
var mappingsRaw []uint16
|
|
var mappingsStart int32
|
|
hasVersion := false
|
|
|
|
for _, prop := range obj.Properties {
|
|
keyRange := source.RangeOfString(prop.Key.Loc)
|
|
|
|
switch js_lexer.UTF16ToString(prop.Key.Data.(*js_ast.EString).Value) {
|
|
case "sections":
|
|
log.AddRangeWarning(&source, keyRange, "Source maps with \"sections\" are not supported")
|
|
return nil
|
|
|
|
case "version":
|
|
if value, ok := prop.Value.Data.(*js_ast.ENumber); ok && value.Value == 3 {
|
|
hasVersion = true
|
|
}
|
|
|
|
case "mappings":
|
|
if value, ok := prop.Value.Data.(*js_ast.EString); ok {
|
|
mappingsRaw = value.Value
|
|
mappingsStart = prop.Value.Loc.Start + 1
|
|
}
|
|
|
|
case "sources":
|
|
if value, ok := prop.Value.Data.(*js_ast.EArray); ok {
|
|
sources = nil
|
|
for _, item := range value.Items {
|
|
if element, ok := item.Data.(*js_ast.EString); ok {
|
|
sources = append(sources, js_lexer.UTF16ToString(element.Value))
|
|
}
|
|
}
|
|
}
|
|
|
|
case "sourcesContent":
|
|
if value, ok := prop.Value.Data.(*js_ast.EArray); ok {
|
|
sourcesContent = nil
|
|
for _, item := range value.Items {
|
|
if element, ok := item.Data.(*js_ast.EString); ok {
|
|
str := js_lexer.UTF16ToString(element.Value)
|
|
sourcesContent = append(sourcesContent, &str)
|
|
} else {
|
|
sourcesContent = append(sourcesContent, nil)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Silently fail if the version was missing or incorrect
|
|
if !hasVersion {
|
|
return nil
|
|
}
|
|
|
|
// Silently fail if the source map is pointless (i.e. empty)
|
|
if len(sources) == 0 || len(mappingsRaw) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var mappings mappingArray
|
|
mappingsLen := len(mappingsRaw)
|
|
sourcesLen := len(sources)
|
|
generatedLine := 0
|
|
generatedColumn := 0
|
|
sourceIndex := 0
|
|
originalLine := 0
|
|
originalColumn := 0
|
|
current := 0
|
|
errorText := ""
|
|
errorLen := 0
|
|
needSort := false
|
|
|
|
// Parse the mappings
|
|
for current < mappingsLen {
|
|
// Handle a line break
|
|
if mappingsRaw[current] == ';' {
|
|
generatedLine++
|
|
generatedColumn = 0
|
|
current++
|
|
continue
|
|
}
|
|
|
|
// Read the generated column
|
|
generatedColumnDelta, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:])
|
|
if !ok {
|
|
errorText = "Missing generated column"
|
|
errorLen = i
|
|
break
|
|
}
|
|
if generatedColumnDelta < 0 {
|
|
// This would mess up binary search
|
|
needSort = true
|
|
}
|
|
generatedColumn += generatedColumnDelta
|
|
if generatedColumn < 0 {
|
|
errorText = fmt.Sprintf("Invalid generated column value: %d", generatedColumn)
|
|
errorLen = i
|
|
break
|
|
}
|
|
current += i
|
|
|
|
// According to the specification, it's valid for a mapping to have 1,
|
|
// 4, or 5 variable-length fields. Having one field means there's no
|
|
// original location information, which is pretty useless. Just ignore
|
|
// those entries.
|
|
if current == mappingsLen {
|
|
break
|
|
}
|
|
switch mappingsRaw[current] {
|
|
case ',':
|
|
current++
|
|
continue
|
|
case ';':
|
|
continue
|
|
}
|
|
|
|
// Read the original source
|
|
sourceIndexDelta, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:])
|
|
if !ok {
|
|
errorText = "Missing source index"
|
|
errorLen = i
|
|
break
|
|
}
|
|
sourceIndex += sourceIndexDelta
|
|
if sourceIndex < 0 || sourceIndex >= sourcesLen {
|
|
errorText = fmt.Sprintf("Invalid source index value: %d", sourceIndex)
|
|
errorLen = i
|
|
break
|
|
}
|
|
current += i
|
|
|
|
// Read the original line
|
|
originalLineDelta, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:])
|
|
if !ok {
|
|
errorText = "Missing original line"
|
|
errorLen = i
|
|
break
|
|
}
|
|
originalLine += originalLineDelta
|
|
if originalLine < 0 {
|
|
errorText = fmt.Sprintf("Invalid original line value: %d", originalLine)
|
|
errorLen = i
|
|
break
|
|
}
|
|
current += i
|
|
|
|
// Read the original column
|
|
originalColumnDelta, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:])
|
|
if !ok {
|
|
errorText = "Missing original column"
|
|
errorLen = i
|
|
break
|
|
}
|
|
originalColumn += originalColumnDelta
|
|
if originalColumn < 0 {
|
|
errorText = fmt.Sprintf("Invalid original column value: %d", originalColumn)
|
|
errorLen = i
|
|
break
|
|
}
|
|
current += i
|
|
|
|
// Ignore the optional name index
|
|
if _, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:]); ok {
|
|
current += i
|
|
}
|
|
|
|
// Handle the next character
|
|
if current < mappingsLen {
|
|
if c := mappingsRaw[current]; c == ',' {
|
|
current++
|
|
} else if c != ';' {
|
|
errorText = fmt.Sprintf("Invalid character after mapping: %q",
|
|
js_lexer.UTF16ToString(mappingsRaw[current:current+1]))
|
|
errorLen = 1
|
|
break
|
|
}
|
|
}
|
|
|
|
mappings = append(mappings, sourcemap.Mapping{
|
|
GeneratedLine: int32(generatedLine),
|
|
GeneratedColumn: int32(generatedColumn),
|
|
SourceIndex: int32(sourceIndex),
|
|
OriginalLine: int32(originalLine),
|
|
OriginalColumn: int32(originalColumn),
|
|
})
|
|
}
|
|
|
|
if errorText != "" {
|
|
r := logger.Range{Loc: logger.Loc{Start: mappingsStart + int32(current)}, Len: int32(errorLen)}
|
|
log.AddRangeWarning(&source, r,
|
|
fmt.Sprintf("Bad \"mappings\" data in source map at character %d: %s", current, errorText))
|
|
return nil
|
|
}
|
|
|
|
if needSort {
|
|
// If we get here, some mappings are out of order. Lines can't be out of
|
|
// order by construction but columns can. This is a pretty rare situation
|
|
// because almost all source map generators always write out mappings in
|
|
// order as they write the output instead of scrambling the order.
|
|
sort.Stable(mappings)
|
|
}
|
|
|
|
return &sourcemap.SourceMap{
|
|
Sources: sources,
|
|
SourcesContent: sourcesContent,
|
|
Mappings: mappings,
|
|
}
|
|
}
|
|
|
|
// This type is just so we can use Go's native sort function
|
|
type mappingArray []sourcemap.Mapping
|
|
|
|
func (a mappingArray) Len() int { return len(a) }
|
|
func (a mappingArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
func (a mappingArray) Less(i int, j int) bool {
|
|
ai := a[i]
|
|
aj := a[j]
|
|
return ai.GeneratedLine < aj.GeneratedLine || (ai.GeneratedLine == aj.GeneratedLine && ai.GeneratedColumn <= aj.GeneratedColumn)
|
|
}
|