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  }