github.com/hashicorp/hcl/v2@v2.20.0/json/parser_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package json 5 6 import ( 7 "math/big" 8 "testing" 9 10 "github.com/go-test/deep" 11 "github.com/hashicorp/hcl/v2" 12 ) 13 14 func init() { 15 deep.MaxDepth = 999 16 } 17 18 func TestParse(t *testing.T) { 19 tests := []struct { 20 Input string 21 Want node 22 DiagCount int 23 }{ 24 // Simple, single-token constructs 25 { 26 `true`, 27 &booleanVal{ 28 Value: true, 29 SrcRange: hcl.Range{ 30 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 31 End: hcl.Pos{Line: 1, Column: 5, Byte: 4}, 32 }, 33 }, 34 0, 35 }, 36 { 37 `false`, 38 &booleanVal{ 39 Value: false, 40 SrcRange: hcl.Range{ 41 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 42 End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 43 }, 44 }, 45 0, 46 }, 47 { 48 `null`, 49 &nullVal{ 50 SrcRange: hcl.Range{ 51 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 52 End: hcl.Pos{Line: 1, Column: 5, Byte: 4}, 53 }, 54 }, 55 0, 56 }, 57 { 58 `undefined`, 59 invalidVal{hcl.Range{ 60 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 61 End: hcl.Pos{Line: 1, Column: 10, Byte: 9}, 62 }}, 63 1, 64 }, 65 { 66 `flase`, 67 invalidVal{hcl.Range{ 68 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 69 End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 70 }}, 71 1, 72 }, 73 { 74 `"hello"`, 75 &stringVal{ 76 Value: "hello", 77 SrcRange: hcl.Range{ 78 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 79 End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, 80 }, 81 }, 82 0, 83 }, 84 { 85 `"hello\nworld"`, 86 &stringVal{ 87 Value: "hello\nworld", 88 SrcRange: hcl.Range{ 89 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 90 End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, 91 }, 92 }, 93 0, 94 }, 95 { 96 `"hello \"world\""`, 97 &stringVal{ 98 Value: `hello "world"`, 99 SrcRange: hcl.Range{ 100 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 101 End: hcl.Pos{Line: 1, Column: 18, Byte: 17}, 102 }, 103 }, 104 0, 105 }, 106 { 107 `"hello \\"`, 108 &stringVal{ 109 Value: "hello \\", 110 SrcRange: hcl.Range{ 111 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 112 End: hcl.Pos{Line: 1, Column: 11, Byte: 10}, 113 }, 114 }, 115 0, 116 }, 117 { 118 `"hello`, 119 invalidVal{hcl.Range{ 120 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 121 End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, 122 }}, 123 1, 124 }, 125 { 126 `"he\llo"`, 127 invalidVal{hcl.Range{ 128 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 129 End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, 130 }}, 131 1, 132 }, 133 { 134 `1`, 135 &numberVal{ 136 Value: mustBigFloat("1"), 137 SrcRange: hcl.Range{ 138 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 139 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 140 }, 141 }, 142 0, 143 }, 144 { 145 `1.2`, 146 &numberVal{ 147 Value: mustBigFloat("1.2"), 148 SrcRange: hcl.Range{ 149 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 150 End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, 151 }, 152 }, 153 0, 154 }, 155 { 156 `-1`, 157 &numberVal{ 158 Value: mustBigFloat("-1"), 159 SrcRange: hcl.Range{ 160 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 161 End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, 162 }, 163 }, 164 0, 165 }, 166 { 167 `1.2e5`, 168 &numberVal{ 169 Value: mustBigFloat("120000"), 170 SrcRange: hcl.Range{ 171 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 172 End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 173 }, 174 }, 175 0, 176 }, 177 { 178 `1.2e+5`, 179 &numberVal{ 180 Value: mustBigFloat("120000"), 181 SrcRange: hcl.Range{ 182 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 183 End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, 184 }, 185 }, 186 0, 187 }, 188 { 189 `1.2e-5`, 190 &numberVal{ 191 Value: mustBigFloat("1.2e-5"), 192 SrcRange: hcl.Range{ 193 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 194 End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, 195 }, 196 }, 197 0, 198 }, 199 { 200 `.1`, 201 invalidVal{hcl.Range{ 202 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 203 End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, 204 }}, 205 1, 206 }, 207 { 208 `+2`, 209 invalidVal{hcl.Range{ 210 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 211 End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, 212 }}, 213 1, 214 }, 215 { 216 `1 2`, 217 &numberVal{ 218 Value: mustBigFloat("1"), 219 SrcRange: hcl.Range{ 220 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 221 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 222 }, 223 }, 224 1, 225 }, 226 227 // Objects 228 { 229 `{"hello": true}`, 230 &objectVal{ 231 Attrs: []*objectAttr{ 232 { 233 Name: "hello", 234 Value: &booleanVal{ 235 Value: true, 236 SrcRange: hcl.Range{ 237 Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, 238 End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, 239 }, 240 }, 241 NameRange: hcl.Range{ 242 Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 243 End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, 244 }, 245 }, 246 }, 247 SrcRange: hcl.Range{ 248 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 249 End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, 250 }, 251 OpenRange: hcl.Range{ 252 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 253 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 254 }, 255 CloseRange: hcl.Range{ 256 Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, 257 End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, 258 }, 259 }, 260 0, 261 }, 262 { 263 `{"hello": true, "bye": false}`, 264 &objectVal{ 265 Attrs: []*objectAttr{ 266 { 267 Name: "hello", 268 Value: &booleanVal{ 269 Value: true, 270 SrcRange: hcl.Range{ 271 Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, 272 End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, 273 }, 274 }, 275 NameRange: hcl.Range{ 276 Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 277 End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, 278 }, 279 }, 280 { 281 Name: "bye", 282 Value: &booleanVal{ 283 Value: false, 284 SrcRange: hcl.Range{ 285 Start: hcl.Pos{Line: 1, Column: 24, Byte: 23}, 286 End: hcl.Pos{Line: 1, Column: 29, Byte: 28}, 287 }, 288 }, 289 NameRange: hcl.Range{ 290 Start: hcl.Pos{Line: 1, Column: 17, Byte: 16}, 291 End: hcl.Pos{Line: 1, Column: 22, Byte: 21}, 292 }, 293 }, 294 }, 295 SrcRange: hcl.Range{ 296 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 297 End: hcl.Pos{Line: 1, Column: 30, Byte: 29}, 298 }, 299 OpenRange: hcl.Range{ 300 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 301 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 302 }, 303 CloseRange: hcl.Range{ 304 Start: hcl.Pos{Line: 1, Column: 29, Byte: 28}, 305 End: hcl.Pos{Line: 1, Column: 30, Byte: 29}, 306 }, 307 }, 308 0, 309 }, 310 { 311 `{}`, 312 &objectVal{ 313 Attrs: []*objectAttr{}, 314 SrcRange: hcl.Range{ 315 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 316 End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, 317 }, 318 OpenRange: hcl.Range{ 319 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 320 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 321 }, 322 CloseRange: hcl.Range{ 323 Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 324 End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, 325 }, 326 }, 327 0, 328 }, 329 { 330 `{"hello":true`, 331 invalidVal{hcl.Range{ 332 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 333 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 334 }}, 335 1, 336 }, 337 { 338 `{"hello":true]`, 339 invalidVal{hcl.Range{ 340 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 341 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 342 }}, 343 1, 344 }, 345 { 346 `{"hello":true,}`, 347 invalidVal{hcl.Range{ 348 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 349 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 350 }}, 351 1, 352 }, 353 { 354 `{true:false}`, 355 invalidVal{hcl.Range{ 356 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 357 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 358 }}, 359 1, 360 }, 361 { 362 `{"hello": true, "hello": true}`, 363 &objectVal{ 364 Attrs: []*objectAttr{ 365 { 366 Name: "hello", 367 Value: &booleanVal{ 368 Value: true, 369 SrcRange: hcl.Range{ 370 Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, 371 End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, 372 }, 373 }, 374 NameRange: hcl.Range{ 375 Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 376 End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, 377 }, 378 }, 379 { 380 Name: "hello", 381 Value: &booleanVal{ 382 Value: true, 383 SrcRange: hcl.Range{ 384 Start: hcl.Pos{Line: 1, Column: 26, Byte: 25}, 385 End: hcl.Pos{Line: 1, Column: 30, Byte: 29}, 386 }, 387 }, 388 NameRange: hcl.Range{ 389 Start: hcl.Pos{Line: 1, Column: 17, Byte: 16}, 390 End: hcl.Pos{Line: 1, Column: 24, Byte: 23}, 391 }, 392 }, 393 }, 394 SrcRange: hcl.Range{ 395 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 396 End: hcl.Pos{Line: 1, Column: 31, Byte: 30}, 397 }, 398 OpenRange: hcl.Range{ 399 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 400 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 401 }, 402 CloseRange: hcl.Range{ 403 Start: hcl.Pos{Line: 1, Column: 30, Byte: 29}, 404 End: hcl.Pos{Line: 1, Column: 31, Byte: 30}, 405 }, 406 }, 407 0, 408 }, 409 { 410 `{"hello": true, "hello": true, "hello", true}`, 411 invalidVal{hcl.Range{ 412 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 413 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 414 }}, 415 1, // comma used where colon is expected 416 }, 417 { 418 `{"hello", "world"}`, 419 invalidVal{hcl.Range{ 420 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 421 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 422 }}, 423 1, 424 }, 425 { 426 `[]`, 427 &arrayVal{ 428 Values: []node{}, 429 SrcRange: hcl.Range{ 430 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 431 End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, 432 }, 433 OpenRange: hcl.Range{ 434 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 435 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 436 }, 437 }, 438 0, 439 }, 440 { 441 `[true]`, 442 &arrayVal{ 443 Values: []node{ 444 &booleanVal{ 445 Value: true, 446 SrcRange: hcl.Range{ 447 Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 448 End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 449 }, 450 }, 451 }, 452 SrcRange: hcl.Range{ 453 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 454 End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, 455 }, 456 OpenRange: hcl.Range{ 457 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 458 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 459 }, 460 }, 461 0, 462 }, 463 { 464 `[true, false]`, 465 &arrayVal{ 466 Values: []node{ 467 &booleanVal{ 468 Value: true, 469 SrcRange: hcl.Range{ 470 Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 471 End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 472 }, 473 }, 474 &booleanVal{ 475 Value: false, 476 SrcRange: hcl.Range{ 477 Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, 478 End: hcl.Pos{Line: 1, Column: 13, Byte: 12}, 479 }, 480 }, 481 }, 482 SrcRange: hcl.Range{ 483 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 484 End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, 485 }, 486 OpenRange: hcl.Range{ 487 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 488 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 489 }, 490 }, 491 0, 492 }, 493 { 494 `[[]]`, 495 &arrayVal{ 496 Values: []node{ 497 &arrayVal{ 498 Values: []node{}, 499 SrcRange: hcl.Range{ 500 Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 501 End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, 502 }, 503 OpenRange: hcl.Range{ 504 Start: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 505 End: hcl.Pos{Line: 1, Column: 3, Byte: 2}, 506 }, 507 }, 508 }, 509 SrcRange: hcl.Range{ 510 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 511 End: hcl.Pos{Line: 1, Column: 5, Byte: 4}, 512 }, 513 OpenRange: hcl.Range{ 514 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 515 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 516 }, 517 }, 518 0, 519 }, 520 { 521 `[`, 522 invalidVal{hcl.Range{ 523 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 524 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 525 }}, 526 2, 527 }, 528 { 529 `[true`, 530 invalidVal{hcl.Range{ 531 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 532 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 533 }}, 534 1, 535 }, 536 { 537 `]`, 538 invalidVal{hcl.Range{ 539 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 540 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 541 }}, 542 1, 543 }, 544 { 545 `[true,]`, 546 invalidVal{hcl.Range{ 547 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 548 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 549 }}, 550 1, 551 }, 552 { 553 `[[],]`, 554 invalidVal{hcl.Range{ 555 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 556 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 557 }}, 558 1, 559 }, 560 { 561 `["hello":true]`, 562 invalidVal{hcl.Range{ 563 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 564 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 565 }}, 566 1, 567 }, 568 { 569 `[true}`, 570 invalidVal{hcl.Range{ 571 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 572 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 573 }}, 574 1, 575 }, 576 { 577 `{"wrong"=true}`, 578 invalidVal{hcl.Range{ 579 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 580 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 581 }}, 582 1, 583 }, 584 { 585 `{"wrong" = true}`, 586 invalidVal{hcl.Range{ 587 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 588 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 589 }}, 590 1, 591 }, 592 { 593 `{"wrong" true}`, 594 invalidVal{hcl.Range{ 595 Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, 596 End: hcl.Pos{Line: 1, Column: 2, Byte: 1}, 597 }}, 598 1, 599 }, 600 } 601 602 for _, test := range tests { 603 t.Run(test.Input, func(t *testing.T) { 604 got, diag := parseFileContent([]byte(test.Input), "", hcl.Pos{Byte: 0, Line: 1, Column: 1}) 605 606 if len(diag) != test.DiagCount { 607 t.Errorf("got %d diagnostics; want %d", len(diag), test.DiagCount) 608 for _, d := range diag { 609 t.Logf(" - %s", d.Error()) 610 } 611 } 612 613 if diff := deep.Equal(got, test.Want); diff != nil { 614 for _, problem := range diff { 615 t.Error(problem) 616 } 617 } 618 }) 619 } 620 } 621 622 func TestParseWithPos(t *testing.T) { 623 tests := []struct { 624 Input string 625 StartPos hcl.Pos 626 Want node 627 DiagCount int 628 }{ 629 // Simple, single-token constructs 630 { 631 `true`, 632 hcl.Pos{Byte: 0, Line: 3, Column: 10}, 633 &booleanVal{ 634 Value: true, 635 SrcRange: hcl.Range{ 636 Start: hcl.Pos{Line: 3, Column: 10, Byte: 0}, 637 End: hcl.Pos{Line: 3, Column: 14, Byte: 4}, 638 }, 639 }, 640 0, 641 }, 642 } 643 644 for _, test := range tests { 645 t.Run(test.Input, func(t *testing.T) { 646 got, diag := parseFileContent([]byte(test.Input), "", test.StartPos) 647 648 if len(diag) != test.DiagCount { 649 t.Errorf("got %d diagnostics; want %d", len(diag), test.DiagCount) 650 for _, d := range diag { 651 t.Logf(" - %s", d.Error()) 652 } 653 } 654 655 if diff := deep.Equal(got, test.Want); diff != nil { 656 for _, problem := range diff { 657 t.Error(problem) 658 } 659 } 660 }) 661 } 662 } 663 664 func mustBigFloat(s string) *big.Float { 665 f, _, err := (&big.Float{}).Parse(s, 10) 666 if err != nil { 667 panic(err) 668 } 669 return f 670 }