github.com/evanw/esbuild@v0.21.4/internal/js_parser/json_parser.go (about)

     1  package js_parser
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/evanw/esbuild/internal/compat"
     7  	"github.com/evanw/esbuild/internal/helpers"
     8  	"github.com/evanw/esbuild/internal/js_ast"
     9  	"github.com/evanw/esbuild/internal/js_lexer"
    10  	"github.com/evanw/esbuild/internal/logger"
    11  )
    12  
    13  type jsonParser struct {
    14  	log                            logger.Log
    15  	source                         logger.Source
    16  	tracker                        logger.LineColumnTracker
    17  	lexer                          js_lexer.Lexer
    18  	options                        JSONOptions
    19  	suppressWarningsAboutWeirdCode bool
    20  }
    21  
    22  func (p *jsonParser) parseMaybeTrailingComma(closeToken js_lexer.T) bool {
    23  	commaRange := p.lexer.Range()
    24  	p.lexer.Expect(js_lexer.TComma)
    25  
    26  	if p.lexer.Token == closeToken {
    27  		if p.options.Flavor == js_lexer.JSON {
    28  			p.log.AddError(&p.tracker, commaRange, "JSON does not support trailing commas")
    29  		}
    30  		return false
    31  	}
    32  
    33  	return true
    34  }
    35  
    36  func (p *jsonParser) parseExpr() js_ast.Expr {
    37  	loc := p.lexer.Loc()
    38  
    39  	switch p.lexer.Token {
    40  	case js_lexer.TFalse:
    41  		p.lexer.Next()
    42  		return js_ast.Expr{Loc: loc, Data: &js_ast.EBoolean{Value: false}}
    43  
    44  	case js_lexer.TTrue:
    45  		p.lexer.Next()
    46  		return js_ast.Expr{Loc: loc, Data: &js_ast.EBoolean{Value: true}}
    47  
    48  	case js_lexer.TNull:
    49  		p.lexer.Next()
    50  		return js_ast.Expr{Loc: loc, Data: js_ast.ENullShared}
    51  
    52  	case js_lexer.TStringLiteral:
    53  		value := p.lexer.StringLiteral()
    54  		p.lexer.Next()
    55  		return js_ast.Expr{Loc: loc, Data: &js_ast.EString{Value: value}}
    56  
    57  	case js_lexer.TNumericLiteral:
    58  		value := p.lexer.Number
    59  		p.lexer.Next()
    60  		return js_ast.Expr{Loc: loc, Data: &js_ast.ENumber{Value: value}}
    61  
    62  	case js_lexer.TMinus:
    63  		p.lexer.Next()
    64  		value := p.lexer.Number
    65  		p.lexer.Expect(js_lexer.TNumericLiteral)
    66  		return js_ast.Expr{Loc: loc, Data: &js_ast.ENumber{Value: -value}}
    67  
    68  	case js_lexer.TOpenBracket:
    69  		p.lexer.Next()
    70  		isSingleLine := !p.lexer.HasNewlineBefore
    71  		items := []js_ast.Expr{}
    72  
    73  		for p.lexer.Token != js_lexer.TCloseBracket {
    74  			if len(items) > 0 {
    75  				if p.lexer.HasNewlineBefore {
    76  					isSingleLine = false
    77  				}
    78  				if !p.parseMaybeTrailingComma(js_lexer.TCloseBracket) {
    79  					break
    80  				}
    81  				if p.lexer.HasNewlineBefore {
    82  					isSingleLine = false
    83  				}
    84  			}
    85  
    86  			item := p.parseExpr()
    87  			items = append(items, item)
    88  		}
    89  
    90  		if p.lexer.HasNewlineBefore {
    91  			isSingleLine = false
    92  		}
    93  		closeBracketLoc := p.lexer.Loc()
    94  		p.lexer.Expect(js_lexer.TCloseBracket)
    95  		return js_ast.Expr{Loc: loc, Data: &js_ast.EArray{
    96  			Items:           items,
    97  			IsSingleLine:    isSingleLine,
    98  			CloseBracketLoc: closeBracketLoc,
    99  		}}
   100  
   101  	case js_lexer.TOpenBrace:
   102  		p.lexer.Next()
   103  		isSingleLine := !p.lexer.HasNewlineBefore
   104  		properties := []js_ast.Property{}
   105  		duplicates := make(map[string]logger.Range)
   106  
   107  		for p.lexer.Token != js_lexer.TCloseBrace {
   108  			if len(properties) > 0 {
   109  				if p.lexer.HasNewlineBefore {
   110  					isSingleLine = false
   111  				}
   112  				if !p.parseMaybeTrailingComma(js_lexer.TCloseBrace) {
   113  					break
   114  				}
   115  				if p.lexer.HasNewlineBefore {
   116  					isSingleLine = false
   117  				}
   118  			}
   119  
   120  			keyString := p.lexer.StringLiteral()
   121  			keyRange := p.lexer.Range()
   122  			key := js_ast.Expr{Loc: keyRange.Loc, Data: &js_ast.EString{Value: keyString}}
   123  			p.lexer.Expect(js_lexer.TStringLiteral)
   124  
   125  			// Warn about duplicate keys
   126  			if !p.suppressWarningsAboutWeirdCode {
   127  				keyText := helpers.UTF16ToString(keyString)
   128  				if prevRange, ok := duplicates[keyText]; ok {
   129  					p.log.AddIDWithNotes(logger.MsgID_JS_DuplicateObjectKey, logger.Warning, &p.tracker, keyRange,
   130  						fmt.Sprintf("Duplicate key %q in object literal", keyText),
   131  						[]logger.MsgData{p.tracker.MsgData(prevRange, fmt.Sprintf("The original key %q is here:", keyText))})
   132  				} else {
   133  					duplicates[keyText] = keyRange
   134  				}
   135  			}
   136  
   137  			p.lexer.Expect(js_lexer.TColon)
   138  			value := p.parseExpr()
   139  
   140  			property := js_ast.Property{
   141  				Kind:       js_ast.PropertyField,
   142  				Loc:        keyRange.Loc,
   143  				Key:        key,
   144  				ValueOrNil: value,
   145  			}
   146  
   147  			// The key "__proto__" must not be a string literal in JavaScript because
   148  			// that actually modifies the prototype of the object. This can be
   149  			// avoided by using a computed property key instead of a string literal.
   150  			if helpers.UTF16EqualsString(keyString, "__proto__") && !p.options.UnsupportedJSFeatures.Has(compat.ObjectExtensions) {
   151  				property.Flags |= js_ast.PropertyIsComputed
   152  			}
   153  
   154  			properties = append(properties, property)
   155  		}
   156  
   157  		if p.lexer.HasNewlineBefore {
   158  			isSingleLine = false
   159  		}
   160  		closeBraceLoc := p.lexer.Loc()
   161  		p.lexer.Expect(js_lexer.TCloseBrace)
   162  		return js_ast.Expr{Loc: loc, Data: &js_ast.EObject{
   163  			Properties:    properties,
   164  			IsSingleLine:  isSingleLine,
   165  			CloseBraceLoc: closeBraceLoc,
   166  		}}
   167  
   168  	default:
   169  		p.lexer.Unexpected()
   170  		return js_ast.Expr{}
   171  	}
   172  }
   173  
   174  type JSONOptions struct {
   175  	UnsupportedJSFeatures compat.JSFeature
   176  	Flavor                js_lexer.JSONFlavor
   177  	ErrorSuffix           string
   178  }
   179  
   180  func ParseJSON(log logger.Log, source logger.Source, options JSONOptions) (result js_ast.Expr, ok bool) {
   181  	ok = true
   182  	defer func() {
   183  		r := recover()
   184  		if _, isLexerPanic := r.(js_lexer.LexerPanic); isLexerPanic {
   185  			ok = false
   186  		} else if r != nil {
   187  			panic(r)
   188  		}
   189  	}()
   190  
   191  	if options.ErrorSuffix == "" {
   192  		options.ErrorSuffix = " in JSON"
   193  	}
   194  
   195  	p := &jsonParser{
   196  		log:                            log,
   197  		source:                         source,
   198  		tracker:                        logger.MakeLineColumnTracker(&source),
   199  		options:                        options,
   200  		lexer:                          js_lexer.NewLexerJSON(log, source, options.Flavor, options.ErrorSuffix),
   201  		suppressWarningsAboutWeirdCode: helpers.IsInsideNodeModules(source.KeyPath.Text),
   202  	}
   203  
   204  	result = p.parseExpr()
   205  	p.lexer.Expect(js_lexer.TEndOfFile)
   206  	return
   207  }
   208  
   209  func isValidJSON(value js_ast.Expr) bool {
   210  	switch e := value.Data.(type) {
   211  	case *js_ast.ENull, *js_ast.EBoolean, *js_ast.EString, *js_ast.ENumber:
   212  		return true
   213  
   214  	case *js_ast.EArray:
   215  		for _, item := range e.Items {
   216  			if !isValidJSON(item) {
   217  				return false
   218  			}
   219  		}
   220  		return true
   221  
   222  	case *js_ast.EObject:
   223  		for _, property := range e.Properties {
   224  			if property.Kind != js_ast.PropertyField || property.Flags.Has(js_ast.PropertyIsComputed) {
   225  				return false
   226  			}
   227  			if _, ok := property.Key.Data.(*js_ast.EString); !ok {
   228  				return false
   229  			}
   230  			if !isValidJSON(property.ValueOrNil) {
   231  				return false
   232  			}
   233  		}
   234  		return true
   235  	}
   236  
   237  	return false
   238  }