package js_parser import ( "fmt" "github.com/evanw/esbuild/internal/compat" "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/logger" ) type jsonParser struct { log logger.Log source logger.Source tracker logger.LineColumnTracker lexer js_lexer.Lexer options JSONOptions suppressWarningsAboutWeirdCode bool } func (p *jsonParser) parseMaybeTrailingComma(closeToken js_lexer.T) bool { commaRange := p.lexer.Range() p.lexer.Expect(js_lexer.TComma) if p.lexer.Token == closeToken { if p.options.Flavor == js_lexer.JSON { p.log.AddError(&p.tracker, commaRange, "JSON does not support trailing commas") } return false } return true } func (p *jsonParser) parseExpr() js_ast.Expr { loc := p.lexer.Loc() switch p.lexer.Token { case js_lexer.TFalse: p.lexer.Next() return js_ast.Expr{Loc: loc, Data: &js_ast.EBoolean{Value: false}} case js_lexer.TTrue: p.lexer.Next() return js_ast.Expr{Loc: loc, Data: &js_ast.EBoolean{Value: true}} case js_lexer.TNull: p.lexer.Next() return js_ast.Expr{Loc: loc, Data: js_ast.ENullShared} case js_lexer.TStringLiteral: value := p.lexer.StringLiteral() p.lexer.Next() return js_ast.Expr{Loc: loc, Data: &js_ast.EString{Value: value}} case js_lexer.TNumericLiteral: value := p.lexer.Number p.lexer.Next() return js_ast.Expr{Loc: loc, Data: &js_ast.ENumber{Value: value}} case js_lexer.TMinus: p.lexer.Next() value := p.lexer.Number p.lexer.Expect(js_lexer.TNumericLiteral) return js_ast.Expr{Loc: loc, Data: &js_ast.ENumber{Value: -value}} case js_lexer.TOpenBracket: p.lexer.Next() isSingleLine := !p.lexer.HasNewlineBefore items := []js_ast.Expr{} for p.lexer.Token != js_lexer.TCloseBracket { if len(items) > 0 { if p.lexer.HasNewlineBefore { isSingleLine = false } if !p.parseMaybeTrailingComma(js_lexer.TCloseBracket) { break } if p.lexer.HasNewlineBefore { isSingleLine = false } } item := p.parseExpr() items = append(items, item) } if p.lexer.HasNewlineBefore { isSingleLine = false } closeBracketLoc := p.lexer.Loc() p.lexer.Expect(js_lexer.TCloseBracket) return js_ast.Expr{Loc: loc, Data: &js_ast.EArray{ Items: items, IsSingleLine: isSingleLine, CloseBracketLoc: closeBracketLoc, }} case js_lexer.TOpenBrace: p.lexer.Next() isSingleLine := !p.lexer.HasNewlineBefore properties := []js_ast.Property{} duplicates := make(map[string]logger.Range) for p.lexer.Token != js_lexer.TCloseBrace { if len(properties) > 0 { if p.lexer.HasNewlineBefore { isSingleLine = false } if !p.parseMaybeTrailingComma(js_lexer.TCloseBrace) { break } if p.lexer.HasNewlineBefore { isSingleLine = false } } keyString := p.lexer.StringLiteral() keyRange := p.lexer.Range() key := js_ast.Expr{Loc: keyRange.Loc, Data: &js_ast.EString{Value: keyString}} p.lexer.Expect(js_lexer.TStringLiteral) // Warn about duplicate keys if !p.suppressWarningsAboutWeirdCode { keyText := helpers.UTF16ToString(keyString) if prevRange, ok := duplicates[keyText]; ok { p.log.AddIDWithNotes(logger.MsgID_JS_DuplicateObjectKey, logger.Warning, &p.tracker, keyRange, fmt.Sprintf("Duplicate key %q in object literal", keyText), []logger.MsgData{p.tracker.MsgData(prevRange, fmt.Sprintf("The original key %q is here:", keyText))}) } else { duplicates[keyText] = keyRange } } p.lexer.Expect(js_lexer.TColon) value := p.parseExpr() property := js_ast.Property{ Kind: js_ast.PropertyField, Loc: keyRange.Loc, Key: key, ValueOrNil: value, } // The key "__proto__" must not be a string literal in JavaScript because // that actually modifies the prototype of the object. This can be // avoided by using a computed property key instead of a string literal. if helpers.UTF16EqualsString(keyString, "__proto__") && !p.options.UnsupportedJSFeatures.Has(compat.ObjectExtensions) { property.Flags |= js_ast.PropertyIsComputed } properties = append(properties, property) } if p.lexer.HasNewlineBefore { isSingleLine = false } closeBraceLoc := p.lexer.Loc() p.lexer.Expect(js_lexer.TCloseBrace) return js_ast.Expr{Loc: loc, Data: &js_ast.EObject{ Properties: properties, IsSingleLine: isSingleLine, CloseBraceLoc: closeBraceLoc, }} case js_lexer.TBigIntegerLiteral: if !p.options.IsForDefine { p.lexer.Unexpected() } value := p.lexer.Identifier p.lexer.Next() return js_ast.Expr{Loc: loc, Data: &js_ast.EBigInt{Value: value.String}} default: p.lexer.Unexpected() return js_ast.Expr{} } } type JSONOptions struct { UnsupportedJSFeatures compat.JSFeature Flavor js_lexer.JSONFlavor ErrorSuffix string IsForDefine bool } func ParseJSON(log logger.Log, source logger.Source, options JSONOptions) (result js_ast.Expr, ok bool) { ok = true defer func() { r := recover() if _, isLexerPanic := r.(js_lexer.LexerPanic); isLexerPanic { ok = false } else if r != nil { panic(r) } }() if options.ErrorSuffix == "" { options.ErrorSuffix = " in JSON" } p := &jsonParser{ log: log, source: source, tracker: logger.MakeLineColumnTracker(&source), options: options, lexer: js_lexer.NewLexerJSON(log, source, options.Flavor, options.ErrorSuffix), suppressWarningsAboutWeirdCode: helpers.IsInsideNodeModules(source.KeyPath.Text), } result = p.parseExpr() p.lexer.Expect(js_lexer.TEndOfFile) return } func isValidJSON(value js_ast.Expr) bool { switch e := value.Data.(type) { case *js_ast.ENull, *js_ast.EBoolean, *js_ast.EString, *js_ast.ENumber: return true case *js_ast.EArray: for _, item := range e.Items { if !isValidJSON(item) { return false } } return true case *js_ast.EObject: for _, property := range e.Properties { if property.Kind != js_ast.PropertyField || property.Flags.Has(js_ast.PropertyIsComputed) { return false } if _, ok := property.Key.Data.(*js_ast.EString); !ok { return false } if !isValidJSON(property.ValueOrNil) { return false } } return true } return false }