github.com/evanw/esbuild@v0.21.4/pkg/cli/mangle_cache.go (about)

     1  package cli
     2  
     3  // The mangle cache is a JSON file that remembers esbuild's property renaming
     4  // decisions. It's a flat map where the keys are strings and the values are
     5  // either strings or the boolean value "false". This is the case both in JSON
     6  // and in Go (so the "interface{}" values are also either strings or "false").
     7  
     8  import (
     9  	"fmt"
    10  	"sort"
    11  	"strings"
    12  	"syscall"
    13  
    14  	"github.com/evanw/esbuild/internal/fs"
    15  	"github.com/evanw/esbuild/internal/helpers"
    16  	"github.com/evanw/esbuild/internal/js_ast"
    17  	"github.com/evanw/esbuild/internal/js_lexer"
    18  	"github.com/evanw/esbuild/internal/js_parser"
    19  	"github.com/evanw/esbuild/internal/logger"
    20  )
    21  
    22  func parseMangleCache(osArgs []string, fs fs.FS, absPath string) (map[string]interface{}, []string) {
    23  	// Log problems with the mangle cache to stderr
    24  	log := logger.NewStderrLog(logger.OutputOptionsForArgs(osArgs))
    25  	defer log.Done()
    26  
    27  	// Try to read the existing file
    28  	prettyPath := absPath
    29  	if rel, ok := fs.Rel(fs.Cwd(), absPath); ok {
    30  		prettyPath = rel
    31  	}
    32  	prettyPath = strings.ReplaceAll(prettyPath, "\\", "/")
    33  	bytes, err, originalError := fs.ReadFile(absPath)
    34  	if err != nil {
    35  		// It's ok if it's just missing
    36  		if err == syscall.ENOENT {
    37  			return make(map[string]interface{}), []string{}
    38  		}
    39  
    40  		// Otherwise, report the error
    41  		log.AddError(nil, logger.Range{},
    42  			fmt.Sprintf("Failed to read from mangle cache file %q: %s", prettyPath, originalError.Error()))
    43  		return nil, nil
    44  	}
    45  
    46  	// Use our JSON parser so we get pretty-printed error messages
    47  	source := logger.Source{
    48  		KeyPath:    logger.Path{Text: absPath, Namespace: "file"},
    49  		PrettyPath: prettyPath,
    50  		Contents:   string(bytes),
    51  	}
    52  	result, ok := js_parser.ParseJSON(log, source, js_parser.JSONOptions{})
    53  	if !ok || log.HasErrors() {
    54  		// Stop if there were any errors so we don't continue and then overwrite this file
    55  		return nil, nil
    56  	}
    57  	tracker := logger.MakeLineColumnTracker(&source)
    58  
    59  	// Validate the top-level object
    60  	root, ok := result.Data.(*js_ast.EObject)
    61  	if !ok {
    62  		log.AddError(&tracker, logger.Range{Loc: result.Loc},
    63  			"Expected a top-level object in mangle cache file")
    64  		return nil, nil
    65  	}
    66  
    67  	mangleCache := make(map[string]interface{}, len(root.Properties))
    68  	order := make([]string, 0, len(root.Properties))
    69  
    70  	for _, property := range root.Properties {
    71  		key := helpers.UTF16ToString(property.Key.Data.(*js_ast.EString).Value)
    72  		order = append(order, key)
    73  
    74  		switch v := property.ValueOrNil.Data.(type) {
    75  		case *js_ast.EBoolean:
    76  			if v.Value {
    77  				log.AddError(&tracker, js_lexer.RangeOfIdentifier(source, property.ValueOrNil.Loc),
    78  					fmt.Sprintf("Expected %q in mangle cache file to map to either a string or false", key))
    79  			} else {
    80  				mangleCache[key] = false
    81  			}
    82  
    83  		case *js_ast.EString:
    84  			mangleCache[key] = helpers.UTF16ToString(v.Value)
    85  
    86  		default:
    87  			log.AddError(&tracker, logger.Range{Loc: property.ValueOrNil.Loc},
    88  				fmt.Sprintf("Expected %q in mangle cache file to map to either a string or false", key))
    89  		}
    90  	}
    91  
    92  	if log.HasErrors() {
    93  		return nil, nil
    94  	}
    95  	return mangleCache, order
    96  }
    97  
    98  func printMangleCache(mangleCache map[string]interface{}, originalOrder []string, asciiOnly bool) []byte {
    99  	j := helpers.Joiner{}
   100  	j.AddString("{")
   101  
   102  	// Determine the order to print the keys in
   103  	order := originalOrder
   104  	if len(mangleCache) > len(order) {
   105  		order = make([]string, 0, len(mangleCache))
   106  		if sort.StringsAreSorted(originalOrder) {
   107  			// If they came sorted, keep them sorted
   108  			for key := range mangleCache {
   109  				order = append(order, key)
   110  			}
   111  			sort.Strings(order)
   112  		} else {
   113  			// Otherwise add all new keys to the end, and only sort the new keys
   114  			originalKeys := make(map[string]bool, len(originalOrder))
   115  			for _, key := range originalOrder {
   116  				originalKeys[key] = true
   117  			}
   118  			order = append(order, originalOrder...)
   119  			for key := range mangleCache {
   120  				if !originalKeys[key] {
   121  					order = append(order, key)
   122  				}
   123  			}
   124  			sort.Strings(order[len(originalOrder):])
   125  		}
   126  	}
   127  
   128  	// Print the JSON while preserving the existing order of the keys
   129  	for i, key := range order {
   130  		// Print the key
   131  		if i > 0 {
   132  			j.AddString(",\n  ")
   133  		} else {
   134  			j.AddString("\n  ")
   135  		}
   136  		j.AddBytes(helpers.QuoteForJSON(key, asciiOnly))
   137  
   138  		// Print the value
   139  		if value := mangleCache[key]; value != false {
   140  			j.AddString(": ")
   141  			j.AddBytes(helpers.QuoteForJSON(value.(string), asciiOnly))
   142  		} else {
   143  			j.AddString(": false")
   144  		}
   145  	}
   146  
   147  	if len(order) > 0 {
   148  		j.AddString("\n")
   149  	}
   150  	j.AddString("}\n")
   151  	return j.Done()
   152  }