userland: add archive tools and pinned GoAWK, esbuild, and QuickJS
This commit is contained in:
@@ -0,0 +1,436 @@
|
||||
package js_parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/evanw/esbuild/internal/ast"
|
||||
"github.com/evanw/esbuild/internal/helpers"
|
||||
"github.com/evanw/esbuild/internal/js_ast"
|
||||
"github.com/evanw/esbuild/internal/logger"
|
||||
"github.com/evanw/esbuild/internal/sourcemap"
|
||||
)
|
||||
|
||||
// New specification: https://tc39.es/ecma426/
|
||||
// Old specification: https://sourcemaps.info/spec.html
|
||||
func ParseSourceMap(log logger.Log, source logger.Source) *sourcemap.SourceMap {
|
||||
expr, ok := ParseJSON(log, source, JSONOptions{ErrorSuffix: " in source map"})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
obj, ok := expr.Data.(*js_ast.EObject)
|
||||
tracker := logger.MakeLineColumnTracker(&source)
|
||||
if !ok {
|
||||
log.AddError(&tracker, logger.Range{Loc: expr.Loc}, "Invalid source map")
|
||||
return nil
|
||||
}
|
||||
|
||||
type sourceMapSection struct {
|
||||
lineOffset int32
|
||||
columnOffset int32
|
||||
sourceMap *js_ast.EObject
|
||||
}
|
||||
|
||||
var sections []sourceMapSection
|
||||
hasSections := false
|
||||
|
||||
for _, prop := range obj.Properties {
|
||||
if !helpers.UTF16EqualsString(prop.Key.Data.(*js_ast.EString).Value, "sections") {
|
||||
continue
|
||||
}
|
||||
|
||||
if value, ok := prop.ValueOrNil.Data.(*js_ast.EArray); ok {
|
||||
for _, item := range value.Items {
|
||||
if element, ok := item.Data.(*js_ast.EObject); ok {
|
||||
var sectionLineOffset int32
|
||||
var sectionColumnOffset int32
|
||||
var sectionSourceMap *js_ast.EObject
|
||||
|
||||
for _, sectionProp := range element.Properties {
|
||||
switch helpers.UTF16ToString(sectionProp.Key.Data.(*js_ast.EString).Value) {
|
||||
case "offset":
|
||||
if offsetValue, ok := sectionProp.ValueOrNil.Data.(*js_ast.EObject); ok {
|
||||
for _, offsetProp := range offsetValue.Properties {
|
||||
switch helpers.UTF16ToString(offsetProp.Key.Data.(*js_ast.EString).Value) {
|
||||
case "line":
|
||||
if lineValue, ok := offsetProp.ValueOrNil.Data.(*js_ast.ENumber); ok {
|
||||
sectionLineOffset = int32(lineValue.Value)
|
||||
}
|
||||
|
||||
case "column":
|
||||
if columnValue, ok := offsetProp.ValueOrNil.Data.(*js_ast.ENumber); ok {
|
||||
sectionColumnOffset = int32(columnValue.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.AddError(&tracker, logger.Range{Loc: sectionProp.ValueOrNil.Loc}, "Expected \"offset\" to be an object")
|
||||
return nil
|
||||
}
|
||||
|
||||
case "map":
|
||||
if mapValue, ok := sectionProp.ValueOrNil.Data.(*js_ast.EObject); ok {
|
||||
sectionSourceMap = mapValue
|
||||
} else {
|
||||
log.AddError(&tracker, logger.Range{Loc: sectionProp.ValueOrNil.Loc}, "Expected \"map\" to be an object")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sectionSourceMap != nil {
|
||||
sections = append(sections, sourceMapSection{
|
||||
lineOffset: sectionLineOffset,
|
||||
columnOffset: sectionColumnOffset,
|
||||
sourceMap: sectionSourceMap,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.AddError(&tracker, logger.Range{Loc: prop.ValueOrNil.Loc}, "Expected \"sections\" to be an array")
|
||||
return nil
|
||||
}
|
||||
|
||||
hasSections = true
|
||||
break
|
||||
}
|
||||
|
||||
if !hasSections {
|
||||
sections = append(sections, sourceMapSection{
|
||||
sourceMap: obj,
|
||||
})
|
||||
}
|
||||
|
||||
var sources []string
|
||||
var sourcesContent []sourcemap.SourceContent
|
||||
var names []string
|
||||
var mappings mappingArray
|
||||
var generatedLine int32
|
||||
var generatedColumn int32
|
||||
needSort := false
|
||||
|
||||
for _, section := range sections {
|
||||
var sourcesArray []js_ast.Expr
|
||||
var sourcesContentArray []js_ast.Expr
|
||||
var namesArray []js_ast.Expr
|
||||
var mappingsRaw []uint16
|
||||
var mappingsStart int32
|
||||
var sourceRoot string
|
||||
hasVersion := false
|
||||
|
||||
for _, prop := range section.sourceMap.Properties {
|
||||
switch helpers.UTF16ToString(prop.Key.Data.(*js_ast.EString).Value) {
|
||||
case "version":
|
||||
if value, ok := prop.ValueOrNil.Data.(*js_ast.ENumber); ok && value.Value == 3 {
|
||||
hasVersion = true
|
||||
}
|
||||
|
||||
case "mappings":
|
||||
if value, ok := prop.ValueOrNil.Data.(*js_ast.EString); ok {
|
||||
mappingsRaw = value.Value
|
||||
mappingsStart = prop.ValueOrNil.Loc.Start + 1
|
||||
}
|
||||
|
||||
case "sourceRoot":
|
||||
if value, ok := prop.ValueOrNil.Data.(*js_ast.EString); ok {
|
||||
sourceRoot = helpers.UTF16ToString(value.Value)
|
||||
}
|
||||
|
||||
case "sources":
|
||||
if value, ok := prop.ValueOrNil.Data.(*js_ast.EArray); ok {
|
||||
sourcesArray = value.Items
|
||||
}
|
||||
|
||||
case "sourcesContent":
|
||||
if value, ok := prop.ValueOrNil.Data.(*js_ast.EArray); ok {
|
||||
sourcesContentArray = value.Items
|
||||
}
|
||||
|
||||
case "names":
|
||||
if value, ok := prop.ValueOrNil.Data.(*js_ast.EArray); ok {
|
||||
namesArray = value.Items
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Silently ignore the section if the version was missing or incorrect
|
||||
if !hasVersion {
|
||||
continue
|
||||
}
|
||||
|
||||
mappingsLen := len(mappingsRaw)
|
||||
sourcesLen := len(sourcesArray)
|
||||
namesLen := len(namesArray)
|
||||
|
||||
// Silently ignore the section if the source map is pointless (i.e. empty)
|
||||
if mappingsLen == 0 || sourcesLen == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if section.lineOffset < generatedLine || (section.lineOffset == generatedLine && section.columnOffset < generatedColumn) {
|
||||
needSort = true
|
||||
}
|
||||
|
||||
lineOffset := section.lineOffset
|
||||
columnOffset := section.columnOffset
|
||||
sourceOffset := int32(len(sources))
|
||||
nameOffset := int32(len(names))
|
||||
|
||||
generatedLine = lineOffset
|
||||
generatedColumn = columnOffset
|
||||
sourceIndex := sourceOffset
|
||||
var originalLine int32
|
||||
var originalColumn int32
|
||||
originalName := nameOffset
|
||||
|
||||
current := 0
|
||||
errorText := ""
|
||||
errorLen := 0
|
||||
|
||||
// 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 (generatedLine == lineOffset && generatedColumn < columnOffset) || 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 < sourceOffset || sourceIndex >= sourceOffset+int32(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
|
||||
|
||||
// Read the original name
|
||||
var optionalName ast.Index32
|
||||
if originalNameDelta, i, ok := sourcemap.DecodeVLQUTF16(mappingsRaw[current:]); ok {
|
||||
originalName += originalNameDelta
|
||||
if originalName < nameOffset || originalName >= nameOffset+int32(namesLen) {
|
||||
errorText = fmt.Sprintf("Invalid name index value: %d", originalName)
|
||||
errorLen = i
|
||||
break
|
||||
}
|
||||
optionalName = ast.MakeIndex32(uint32(originalName))
|
||||
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",
|
||||
helpers.UTF16ToString(mappingsRaw[current:current+1]))
|
||||
errorLen = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
mappings = append(mappings, sourcemap.Mapping{
|
||||
GeneratedLine: generatedLine,
|
||||
GeneratedColumn: generatedColumn,
|
||||
SourceIndex: sourceIndex,
|
||||
OriginalLine: originalLine,
|
||||
OriginalColumn: originalColumn,
|
||||
OriginalName: optionalName,
|
||||
})
|
||||
}
|
||||
|
||||
if errorText != "" {
|
||||
r := logger.Range{Loc: logger.Loc{Start: mappingsStart + int32(current)}, Len: int32(errorLen)}
|
||||
log.AddID(logger.MsgID_SourceMap_InvalidSourceMappings, logger.Warning, &tracker, r,
|
||||
fmt.Sprintf("Bad \"mappings\" data in source map at character %d: %s", current, errorText))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try resolving relative source URLs into absolute source URLs.
|
||||
// See https://tc39.es/ecma426/#resolving-sources for details.
|
||||
var sourceURLPrefix string
|
||||
var baseURL *url.URL
|
||||
if sourceRoot != "" {
|
||||
if index := strings.LastIndexByte(sourceRoot, '/'); index != -1 {
|
||||
sourceURLPrefix = sourceRoot[:index+1]
|
||||
} else {
|
||||
sourceURLPrefix = sourceRoot + "/"
|
||||
}
|
||||
}
|
||||
if source.KeyPath.Namespace == "file" {
|
||||
baseURL = helpers.FileURLFromFilePath(source.KeyPath.Text)
|
||||
}
|
||||
|
||||
for _, item := range sourcesArray {
|
||||
if element, ok := item.Data.(*js_ast.EString); ok {
|
||||
sourcePath := sourceURLPrefix + helpers.UTF16ToString(element.Value)
|
||||
sourceURL, err := url.Parse(sourcePath)
|
||||
|
||||
// Ignore URL parse errors (such as "%XY" being an invalid escape)
|
||||
if err != nil {
|
||||
sources = append(sources, sourcePath)
|
||||
continue
|
||||
}
|
||||
|
||||
// Resolve this URL relative to the enclosing directory
|
||||
if baseURL != nil {
|
||||
sourceURL = baseURL.ResolveReference(sourceURL)
|
||||
}
|
||||
sources = append(sources, sourceURL.String())
|
||||
} else {
|
||||
sources = append(sources, "")
|
||||
}
|
||||
}
|
||||
|
||||
if len(sourcesContentArray) > 0 {
|
||||
// It's possible that one of the source maps inside "sections" has
|
||||
// different lengths for the "sources" and "sourcesContent" arrays.
|
||||
// This is bad because we need to us a single index to get the name
|
||||
// of the source from "sources[i]" and the content of the source
|
||||
// from "sourcesContent[i]".
|
||||
//
|
||||
// So if a previous source map had a shorter "sourcesContent" array
|
||||
// than its "sources" array (or if the previous source map just had
|
||||
// no "sourcesContent" array), expand our aggregated array to the
|
||||
// right length by padding it out with empty entries.
|
||||
sourcesContent = append(sourcesContent, make([]sourcemap.SourceContent, int(sourceOffset)-len(sourcesContent))...)
|
||||
|
||||
for i, item := range sourcesContentArray {
|
||||
// Make sure we don't ever record more "sourcesContent" entries
|
||||
// than there are "sources" entries, which is possible because
|
||||
// these are two separate arrays in the source map JSON. We need
|
||||
// to avoid this because that would mess up our shared indexing
|
||||
// of the "sources" and "sourcesContent" arrays. See the above
|
||||
// comment for more details.
|
||||
if i == sourcesLen {
|
||||
break
|
||||
}
|
||||
|
||||
if element, ok := item.Data.(*js_ast.EString); ok {
|
||||
sourcesContent = append(sourcesContent, sourcemap.SourceContent{
|
||||
Value: element.Value,
|
||||
Quoted: source.TextForRange(source.RangeOfString(item.Loc)),
|
||||
})
|
||||
} else {
|
||||
sourcesContent = append(sourcesContent, sourcemap.SourceContent{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range namesArray {
|
||||
if element, ok := item.Data.(*js_ast.EString); ok {
|
||||
names = append(names, helpers.UTF16ToString(element.Value))
|
||||
} else {
|
||||
names = append(names, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Silently fail if the source map is pointless (i.e. empty)
|
||||
if len(sources) == 0 || len(mappings) == 0 {
|
||||
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,
|
||||
Names: names,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user