package cli // The mangle cache is a JSON file that remembers esbuild's property renaming // decisions. It's a flat map where the keys are strings and the values are // either strings or the boolean value "false". This is the case both in JSON // and in Go (so the "interface{}" values are also either strings or "false"). import ( "fmt" "sort" "strings" "syscall" "github.com/evanw/esbuild/internal/fs" "github.com/evanw/esbuild/internal/helpers" "github.com/evanw/esbuild/internal/js_ast" "github.com/evanw/esbuild/internal/js_lexer" "github.com/evanw/esbuild/internal/js_parser" "github.com/evanw/esbuild/internal/logger" "github.com/evanw/esbuild/internal/resolver" ) func parseMangleCache(osArgs []string, fs fs.FS, absPath string) (map[string]interface{}, []string) { // Log problems with the mangle cache to stderr log := logger.NewStderrLog(logger.OutputOptionsForArgs(osArgs)) defer log.Done() // Try to read the existing file prettyPath := absPath if rel, ok := fs.Rel(fs.Cwd(), absPath); ok { prettyPath = rel } prettyPath = strings.ReplaceAll(prettyPath, "\\", "/") bytes, err, originalError := fs.ReadFile(absPath) if err != nil { // It's ok if it's just missing if err == syscall.ENOENT { return make(map[string]interface{}), []string{} } // Otherwise, report the error log.AddError(nil, logger.Range{}, fmt.Sprintf("Failed to read from mangle cache file %q: %s", prettyPath, originalError.Error())) return nil, nil } // Use our JSON parser so we get pretty-printed error messages keyPath := logger.Path{Text: absPath, Namespace: "file"} source := logger.Source{ KeyPath: keyPath, PrettyPaths: resolver.MakePrettyPaths(fs, keyPath), Contents: string(bytes), } result, ok := js_parser.ParseJSON(log, source, js_parser.JSONOptions{}) if !ok || log.HasErrors() { // Stop if there were any errors so we don't continue and then overwrite this file return nil, nil } tracker := logger.MakeLineColumnTracker(&source) // Validate the top-level object root, ok := result.Data.(*js_ast.EObject) if !ok { log.AddError(&tracker, logger.Range{Loc: result.Loc}, "Expected a top-level object in mangle cache file") return nil, nil } mangleCache := make(map[string]interface{}, len(root.Properties)) order := make([]string, 0, len(root.Properties)) for _, property := range root.Properties { key := helpers.UTF16ToString(property.Key.Data.(*js_ast.EString).Value) order = append(order, key) switch v := property.ValueOrNil.Data.(type) { case *js_ast.EBoolean: if v.Value { log.AddError(&tracker, js_lexer.RangeOfIdentifier(source, property.ValueOrNil.Loc), fmt.Sprintf("Expected %q in mangle cache file to map to either a string or false", key)) } else { mangleCache[key] = false } case *js_ast.EString: mangleCache[key] = helpers.UTF16ToString(v.Value) default: log.AddError(&tracker, logger.Range{Loc: property.ValueOrNil.Loc}, fmt.Sprintf("Expected %q in mangle cache file to map to either a string or false", key)) } } if log.HasErrors() { return nil, nil } return mangleCache, order } func printMangleCache(mangleCache map[string]interface{}, originalOrder []string, asciiOnly bool) []byte { j := helpers.Joiner{} j.AddString("{") // Determine the order to print the keys in order := originalOrder if len(mangleCache) > len(order) { order = make([]string, 0, len(mangleCache)) if sort.StringsAreSorted(originalOrder) { // If they came sorted, keep them sorted for key := range mangleCache { order = append(order, key) } sort.Strings(order) } else { // Otherwise add all new keys to the end, and only sort the new keys originalKeys := make(map[string]bool, len(originalOrder)) for _, key := range originalOrder { originalKeys[key] = true } order = append(order, originalOrder...) for key := range mangleCache { if !originalKeys[key] { order = append(order, key) } } sort.Strings(order[len(originalOrder):]) } } // Print the JSON while preserving the existing order of the keys for i, key := range order { // Print the key if i > 0 { j.AddString(",\n ") } else { j.AddString("\n ") } j.AddBytes(helpers.QuoteForJSON(key, asciiOnly)) // Print the value if value := mangleCache[key]; value != false { j.AddString(": ") j.AddBytes(helpers.QuoteForJSON(value.(string), asciiOnly)) } else { j.AddString(": false") } } if len(order) > 0 { j.AddString("\n") } j.AddString("}\n") return j.Done() }