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 }