github.com/evanw/esbuild@v0.21.4/internal/js_parser/ts_parser.go (about) 1 // This file contains code for parsing TypeScript syntax. The parser just skips 2 // over type expressions as if they are whitespace and doesn't bother generating 3 // an AST because nothing uses type information. 4 5 package js_parser 6 7 import ( 8 "fmt" 9 "strings" 10 11 "github.com/evanw/esbuild/internal/ast" 12 "github.com/evanw/esbuild/internal/compat" 13 "github.com/evanw/esbuild/internal/helpers" 14 "github.com/evanw/esbuild/internal/js_ast" 15 "github.com/evanw/esbuild/internal/js_lexer" 16 "github.com/evanw/esbuild/internal/logger" 17 ) 18 19 func (p *parser) skipTypeScriptBinding() { 20 switch p.lexer.Token { 21 case js_lexer.TIdentifier, js_lexer.TThis: 22 p.lexer.Next() 23 24 case js_lexer.TOpenBracket: 25 p.lexer.Next() 26 27 // "[, , a]" 28 for p.lexer.Token == js_lexer.TComma { 29 p.lexer.Next() 30 } 31 32 // "[a, b]" 33 for p.lexer.Token != js_lexer.TCloseBracket { 34 // "[...a]" 35 if p.lexer.Token == js_lexer.TDotDotDot { 36 p.lexer.Next() 37 } 38 39 p.skipTypeScriptBinding() 40 if p.lexer.Token != js_lexer.TComma { 41 break 42 } 43 p.lexer.Next() 44 } 45 46 p.lexer.Expect(js_lexer.TCloseBracket) 47 48 case js_lexer.TOpenBrace: 49 p.lexer.Next() 50 51 for p.lexer.Token != js_lexer.TCloseBrace { 52 foundIdentifier := false 53 54 switch p.lexer.Token { 55 case js_lexer.TDotDotDot: 56 p.lexer.Next() 57 58 if p.lexer.Token != js_lexer.TIdentifier { 59 p.lexer.Unexpected() 60 } 61 62 // "{...x}" 63 foundIdentifier = true 64 p.lexer.Next() 65 66 case js_lexer.TIdentifier: 67 // "{x}" 68 // "{x: y}" 69 foundIdentifier = true 70 p.lexer.Next() 71 72 // "{1: y}" 73 // "{'x': y}" 74 case js_lexer.TStringLiteral, js_lexer.TNumericLiteral: 75 p.lexer.Next() 76 77 default: 78 if p.lexer.IsIdentifierOrKeyword() { 79 // "{if: x}" 80 p.lexer.Next() 81 } else { 82 p.lexer.Unexpected() 83 } 84 } 85 86 if p.lexer.Token == js_lexer.TColon || !foundIdentifier { 87 p.lexer.Expect(js_lexer.TColon) 88 p.skipTypeScriptBinding() 89 } 90 91 if p.lexer.Token != js_lexer.TComma { 92 break 93 } 94 p.lexer.Next() 95 } 96 97 p.lexer.Expect(js_lexer.TCloseBrace) 98 99 default: 100 p.lexer.Unexpected() 101 } 102 } 103 104 func (p *parser) skipTypeScriptFnArgs() { 105 p.lexer.Expect(js_lexer.TOpenParen) 106 107 for p.lexer.Token != js_lexer.TCloseParen { 108 // "(...a)" 109 if p.lexer.Token == js_lexer.TDotDotDot { 110 p.lexer.Next() 111 } 112 113 p.skipTypeScriptBinding() 114 115 // "(a?)" 116 if p.lexer.Token == js_lexer.TQuestion { 117 p.lexer.Next() 118 } 119 120 // "(a: any)" 121 if p.lexer.Token == js_lexer.TColon { 122 p.lexer.Next() 123 p.skipTypeScriptType(js_ast.LLowest) 124 } 125 126 // "(a, b)" 127 if p.lexer.Token != js_lexer.TComma { 128 break 129 } 130 p.lexer.Next() 131 } 132 133 p.lexer.Expect(js_lexer.TCloseParen) 134 } 135 136 // This is a spot where the TypeScript grammar is highly ambiguous. Here are 137 // some cases that are valid: 138 // 139 // let x = (y: any): (() => {}) => { }; 140 // let x = (y: any): () => {} => { }; 141 // let x = (y: any): (y) => {} => { }; 142 // let x = (y: any): (y[]) => {}; 143 // let x = (y: any): (a | b) => {}; 144 // 145 // Here are some cases that aren't valid: 146 // 147 // let x = (y: any): (y) => {}; 148 // let x = (y: any): (y) => {return 0}; 149 // let x = (y: any): asserts y is (y) => {}; 150 func (p *parser) skipTypeScriptParenOrFnType() { 151 if p.trySkipTypeScriptArrowArgsWithBacktracking() { 152 p.skipTypeScriptReturnType() 153 } else { 154 p.lexer.Expect(js_lexer.TOpenParen) 155 p.skipTypeScriptType(js_ast.LLowest) 156 p.lexer.Expect(js_lexer.TCloseParen) 157 } 158 } 159 160 func (p *parser) skipTypeScriptReturnType() { 161 p.skipTypeScriptTypeWithFlags(js_ast.LLowest, isReturnTypeFlag) 162 } 163 164 func (p *parser) skipTypeScriptType(level js_ast.L) { 165 p.skipTypeScriptTypeWithFlags(level, 0) 166 } 167 168 type skipTypeFlags uint8 169 170 const ( 171 isReturnTypeFlag skipTypeFlags = 1 << iota 172 isIndexSignatureFlag 173 allowTupleLabelsFlag 174 disallowConditionalTypesFlag 175 ) 176 177 func (flags skipTypeFlags) has(flag skipTypeFlags) bool { 178 return (flags & flag) != 0 179 } 180 181 type tsTypeIdentifierKind uint8 182 183 const ( 184 tsTypeIdentifierNormal tsTypeIdentifierKind = iota 185 tsTypeIdentifierUnique 186 tsTypeIdentifierAbstract 187 tsTypeIdentifierAsserts 188 tsTypeIdentifierPrefix 189 tsTypeIdentifierPrimitive 190 tsTypeIdentifierInfer 191 ) 192 193 // Use a map to improve lookup speed 194 var tsTypeIdentifierMap = map[string]tsTypeIdentifierKind{ 195 "unique": tsTypeIdentifierUnique, 196 "abstract": tsTypeIdentifierAbstract, 197 "asserts": tsTypeIdentifierAsserts, 198 199 "keyof": tsTypeIdentifierPrefix, 200 "readonly": tsTypeIdentifierPrefix, 201 202 "any": tsTypeIdentifierPrimitive, 203 "never": tsTypeIdentifierPrimitive, 204 "unknown": tsTypeIdentifierPrimitive, 205 "undefined": tsTypeIdentifierPrimitive, 206 "object": tsTypeIdentifierPrimitive, 207 "number": tsTypeIdentifierPrimitive, 208 "string": tsTypeIdentifierPrimitive, 209 "boolean": tsTypeIdentifierPrimitive, 210 "bigint": tsTypeIdentifierPrimitive, 211 "symbol": tsTypeIdentifierPrimitive, 212 213 "infer": tsTypeIdentifierInfer, 214 } 215 216 func (p *parser) skipTypeScriptTypeWithFlags(level js_ast.L, flags skipTypeFlags) { 217 loop: 218 for { 219 switch p.lexer.Token { 220 case js_lexer.TNumericLiteral, js_lexer.TBigIntegerLiteral, js_lexer.TStringLiteral, 221 js_lexer.TNoSubstitutionTemplateLiteral, js_lexer.TTrue, js_lexer.TFalse, 222 js_lexer.TNull, js_lexer.TVoid: 223 p.lexer.Next() 224 225 case js_lexer.TConst: 226 r := p.lexer.Range() 227 p.lexer.Next() 228 229 // "[const: number]" 230 if flags.has(allowTupleLabelsFlag) && p.lexer.Token == js_lexer.TColon { 231 p.log.AddError(&p.tracker, r, "Unexpected \"const\"") 232 } 233 234 case js_lexer.TThis: 235 p.lexer.Next() 236 237 // "function check(): this is boolean" 238 if p.lexer.IsContextualKeyword("is") && !p.lexer.HasNewlineBefore { 239 p.lexer.Next() 240 p.skipTypeScriptType(js_ast.LLowest) 241 return 242 } 243 244 case js_lexer.TMinus: 245 // "-123" 246 // "-123n" 247 p.lexer.Next() 248 if p.lexer.Token == js_lexer.TBigIntegerLiteral { 249 p.lexer.Next() 250 } else { 251 p.lexer.Expect(js_lexer.TNumericLiteral) 252 } 253 254 case js_lexer.TAmpersand: 255 case js_lexer.TBar: 256 // Support things like "type Foo = | A | B" and "type Foo = & A & B" 257 p.lexer.Next() 258 continue 259 260 case js_lexer.TImport: 261 // "import('fs')" 262 p.lexer.Next() 263 264 // "[import: number]" 265 if flags.has(allowTupleLabelsFlag) && p.lexer.Token == js_lexer.TColon { 266 return 267 } 268 269 p.lexer.Expect(js_lexer.TOpenParen) 270 p.lexer.Expect(js_lexer.TStringLiteral) 271 272 // "import('./foo.json', { assert: { type: 'json' } })" 273 if p.lexer.Token == js_lexer.TComma { 274 p.lexer.Next() 275 p.skipTypeScriptObjectType() 276 277 // "import('./foo.json', { assert: { type: 'json' } }, )" 278 if p.lexer.Token == js_lexer.TComma { 279 p.lexer.Next() 280 } 281 } 282 283 p.lexer.Expect(js_lexer.TCloseParen) 284 285 case js_lexer.TNew: 286 // "new () => Foo" 287 // "new <T>() => Foo<T>" 288 p.lexer.Next() 289 290 // "[new: number]" 291 if flags.has(allowTupleLabelsFlag) && p.lexer.Token == js_lexer.TColon { 292 return 293 } 294 295 p.skipTypeScriptTypeParameters(allowConstModifier) 296 p.skipTypeScriptParenOrFnType() 297 298 case js_lexer.TLessThan: 299 // "<T>() => Foo<T>" 300 p.skipTypeScriptTypeParameters(allowConstModifier) 301 p.skipTypeScriptParenOrFnType() 302 303 case js_lexer.TOpenParen: 304 // "(number | string)" 305 p.skipTypeScriptParenOrFnType() 306 307 case js_lexer.TIdentifier: 308 kind := tsTypeIdentifierMap[p.lexer.Identifier.String] 309 checkTypeParameters := true 310 311 switch kind { 312 case tsTypeIdentifierPrefix: 313 p.lexer.Next() 314 315 // Valid: 316 // "[keyof: string]" 317 // "{[keyof: string]: number}" 318 // "{[keyof in string]: number}" 319 // 320 // Invalid: 321 // "A extends B ? keyof : string" 322 // 323 if (p.lexer.Token != js_lexer.TColon && p.lexer.Token != js_lexer.TIn) || (!flags.has(isIndexSignatureFlag) && !flags.has(allowTupleLabelsFlag)) { 324 p.skipTypeScriptType(js_ast.LPrefix) 325 } 326 break loop 327 328 case tsTypeIdentifierInfer: 329 p.lexer.Next() 330 331 // "type Foo = Bar extends [infer T] ? T : null" 332 // "type Foo = Bar extends [infer T extends string] ? T : null" 333 // "type Foo = Bar extends [infer T extends string ? infer T : never] ? T : null" 334 // "type Foo = { [infer in Bar]: number }" 335 if (p.lexer.Token != js_lexer.TColon && p.lexer.Token != js_lexer.TIn) || (!flags.has(isIndexSignatureFlag) && !flags.has(allowTupleLabelsFlag)) { 336 p.lexer.Expect(js_lexer.TIdentifier) 337 if p.lexer.Token == js_lexer.TExtends { 338 p.trySkipTypeScriptConstraintOfInferTypeWithBacktracking(flags) 339 } 340 } 341 break loop 342 343 case tsTypeIdentifierUnique: 344 p.lexer.Next() 345 346 // "let foo: unique symbol" 347 if p.lexer.IsContextualKeyword("symbol") { 348 p.lexer.Next() 349 break loop 350 } 351 352 case tsTypeIdentifierAbstract: 353 p.lexer.Next() 354 355 // "let foo: abstract new () => {}" added in TypeScript 4.2 356 if p.lexer.Token == js_lexer.TNew { 357 continue 358 } 359 360 case tsTypeIdentifierAsserts: 361 p.lexer.Next() 362 363 // "function assert(x: boolean): asserts x" 364 // "function assert(x: boolean): asserts x is boolean" 365 if flags.has(isReturnTypeFlag) && !p.lexer.HasNewlineBefore && (p.lexer.Token == js_lexer.TIdentifier || p.lexer.Token == js_lexer.TThis) { 366 p.lexer.Next() 367 } 368 369 case tsTypeIdentifierPrimitive: 370 p.lexer.Next() 371 checkTypeParameters = false 372 373 default: 374 p.lexer.Next() 375 } 376 377 // "function assert(x: any): x is boolean" 378 if p.lexer.IsContextualKeyword("is") && !p.lexer.HasNewlineBefore { 379 p.lexer.Next() 380 p.skipTypeScriptType(js_ast.LLowest) 381 return 382 } 383 384 // "let foo: any \n <number>foo" must not become a single type 385 if checkTypeParameters && !p.lexer.HasNewlineBefore { 386 p.skipTypeScriptTypeArguments(skipTypeScriptTypeArgumentsOpts{}) 387 } 388 389 case js_lexer.TTypeof: 390 p.lexer.Next() 391 392 // "[typeof: number]" 393 if flags.has(allowTupleLabelsFlag) && p.lexer.Token == js_lexer.TColon { 394 return 395 } 396 397 if p.lexer.Token == js_lexer.TImport { 398 // "typeof import('fs')" 399 continue 400 } else { 401 // "typeof x" 402 if !p.lexer.IsIdentifierOrKeyword() { 403 p.lexer.Expected(js_lexer.TIdentifier) 404 } 405 p.lexer.Next() 406 407 // "typeof x.y" 408 // "typeof x.#y" 409 for p.lexer.Token == js_lexer.TDot { 410 p.lexer.Next() 411 if !p.lexer.IsIdentifierOrKeyword() && p.lexer.Token != js_lexer.TPrivateIdentifier { 412 p.lexer.Expected(js_lexer.TIdentifier) 413 } 414 p.lexer.Next() 415 } 416 417 if !p.lexer.HasNewlineBefore { 418 p.skipTypeScriptTypeArguments(skipTypeScriptTypeArgumentsOpts{}) 419 } 420 } 421 422 case js_lexer.TOpenBracket: 423 // "[number, string]" 424 // "[first: number, second: string]" 425 p.lexer.Next() 426 for p.lexer.Token != js_lexer.TCloseBracket { 427 if p.lexer.Token == js_lexer.TDotDotDot { 428 p.lexer.Next() 429 } 430 p.skipTypeScriptTypeWithFlags(js_ast.LLowest, allowTupleLabelsFlag) 431 if p.lexer.Token == js_lexer.TQuestion { 432 p.lexer.Next() 433 } 434 if p.lexer.Token == js_lexer.TColon { 435 p.lexer.Next() 436 p.skipTypeScriptType(js_ast.LLowest) 437 } 438 if p.lexer.Token != js_lexer.TComma { 439 break 440 } 441 p.lexer.Next() 442 } 443 p.lexer.Expect(js_lexer.TCloseBracket) 444 445 case js_lexer.TOpenBrace: 446 p.skipTypeScriptObjectType() 447 448 case js_lexer.TTemplateHead: 449 // "`${'a' | 'b'}-${'c' | 'd'}`" 450 for { 451 p.lexer.Next() 452 p.skipTypeScriptType(js_ast.LLowest) 453 p.lexer.RescanCloseBraceAsTemplateToken() 454 if p.lexer.Token == js_lexer.TTemplateTail { 455 p.lexer.Next() 456 break 457 } 458 } 459 460 default: 461 // "[function: number]" 462 if flags.has(allowTupleLabelsFlag) && p.lexer.IsIdentifierOrKeyword() { 463 if p.lexer.Token != js_lexer.TFunction { 464 p.log.AddError(&p.tracker, p.lexer.Range(), fmt.Sprintf("Unexpected %q", p.lexer.Raw())) 465 } 466 p.lexer.Next() 467 if p.lexer.Token != js_lexer.TColon { 468 p.lexer.Expect(js_lexer.TColon) 469 } 470 return 471 } 472 473 p.lexer.Unexpected() 474 } 475 break 476 } 477 478 for { 479 switch p.lexer.Token { 480 case js_lexer.TBar: 481 if level >= js_ast.LBitwiseOr { 482 return 483 } 484 p.lexer.Next() 485 p.skipTypeScriptTypeWithFlags(js_ast.LBitwiseOr, flags) 486 487 case js_lexer.TAmpersand: 488 if level >= js_ast.LBitwiseAnd { 489 return 490 } 491 p.lexer.Next() 492 p.skipTypeScriptTypeWithFlags(js_ast.LBitwiseAnd, flags) 493 494 case js_lexer.TExclamation: 495 // A postfix "!" is allowed in JSDoc types in TypeScript, which are only 496 // present in comments. While it's not valid in a non-comment position, 497 // it's still parsed and turned into a soft error by the TypeScript 498 // compiler. It turns out parsing this is important for correctness for 499 // "as" casts because the "!" token must still be consumed. 500 if p.lexer.HasNewlineBefore { 501 return 502 } 503 p.lexer.Next() 504 505 case js_lexer.TDot: 506 p.lexer.Next() 507 if !p.lexer.IsIdentifierOrKeyword() { 508 p.lexer.Expect(js_lexer.TIdentifier) 509 } 510 p.lexer.Next() 511 512 // "{ <A extends B>(): c.d \n <E extends F>(): g.h }" must not become a single type 513 if !p.lexer.HasNewlineBefore { 514 p.skipTypeScriptTypeArguments(skipTypeScriptTypeArgumentsOpts{}) 515 } 516 517 case js_lexer.TOpenBracket: 518 // "{ ['x']: string \n ['y']: string }" must not become a single type 519 if p.lexer.HasNewlineBefore { 520 return 521 } 522 p.lexer.Next() 523 if p.lexer.Token != js_lexer.TCloseBracket { 524 p.skipTypeScriptType(js_ast.LLowest) 525 } 526 p.lexer.Expect(js_lexer.TCloseBracket) 527 528 case js_lexer.TExtends: 529 // "{ x: number \n extends: boolean }" must not become a single type 530 if p.lexer.HasNewlineBefore || flags.has(disallowConditionalTypesFlag) { 531 return 532 } 533 p.lexer.Next() 534 535 // The type following "extends" is not permitted to be another conditional type 536 p.skipTypeScriptTypeWithFlags(js_ast.LLowest, disallowConditionalTypesFlag) 537 p.lexer.Expect(js_lexer.TQuestion) 538 p.skipTypeScriptType(js_ast.LLowest) 539 p.lexer.Expect(js_lexer.TColon) 540 p.skipTypeScriptType(js_ast.LLowest) 541 542 default: 543 return 544 } 545 } 546 } 547 548 func (p *parser) skipTypeScriptObjectType() { 549 p.lexer.Expect(js_lexer.TOpenBrace) 550 551 for p.lexer.Token != js_lexer.TCloseBrace { 552 // "{ -readonly [K in keyof T]: T[K] }" 553 // "{ +readonly [K in keyof T]: T[K] }" 554 if p.lexer.Token == js_lexer.TPlus || p.lexer.Token == js_lexer.TMinus { 555 p.lexer.Next() 556 } 557 558 // Skip over modifiers and the property identifier 559 foundKey := false 560 for p.lexer.IsIdentifierOrKeyword() || 561 p.lexer.Token == js_lexer.TStringLiteral || 562 p.lexer.Token == js_lexer.TNumericLiteral { 563 p.lexer.Next() 564 foundKey = true 565 } 566 567 if p.lexer.Token == js_lexer.TOpenBracket { 568 // Index signature or computed property 569 p.lexer.Next() 570 p.skipTypeScriptTypeWithFlags(js_ast.LLowest, isIndexSignatureFlag) 571 572 // "{ [key: string]: number }" 573 // "{ readonly [K in keyof T]: T[K] }" 574 if p.lexer.Token == js_lexer.TColon { 575 p.lexer.Next() 576 p.skipTypeScriptType(js_ast.LLowest) 577 } else if p.lexer.Token == js_lexer.TIn { 578 p.lexer.Next() 579 p.skipTypeScriptType(js_ast.LLowest) 580 if p.lexer.IsContextualKeyword("as") { 581 // "{ [K in keyof T as `get-${K}`]: T[K] }" 582 p.lexer.Next() 583 p.skipTypeScriptType(js_ast.LLowest) 584 } 585 } 586 587 p.lexer.Expect(js_lexer.TCloseBracket) 588 589 // "{ [K in keyof T]+?: T[K] }" 590 // "{ [K in keyof T]-?: T[K] }" 591 if p.lexer.Token == js_lexer.TPlus || p.lexer.Token == js_lexer.TMinus { 592 p.lexer.Next() 593 } 594 595 foundKey = true 596 } 597 598 // "?" indicates an optional property 599 // "!" indicates an initialization assertion 600 if foundKey && (p.lexer.Token == js_lexer.TQuestion || p.lexer.Token == js_lexer.TExclamation) { 601 p.lexer.Next() 602 } 603 604 // Type parameters come right after the optional mark 605 p.skipTypeScriptTypeParameters(allowConstModifier) 606 607 switch p.lexer.Token { 608 case js_lexer.TColon: 609 // Regular property 610 if !foundKey { 611 p.lexer.Expect(js_lexer.TIdentifier) 612 } 613 p.lexer.Next() 614 p.skipTypeScriptType(js_ast.LLowest) 615 616 case js_lexer.TOpenParen: 617 // Method signature 618 p.skipTypeScriptFnArgs() 619 if p.lexer.Token == js_lexer.TColon { 620 p.lexer.Next() 621 p.skipTypeScriptReturnType() 622 } 623 624 default: 625 if !foundKey { 626 p.lexer.Unexpected() 627 } 628 } 629 630 switch p.lexer.Token { 631 case js_lexer.TCloseBrace: 632 633 case js_lexer.TComma, js_lexer.TSemicolon: 634 p.lexer.Next() 635 636 default: 637 if !p.lexer.HasNewlineBefore { 638 p.lexer.Unexpected() 639 } 640 } 641 } 642 643 p.lexer.Expect(js_lexer.TCloseBrace) 644 } 645 646 type typeParameterFlags uint8 647 648 const ( 649 // TypeScript 4.7 650 allowInOutVarianceAnnotations typeParameterFlags = 1 << iota 651 652 // TypeScript 5.0 653 allowConstModifier 654 655 // Allow "<>" without any type parameters 656 allowEmptyTypeParameters 657 ) 658 659 type skipTypeScriptTypeParametersResult uint8 660 661 const ( 662 didNotSkipAnything skipTypeScriptTypeParametersResult = iota 663 couldBeTypeCast 664 definitelyTypeParameters 665 ) 666 667 // This is the type parameter declarations that go with other symbol 668 // declarations (class, function, type, etc.) 669 func (p *parser) skipTypeScriptTypeParameters(flags typeParameterFlags) skipTypeScriptTypeParametersResult { 670 if p.lexer.Token != js_lexer.TLessThan { 671 return didNotSkipAnything 672 } 673 674 p.lexer.Next() 675 result := couldBeTypeCast 676 677 if (flags&allowEmptyTypeParameters) != 0 && p.lexer.Token == js_lexer.TGreaterThan { 678 p.lexer.Next() 679 return definitelyTypeParameters 680 } 681 682 for { 683 hasIn := false 684 hasOut := false 685 expectIdentifier := true 686 invalidModifierRange := logger.Range{} 687 688 // Scan over a sequence of "in" and "out" modifiers (a.k.a. optional 689 // variance annotations) as well as "const" modifiers 690 for { 691 if p.lexer.Token == js_lexer.TConst { 692 if invalidModifierRange.Len == 0 && (flags&allowConstModifier) == 0 { 693 // Valid: 694 // "class Foo<const T> {}" 695 // Invalid: 696 // "interface Foo<const T> {}" 697 invalidModifierRange = p.lexer.Range() 698 } 699 result = definitelyTypeParameters 700 p.lexer.Next() 701 expectIdentifier = true 702 continue 703 } 704 705 if p.lexer.Token == js_lexer.TIn { 706 if invalidModifierRange.Len == 0 && ((flags&allowInOutVarianceAnnotations) == 0 || hasIn || hasOut) { 707 // Valid: 708 // "type Foo<in T> = T" 709 // Invalid: 710 // "type Foo<in in T> = T" 711 // "type Foo<out in T> = T" 712 invalidModifierRange = p.lexer.Range() 713 } 714 p.lexer.Next() 715 hasIn = true 716 expectIdentifier = true 717 continue 718 } 719 720 if p.lexer.IsContextualKeyword("out") { 721 r := p.lexer.Range() 722 if invalidModifierRange.Len == 0 && (flags&allowInOutVarianceAnnotations) == 0 { 723 invalidModifierRange = r 724 } 725 p.lexer.Next() 726 if invalidModifierRange.Len == 0 && hasOut && (p.lexer.Token == js_lexer.TIn || p.lexer.Token == js_lexer.TIdentifier) { 727 // Valid: 728 // "type Foo<out T> = T" 729 // "type Foo<out out> = T" 730 // "type Foo<out out, T> = T" 731 // "type Foo<out out = T> = T" 732 // "type Foo<out out extends T> = T" 733 // Invalid: 734 // "type Foo<out out in T> = T" 735 // "type Foo<out out T> = T" 736 invalidModifierRange = r 737 } 738 hasOut = true 739 expectIdentifier = false 740 continue 741 } 742 743 break 744 } 745 746 // Only report an error for the first invalid modifier 747 if invalidModifierRange.Len > 0 { 748 p.log.AddError(&p.tracker, invalidModifierRange, fmt.Sprintf( 749 "The modifier %q is not valid here:", p.source.TextForRange(invalidModifierRange))) 750 } 751 752 // expectIdentifier => Mandatory identifier (e.g. after "type Foo <in ___") 753 // !expectIdentifier => Optional identifier (e.g. after "type Foo <out ___" since "out" may be the identifier) 754 if expectIdentifier || p.lexer.Token == js_lexer.TIdentifier { 755 p.lexer.Expect(js_lexer.TIdentifier) 756 } 757 758 // "class Foo<T extends number> {}" 759 if p.lexer.Token == js_lexer.TExtends { 760 result = definitelyTypeParameters 761 p.lexer.Next() 762 p.skipTypeScriptType(js_ast.LLowest) 763 } 764 765 // "class Foo<T = void> {}" 766 if p.lexer.Token == js_lexer.TEquals { 767 result = definitelyTypeParameters 768 p.lexer.Next() 769 p.skipTypeScriptType(js_ast.LLowest) 770 } 771 772 if p.lexer.Token != js_lexer.TComma { 773 break 774 } 775 p.lexer.Next() 776 if p.lexer.Token == js_lexer.TGreaterThan { 777 result = definitelyTypeParameters 778 break 779 } 780 } 781 782 p.lexer.ExpectGreaterThan(false /* isInsideJSXElement */) 783 return result 784 } 785 786 type skipTypeScriptTypeArgumentsOpts struct { 787 isInsideJSXElement bool 788 isParseTypeArgumentsInExpression bool 789 } 790 791 func (p *parser) skipTypeScriptTypeArguments(opts skipTypeScriptTypeArgumentsOpts) bool { 792 switch p.lexer.Token { 793 case js_lexer.TLessThan, js_lexer.TLessThanEquals, 794 js_lexer.TLessThanLessThan, js_lexer.TLessThanLessThanEquals: 795 default: 796 return false 797 } 798 799 p.lexer.ExpectLessThan(false /* isInsideJSXElement */) 800 801 for { 802 p.skipTypeScriptType(js_ast.LLowest) 803 if p.lexer.Token != js_lexer.TComma { 804 break 805 } 806 p.lexer.Next() 807 } 808 809 // This type argument list must end with a ">" 810 if !opts.isParseTypeArgumentsInExpression { 811 // Normally TypeScript allows any token starting with ">". For example, 812 // "Array<Array<number>>()" is a type argument list even though there's a 813 // ">>" token, because ">>" starts with ">". 814 p.lexer.ExpectGreaterThan(opts.isInsideJSXElement) 815 } else { 816 // However, if we're emulating the TypeScript compiler's function called 817 // "parseTypeArgumentsInExpression" function, then we must only allow the 818 // ">" token itself. For example, "x < y >= z" is not a type argument list. 819 // 820 // This doesn't detect ">>" in "Array<Array<number>>()" because the inner 821 // type argument list isn't a call to "parseTypeArgumentsInExpression" 822 // because it's within a type context, not an expression context. So the 823 // token that we see here is ">" in that case because the first ">" has 824 // already been stripped off of the ">>" by the inner call. 825 if opts.isInsideJSXElement { 826 p.lexer.ExpectInsideJSXElement(js_lexer.TGreaterThan) 827 } else { 828 p.lexer.Expect(js_lexer.TGreaterThan) 829 } 830 } 831 return true 832 } 833 834 func (p *parser) trySkipTypeArgumentsInExpressionWithBacktracking() bool { 835 oldLexer := p.lexer 836 p.lexer.IsLogDisabled = true 837 838 // Implement backtracking by restoring the lexer's memory to its original state 839 defer func() { 840 r := recover() 841 if _, isLexerPanic := r.(js_lexer.LexerPanic); isLexerPanic { 842 p.lexer = oldLexer 843 } else if r != nil { 844 panic(r) 845 } 846 }() 847 848 if p.skipTypeScriptTypeArguments(skipTypeScriptTypeArgumentsOpts{isParseTypeArgumentsInExpression: true}) { 849 // Check the token after the type argument list and backtrack if it's invalid 850 if !p.tsCanFollowTypeArgumentsInExpression() { 851 p.lexer.Unexpected() 852 } 853 } 854 855 // Restore the log disabled flag. Note that we can't just set it back to false 856 // because it may have been true to start with. 857 p.lexer.IsLogDisabled = oldLexer.IsLogDisabled 858 return true 859 } 860 861 func (p *parser) trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking() skipTypeScriptTypeParametersResult { 862 oldLexer := p.lexer 863 p.lexer.IsLogDisabled = true 864 865 // Implement backtracking by restoring the lexer's memory to its original state 866 defer func() { 867 r := recover() 868 if _, isLexerPanic := r.(js_lexer.LexerPanic); isLexerPanic { 869 p.lexer = oldLexer 870 } else if r != nil { 871 panic(r) 872 } 873 }() 874 875 result := p.skipTypeScriptTypeParameters(allowConstModifier) 876 if p.lexer.Token != js_lexer.TOpenParen { 877 p.lexer.Unexpected() 878 } 879 880 // Restore the log disabled flag. Note that we can't just set it back to false 881 // because it may have been true to start with. 882 p.lexer.IsLogDisabled = oldLexer.IsLogDisabled 883 return result 884 } 885 886 func (p *parser) trySkipTypeScriptArrowReturnTypeWithBacktracking() bool { 887 oldLexer := p.lexer 888 p.lexer.IsLogDisabled = true 889 890 // Implement backtracking by restoring the lexer's memory to its original state 891 defer func() { 892 r := recover() 893 if _, isLexerPanic := r.(js_lexer.LexerPanic); isLexerPanic { 894 p.lexer = oldLexer 895 } else if r != nil { 896 panic(r) 897 } 898 }() 899 900 p.lexer.Expect(js_lexer.TColon) 901 p.skipTypeScriptReturnType() 902 903 // Check the token after this and backtrack if it's the wrong one 904 if p.lexer.Token != js_lexer.TEqualsGreaterThan { 905 p.lexer.Unexpected() 906 } 907 908 // Restore the log disabled flag. Note that we can't just set it back to false 909 // because it may have been true to start with. 910 p.lexer.IsLogDisabled = oldLexer.IsLogDisabled 911 return true 912 } 913 914 func (p *parser) trySkipTypeScriptArrowArgsWithBacktracking() bool { 915 oldLexer := p.lexer 916 p.lexer.IsLogDisabled = true 917 918 // Implement backtracking by restoring the lexer's memory to its original state 919 defer func() { 920 r := recover() 921 if _, isLexerPanic := r.(js_lexer.LexerPanic); isLexerPanic { 922 p.lexer = oldLexer 923 } else if r != nil { 924 panic(r) 925 } 926 }() 927 928 p.skipTypeScriptFnArgs() 929 p.lexer.Expect(js_lexer.TEqualsGreaterThan) 930 931 // Restore the log disabled flag. Note that we can't just set it back to false 932 // because it may have been true to start with. 933 p.lexer.IsLogDisabled = oldLexer.IsLogDisabled 934 return true 935 } 936 937 func (p *parser) trySkipTypeScriptConstraintOfInferTypeWithBacktracking(flags skipTypeFlags) bool { 938 oldLexer := p.lexer 939 p.lexer.IsLogDisabled = true 940 941 // Implement backtracking by restoring the lexer's memory to its original state 942 defer func() { 943 r := recover() 944 if _, isLexerPanic := r.(js_lexer.LexerPanic); isLexerPanic { 945 p.lexer = oldLexer 946 } else if r != nil { 947 panic(r) 948 } 949 }() 950 951 p.lexer.Expect(js_lexer.TExtends) 952 p.skipTypeScriptTypeWithFlags(js_ast.LPrefix, disallowConditionalTypesFlag) 953 if !flags.has(disallowConditionalTypesFlag) && p.lexer.Token == js_lexer.TQuestion { 954 p.lexer.Unexpected() 955 } 956 957 // Restore the log disabled flag. Note that we can't just set it back to false 958 // because it may have been true to start with. 959 p.lexer.IsLogDisabled = oldLexer.IsLogDisabled 960 return true 961 } 962 963 // Returns true if the current less-than token is considered to be an arrow 964 // function under TypeScript's rules for files containing JSX syntax 965 func (p *parser) isTSArrowFnJSX() (isTSArrowFn bool) { 966 oldLexer := p.lexer 967 p.lexer.Next() 968 969 // Look ahead to see if this should be an arrow function instead 970 if p.lexer.Token == js_lexer.TConst { 971 p.lexer.Next() 972 } 973 if p.lexer.Token == js_lexer.TIdentifier { 974 p.lexer.Next() 975 if p.lexer.Token == js_lexer.TComma || p.lexer.Token == js_lexer.TEquals { 976 isTSArrowFn = true 977 } else if p.lexer.Token == js_lexer.TExtends { 978 p.lexer.Next() 979 isTSArrowFn = p.lexer.Token != js_lexer.TEquals && p.lexer.Token != js_lexer.TGreaterThan && p.lexer.Token != js_lexer.TSlash 980 } 981 } 982 983 // Restore the lexer 984 p.lexer = oldLexer 985 return 986 } 987 988 // This function is taken from the official TypeScript compiler source code: 989 // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts 990 // 991 // This function is pretty inefficient as written, and could be collapsed into 992 // a single switch statement. But that would make it harder to keep this in 993 // sync with the TypeScript compiler's source code, so we keep doing it the 994 // slow way. 995 func (p *parser) tsCanFollowTypeArgumentsInExpression() bool { 996 switch p.lexer.Token { 997 case 998 // These tokens can follow a type argument list in a call expression. 999 js_lexer.TOpenParen, // foo<x>( 1000 js_lexer.TNoSubstitutionTemplateLiteral, // foo<T> `...` 1001 js_lexer.TTemplateHead: // foo<T> `...${100}...` 1002 return true 1003 1004 // A type argument list followed by `<` never makes sense, and a type argument list followed 1005 // by `>` is ambiguous with a (re-scanned) `>>` operator, so we disqualify both. Also, in 1006 // this context, `+` and `-` are unary operators, not binary operators. 1007 case js_lexer.TLessThan, 1008 js_lexer.TGreaterThan, 1009 js_lexer.TPlus, 1010 js_lexer.TMinus, 1011 // TypeScript always sees "TGreaterThan" instead of these tokens since 1012 // their scanner works a little differently than our lexer. So since 1013 // "TGreaterThan" is forbidden above, we also forbid these too. 1014 js_lexer.TGreaterThanEquals, 1015 js_lexer.TGreaterThanGreaterThan, 1016 js_lexer.TGreaterThanGreaterThanEquals, 1017 js_lexer.TGreaterThanGreaterThanGreaterThan, 1018 js_lexer.TGreaterThanGreaterThanGreaterThanEquals: 1019 return false 1020 } 1021 1022 // We favor the type argument list interpretation when it is immediately followed by 1023 // a line break, a binary operator, or something that can't start an expression. 1024 return p.lexer.HasNewlineBefore || p.tsIsBinaryOperator() || !p.tsIsStartOfExpression() 1025 } 1026 1027 // This function is taken from the official TypeScript compiler source code: 1028 // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts 1029 func (p *parser) tsIsBinaryOperator() bool { 1030 switch p.lexer.Token { 1031 case js_lexer.TIn: 1032 return p.allowIn 1033 1034 case 1035 js_lexer.TQuestionQuestion, 1036 js_lexer.TBarBar, 1037 js_lexer.TAmpersandAmpersand, 1038 js_lexer.TBar, 1039 js_lexer.TCaret, 1040 js_lexer.TAmpersand, 1041 js_lexer.TEqualsEquals, 1042 js_lexer.TExclamationEquals, 1043 js_lexer.TEqualsEqualsEquals, 1044 js_lexer.TExclamationEqualsEquals, 1045 js_lexer.TLessThan, 1046 js_lexer.TGreaterThan, 1047 js_lexer.TLessThanEquals, 1048 js_lexer.TGreaterThanEquals, 1049 js_lexer.TInstanceof, 1050 js_lexer.TLessThanLessThan, 1051 js_lexer.TGreaterThanGreaterThan, 1052 js_lexer.TGreaterThanGreaterThanGreaterThan, 1053 js_lexer.TPlus, 1054 js_lexer.TMinus, 1055 js_lexer.TAsterisk, 1056 js_lexer.TSlash, 1057 js_lexer.TPercent, 1058 js_lexer.TAsteriskAsterisk: 1059 return true 1060 1061 case js_lexer.TIdentifier: 1062 if p.lexer.IsContextualKeyword("as") || p.lexer.IsContextualKeyword("satisfies") { 1063 return true 1064 } 1065 } 1066 1067 return false 1068 } 1069 1070 // This function is taken from the official TypeScript compiler source code: 1071 // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts 1072 func (p *parser) tsIsStartOfExpression() bool { 1073 if p.tsIsStartOfLeftHandSideExpression() { 1074 return true 1075 } 1076 1077 switch p.lexer.Token { 1078 case 1079 js_lexer.TPlus, 1080 js_lexer.TMinus, 1081 js_lexer.TTilde, 1082 js_lexer.TExclamation, 1083 js_lexer.TDelete, 1084 js_lexer.TTypeof, 1085 js_lexer.TVoid, 1086 js_lexer.TPlusPlus, 1087 js_lexer.TMinusMinus, 1088 js_lexer.TLessThan, 1089 js_lexer.TPrivateIdentifier, 1090 js_lexer.TAt: 1091 return true 1092 1093 default: 1094 if p.lexer.Token == js_lexer.TIdentifier && (p.lexer.Identifier.String == "await" || p.lexer.Identifier.String == "yield") { 1095 // Yield/await always starts an expression. Either it is an identifier (in which case 1096 // it is definitely an expression). Or it's a keyword (either because we're in 1097 // a generator or async function, or in strict mode (or both)) and it started a yield or await expression. 1098 return true 1099 } 1100 1101 // Error tolerance. If we see the start of some binary operator, we consider 1102 // that the start of an expression. That way we'll parse out a missing identifier, 1103 // give a good message about an identifier being missing, and then consume the 1104 // rest of the binary expression. 1105 if p.tsIsBinaryOperator() { 1106 return true 1107 } 1108 1109 return p.tsIsIdentifier() 1110 } 1111 } 1112 1113 // This function is taken from the official TypeScript compiler source code: 1114 // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts 1115 func (p *parser) tsIsStartOfLeftHandSideExpression() bool { 1116 switch p.lexer.Token { 1117 case 1118 js_lexer.TThis, 1119 js_lexer.TSuper, 1120 js_lexer.TNull, 1121 js_lexer.TTrue, 1122 js_lexer.TFalse, 1123 js_lexer.TNumericLiteral, 1124 js_lexer.TBigIntegerLiteral, 1125 js_lexer.TStringLiteral, 1126 js_lexer.TNoSubstitutionTemplateLiteral, 1127 js_lexer.TTemplateHead, 1128 js_lexer.TOpenParen, 1129 js_lexer.TOpenBracket, 1130 js_lexer.TOpenBrace, 1131 js_lexer.TFunction, 1132 js_lexer.TClass, 1133 js_lexer.TNew, 1134 js_lexer.TSlash, 1135 js_lexer.TSlashEquals, 1136 js_lexer.TIdentifier: 1137 return true 1138 1139 case js_lexer.TImport: 1140 return p.tsLookAheadNextTokenIsOpenParenOrLessThanOrDot() 1141 1142 default: 1143 return p.tsIsIdentifier() 1144 } 1145 } 1146 1147 // This function is taken from the official TypeScript compiler source code: 1148 // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts 1149 func (p *parser) tsLookAheadNextTokenIsOpenParenOrLessThanOrDot() (result bool) { 1150 oldLexer := p.lexer 1151 p.lexer.Next() 1152 1153 result = p.lexer.Token == js_lexer.TOpenParen || 1154 p.lexer.Token == js_lexer.TLessThan || 1155 p.lexer.Token == js_lexer.TDot 1156 1157 // Restore the lexer 1158 p.lexer = oldLexer 1159 return 1160 } 1161 1162 // This function is taken from the official TypeScript compiler source code: 1163 // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts 1164 func (p *parser) tsIsIdentifier() bool { 1165 if p.lexer.Token == js_lexer.TIdentifier { 1166 // If we have a 'yield' keyword, and we're in the [yield] context, then 'yield' is 1167 // considered a keyword and is not an identifier. 1168 if p.fnOrArrowDataParse.yield != allowIdent && p.lexer.Identifier.String == "yield" { 1169 return false 1170 } 1171 1172 // If we have a 'await' keyword, and we're in the [Await] context, then 'await' is 1173 // considered a keyword and is not an identifier. 1174 if p.fnOrArrowDataParse.await != allowIdent && p.lexer.Identifier.String == "await" { 1175 return false 1176 } 1177 1178 return true 1179 } 1180 1181 return false 1182 } 1183 1184 func (p *parser) skipTypeScriptInterfaceStmt(opts parseStmtOpts) { 1185 name := p.lexer.Identifier.String 1186 p.lexer.Expect(js_lexer.TIdentifier) 1187 1188 if opts.isModuleScope { 1189 p.localTypeNames[name] = true 1190 } 1191 1192 p.skipTypeScriptTypeParameters(allowInOutVarianceAnnotations | allowEmptyTypeParameters) 1193 1194 if p.lexer.Token == js_lexer.TExtends { 1195 p.lexer.Next() 1196 for { 1197 p.skipTypeScriptType(js_ast.LLowest) 1198 if p.lexer.Token != js_lexer.TComma { 1199 break 1200 } 1201 p.lexer.Next() 1202 } 1203 } 1204 1205 if p.lexer.IsContextualKeyword("implements") { 1206 p.lexer.Next() 1207 for { 1208 p.skipTypeScriptType(js_ast.LLowest) 1209 if p.lexer.Token != js_lexer.TComma { 1210 break 1211 } 1212 p.lexer.Next() 1213 } 1214 } 1215 1216 p.skipTypeScriptObjectType() 1217 } 1218 1219 func (p *parser) skipTypeScriptTypeStmt(opts parseStmtOpts) { 1220 if opts.isExport { 1221 switch p.lexer.Token { 1222 case js_lexer.TOpenBrace: 1223 // "export type {foo}" 1224 // "export type {foo} from 'bar'" 1225 p.parseExportClause() 1226 if p.lexer.IsContextualKeyword("from") { 1227 p.lexer.Next() 1228 p.parsePath() 1229 } 1230 p.lexer.ExpectOrInsertSemicolon() 1231 return 1232 1233 // This is invalid TypeScript, and is rejected by the TypeScript compiler: 1234 // 1235 // example.ts:1:1 - error TS1383: Only named exports may use 'export type'. 1236 // 1237 // 1 export type * from './types' 1238 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1239 // 1240 // However, people may not know this and then blame esbuild for it not 1241 // working. So we parse it anyway and then discard it (since we always 1242 // discard all types). People who do this should be running the TypeScript 1243 // type checker when using TypeScript, which will then report this error. 1244 case js_lexer.TAsterisk: 1245 // "export type * from 'path'" 1246 p.lexer.Next() 1247 if p.lexer.IsContextualKeyword("as") { 1248 // "export type * as ns from 'path'" 1249 p.lexer.Next() 1250 p.parseClauseAlias("export") 1251 p.lexer.Next() 1252 } 1253 p.lexer.ExpectContextualKeyword("from") 1254 p.parsePath() 1255 p.lexer.ExpectOrInsertSemicolon() 1256 return 1257 } 1258 } 1259 1260 name := p.lexer.Identifier.String 1261 p.lexer.Expect(js_lexer.TIdentifier) 1262 1263 if opts.isModuleScope { 1264 p.localTypeNames[name] = true 1265 } 1266 1267 p.skipTypeScriptTypeParameters(allowInOutVarianceAnnotations | allowEmptyTypeParameters) 1268 p.lexer.Expect(js_lexer.TEquals) 1269 p.skipTypeScriptType(js_ast.LLowest) 1270 p.lexer.ExpectOrInsertSemicolon() 1271 } 1272 1273 func (p *parser) parseTypeScriptEnumStmt(loc logger.Loc, opts parseStmtOpts) js_ast.Stmt { 1274 p.lexer.Expect(js_lexer.TEnum) 1275 nameLoc := p.lexer.Loc() 1276 nameText := p.lexer.Identifier.String 1277 p.lexer.Expect(js_lexer.TIdentifier) 1278 name := ast.LocRef{Loc: nameLoc, Ref: ast.InvalidRef} 1279 1280 // Generate the namespace object 1281 exportedMembers := p.getOrCreateExportedNamespaceMembers(nameText, opts.isExport) 1282 tsNamespace := &js_ast.TSNamespaceScope{ 1283 ExportedMembers: exportedMembers, 1284 ArgRef: ast.InvalidRef, 1285 IsEnumScope: true, 1286 } 1287 enumMemberData := &js_ast.TSNamespaceMemberNamespace{ 1288 ExportedMembers: exportedMembers, 1289 } 1290 1291 // Declare the enum and create the scope 1292 scopeIndex := len(p.scopesInOrder) 1293 if !opts.isTypeScriptDeclare { 1294 name.Ref = p.declareSymbol(ast.SymbolTSEnum, nameLoc, nameText) 1295 p.pushScopeForParsePass(js_ast.ScopeEntry, loc) 1296 p.currentScope.TSNamespace = tsNamespace 1297 p.refToTSNamespaceMemberData[name.Ref] = enumMemberData 1298 } 1299 1300 p.lexer.Expect(js_lexer.TOpenBrace) 1301 values := []js_ast.EnumValue{} 1302 1303 oldFnOrArrowData := p.fnOrArrowDataParse 1304 p.fnOrArrowDataParse = fnOrArrowDataParse{ 1305 isThisDisallowed: true, 1306 needsAsyncLoc: logger.Loc{Start: -1}, 1307 } 1308 1309 // Parse the body 1310 for p.lexer.Token != js_lexer.TCloseBrace { 1311 nameRange := p.lexer.Range() 1312 value := js_ast.EnumValue{ 1313 Loc: nameRange.Loc, 1314 Ref: ast.InvalidRef, 1315 } 1316 1317 // Parse the name 1318 var nameText string 1319 if p.lexer.Token == js_lexer.TStringLiteral { 1320 value.Name = p.lexer.StringLiteral() 1321 nameText = helpers.UTF16ToString(value.Name) 1322 } else if p.lexer.IsIdentifierOrKeyword() { 1323 nameText = p.lexer.Identifier.String 1324 value.Name = helpers.StringToUTF16(nameText) 1325 } else { 1326 p.lexer.Expect(js_lexer.TIdentifier) 1327 } 1328 p.lexer.Next() 1329 1330 // Identifiers can be referenced by other values 1331 if !opts.isTypeScriptDeclare && js_ast.IsIdentifierUTF16(value.Name) { 1332 value.Ref = p.declareSymbol(ast.SymbolOther, value.Loc, helpers.UTF16ToString(value.Name)) 1333 } 1334 1335 // Parse the initializer 1336 if p.lexer.Token == js_lexer.TEquals { 1337 p.lexer.Next() 1338 value.ValueOrNil = p.parseExpr(js_ast.LComma) 1339 } 1340 1341 values = append(values, value) 1342 1343 // Add this enum value as a member of the enum's namespace 1344 exportedMembers[nameText] = js_ast.TSNamespaceMember{ 1345 Loc: value.Loc, 1346 Data: &js_ast.TSNamespaceMemberProperty{}, 1347 IsEnumValue: true, 1348 } 1349 1350 if p.lexer.Token != js_lexer.TComma && p.lexer.Token != js_lexer.TSemicolon { 1351 if p.lexer.IsIdentifierOrKeyword() || p.lexer.Token == js_lexer.TStringLiteral { 1352 var errorLoc logger.Loc 1353 var errorText string 1354 1355 if value.ValueOrNil.Data == nil { 1356 errorLoc = logger.Loc{Start: nameRange.End()} 1357 errorText = fmt.Sprintf("Expected \",\" after %q in enum", nameText) 1358 } else { 1359 var nextName string 1360 if p.lexer.Token == js_lexer.TStringLiteral { 1361 nextName = helpers.UTF16ToString(p.lexer.StringLiteral()) 1362 } else { 1363 nextName = p.lexer.Identifier.String 1364 } 1365 errorLoc = p.lexer.Loc() 1366 errorText = fmt.Sprintf("Expected \",\" before %q in enum", nextName) 1367 } 1368 1369 data := p.tracker.MsgData(logger.Range{Loc: errorLoc}, errorText) 1370 data.Location.Suggestion = "," 1371 p.log.AddMsg(logger.Msg{Kind: logger.Error, Data: data}) 1372 panic(js_lexer.LexerPanic{}) 1373 } 1374 break 1375 } 1376 p.lexer.Next() 1377 } 1378 1379 p.fnOrArrowDataParse = oldFnOrArrowData 1380 1381 if !opts.isTypeScriptDeclare { 1382 // Avoid a collision with the enum closure argument variable if the 1383 // enum exports a symbol with the same name as the enum itself: 1384 // 1385 // enum foo { 1386 // foo = 123, 1387 // bar = foo, 1388 // } 1389 // 1390 // TypeScript generates the following code in this case: 1391 // 1392 // var foo; 1393 // (function (foo) { 1394 // foo[foo["foo"] = 123] = "foo"; 1395 // foo[foo["bar"] = 123] = "bar"; 1396 // })(foo || (foo = {})); 1397 // 1398 // Whereas in this case: 1399 // 1400 // enum foo { 1401 // bar = foo as any, 1402 // } 1403 // 1404 // TypeScript generates the following code: 1405 // 1406 // var foo; 1407 // (function (foo) { 1408 // foo[foo["bar"] = foo] = "bar"; 1409 // })(foo || (foo = {})); 1410 // 1411 if _, ok := p.currentScope.Members[nameText]; ok { 1412 // Add a "_" to make tests easier to read, since non-bundler tests don't 1413 // run the renamer. For external-facing things the renamer will avoid 1414 // collisions automatically so this isn't important for correctness. 1415 tsNamespace.ArgRef = p.newSymbol(ast.SymbolHoisted, "_"+nameText) 1416 p.currentScope.Generated = append(p.currentScope.Generated, tsNamespace.ArgRef) 1417 } else { 1418 tsNamespace.ArgRef = p.declareSymbol(ast.SymbolHoisted, nameLoc, nameText) 1419 } 1420 p.refToTSNamespaceMemberData[tsNamespace.ArgRef] = enumMemberData 1421 1422 p.popScope() 1423 } 1424 1425 p.lexer.Expect(js_lexer.TCloseBrace) 1426 1427 if opts.isTypeScriptDeclare { 1428 if opts.isNamespaceScope && opts.isExport { 1429 p.hasNonLocalExportDeclareInsideNamespace = true 1430 } 1431 1432 return js_ast.Stmt{Loc: loc, Data: js_ast.STypeScriptShared} 1433 } 1434 1435 // Save these for when we do out-of-order enum visiting 1436 if p.scopesInOrderForEnum == nil { 1437 p.scopesInOrderForEnum = make(map[logger.Loc][]scopeOrder) 1438 } 1439 1440 // Make a copy of "scopesInOrder" instead of a slice since the original 1441 // array may be flattened in the future by "popAndFlattenScope" 1442 p.scopesInOrderForEnum[loc] = append([]scopeOrder{}, p.scopesInOrder[scopeIndex:]...) 1443 1444 return js_ast.Stmt{Loc: loc, Data: &js_ast.SEnum{ 1445 Name: name, 1446 Arg: tsNamespace.ArgRef, 1447 Values: values, 1448 IsExport: opts.isExport, 1449 }} 1450 } 1451 1452 // This assumes the caller has already parsed the "import" token 1453 func (p *parser) parseTypeScriptImportEqualsStmt(loc logger.Loc, opts parseStmtOpts, defaultNameLoc logger.Loc, defaultName string) js_ast.Stmt { 1454 p.lexer.Expect(js_lexer.TEquals) 1455 1456 kind := p.selectLocalKind(js_ast.LocalConst) 1457 name := p.lexer.Identifier 1458 value := js_ast.Expr{Loc: p.lexer.Loc(), Data: &js_ast.EIdentifier{Ref: p.storeNameInRef(name)}} 1459 p.lexer.Expect(js_lexer.TIdentifier) 1460 1461 if name.String == "require" && p.lexer.Token == js_lexer.TOpenParen { 1462 // "import ns = require('x')" 1463 p.lexer.Next() 1464 path := js_ast.Expr{Loc: p.lexer.Loc(), Data: &js_ast.EString{Value: p.lexer.StringLiteral()}} 1465 p.lexer.Expect(js_lexer.TStringLiteral) 1466 p.lexer.Expect(js_lexer.TCloseParen) 1467 value.Data = &js_ast.ECall{ 1468 Target: value, 1469 Args: []js_ast.Expr{path}, 1470 } 1471 } else { 1472 // "import Foo = Bar" 1473 // "import Foo = Bar.Baz" 1474 for p.lexer.Token == js_lexer.TDot { 1475 p.lexer.Next() 1476 value.Data = &js_ast.EDot{ 1477 Target: value, 1478 Name: p.lexer.Identifier.String, 1479 NameLoc: p.lexer.Loc(), 1480 CanBeRemovedIfUnused: true, 1481 } 1482 p.lexer.Expect(js_lexer.TIdentifier) 1483 } 1484 } 1485 1486 p.lexer.ExpectOrInsertSemicolon() 1487 1488 if opts.isTypeScriptDeclare { 1489 // "import type foo = require('bar');" 1490 // "import type foo = bar.baz;" 1491 return js_ast.Stmt{Loc: loc, Data: js_ast.STypeScriptShared} 1492 } 1493 1494 ref := p.declareSymbol(ast.SymbolConst, defaultNameLoc, defaultName) 1495 decls := []js_ast.Decl{{ 1496 Binding: js_ast.Binding{Loc: defaultNameLoc, Data: &js_ast.BIdentifier{Ref: ref}}, 1497 ValueOrNil: value, 1498 }} 1499 1500 return js_ast.Stmt{Loc: loc, Data: &js_ast.SLocal{ 1501 Kind: kind, 1502 Decls: decls, 1503 IsExport: opts.isExport, 1504 WasTSImportEquals: true, 1505 }} 1506 } 1507 1508 // Generate a TypeScript namespace object for this namespace's scope. If this 1509 // namespace is another block that is to be merged with an existing namespace, 1510 // use that earlier namespace's object instead. 1511 func (p *parser) getOrCreateExportedNamespaceMembers(name string, isExport bool) js_ast.TSNamespaceMembers { 1512 // Merge with a sibling namespace from the same scope 1513 if existingMember, ok := p.currentScope.Members[name]; ok { 1514 if memberData, ok := p.refToTSNamespaceMemberData[existingMember.Ref]; ok { 1515 if nsMemberData, ok := memberData.(*js_ast.TSNamespaceMemberNamespace); ok { 1516 return nsMemberData.ExportedMembers 1517 } 1518 } 1519 } 1520 1521 // Merge with a sibling namespace from a different scope 1522 if isExport { 1523 if parentNamespace := p.currentScope.TSNamespace; parentNamespace != nil { 1524 if existing, ok := parentNamespace.ExportedMembers[name]; ok { 1525 if existing, ok := existing.Data.(*js_ast.TSNamespaceMemberNamespace); ok { 1526 return existing.ExportedMembers 1527 } 1528 } 1529 } 1530 } 1531 1532 // Otherwise, generate a new namespace object 1533 return make(js_ast.TSNamespaceMembers) 1534 } 1535 1536 func (p *parser) parseTypeScriptNamespaceStmt(loc logger.Loc, opts parseStmtOpts) js_ast.Stmt { 1537 // "namespace Foo {}" 1538 nameLoc := p.lexer.Loc() 1539 nameText := p.lexer.Identifier.String 1540 p.lexer.Next() 1541 1542 // Generate the namespace object 1543 exportedMembers := p.getOrCreateExportedNamespaceMembers(nameText, opts.isExport) 1544 tsNamespace := &js_ast.TSNamespaceScope{ 1545 ExportedMembers: exportedMembers, 1546 ArgRef: ast.InvalidRef, 1547 } 1548 nsMemberData := &js_ast.TSNamespaceMemberNamespace{ 1549 ExportedMembers: exportedMembers, 1550 } 1551 1552 // Declare the namespace and create the scope 1553 name := ast.LocRef{Loc: nameLoc, Ref: ast.InvalidRef} 1554 scopeIndex := p.pushScopeForParsePass(js_ast.ScopeEntry, loc) 1555 p.currentScope.TSNamespace = tsNamespace 1556 1557 oldHasNonLocalExportDeclareInsideNamespace := p.hasNonLocalExportDeclareInsideNamespace 1558 oldFnOrArrowData := p.fnOrArrowDataParse 1559 p.hasNonLocalExportDeclareInsideNamespace = false 1560 p.fnOrArrowDataParse = fnOrArrowDataParse{ 1561 isThisDisallowed: true, 1562 isReturnDisallowed: true, 1563 needsAsyncLoc: logger.Loc{Start: -1}, 1564 } 1565 1566 // Parse the statements inside the namespace 1567 var stmts []js_ast.Stmt 1568 if p.lexer.Token == js_lexer.TDot { 1569 dotLoc := p.lexer.Loc() 1570 p.lexer.Next() 1571 stmts = []js_ast.Stmt{p.parseTypeScriptNamespaceStmt(dotLoc, parseStmtOpts{ 1572 isExport: true, 1573 isNamespaceScope: true, 1574 isTypeScriptDeclare: opts.isTypeScriptDeclare, 1575 })} 1576 } else if opts.isTypeScriptDeclare && p.lexer.Token != js_lexer.TOpenBrace { 1577 p.lexer.ExpectOrInsertSemicolon() 1578 } else { 1579 p.lexer.Expect(js_lexer.TOpenBrace) 1580 stmts = p.parseStmtsUpTo(js_lexer.TCloseBrace, parseStmtOpts{ 1581 isNamespaceScope: true, 1582 isTypeScriptDeclare: opts.isTypeScriptDeclare, 1583 }) 1584 p.lexer.Next() 1585 } 1586 1587 hasNonLocalExportDeclareInsideNamespace := p.hasNonLocalExportDeclareInsideNamespace 1588 p.hasNonLocalExportDeclareInsideNamespace = oldHasNonLocalExportDeclareInsideNamespace 1589 p.fnOrArrowDataParse = oldFnOrArrowData 1590 1591 // Add any exported members from this namespace's body as members of the 1592 // associated namespace object. 1593 for _, stmt := range stmts { 1594 switch s := stmt.Data.(type) { 1595 case *js_ast.SFunction: 1596 if s.IsExport { 1597 name := p.symbols[s.Fn.Name.Ref.InnerIndex].OriginalName 1598 member := js_ast.TSNamespaceMember{ 1599 Loc: s.Fn.Name.Loc, 1600 Data: &js_ast.TSNamespaceMemberProperty{}, 1601 } 1602 exportedMembers[name] = member 1603 p.refToTSNamespaceMemberData[s.Fn.Name.Ref] = member.Data 1604 } 1605 1606 case *js_ast.SClass: 1607 if s.IsExport { 1608 name := p.symbols[s.Class.Name.Ref.InnerIndex].OriginalName 1609 member := js_ast.TSNamespaceMember{ 1610 Loc: s.Class.Name.Loc, 1611 Data: &js_ast.TSNamespaceMemberProperty{}, 1612 } 1613 exportedMembers[name] = member 1614 p.refToTSNamespaceMemberData[s.Class.Name.Ref] = member.Data 1615 } 1616 1617 case *js_ast.SNamespace: 1618 if s.IsExport { 1619 if memberData, ok := p.refToTSNamespaceMemberData[s.Name.Ref]; ok { 1620 if nsMemberData, ok := memberData.(*js_ast.TSNamespaceMemberNamespace); ok { 1621 member := js_ast.TSNamespaceMember{ 1622 Loc: s.Name.Loc, 1623 Data: &js_ast.TSNamespaceMemberNamespace{ 1624 ExportedMembers: nsMemberData.ExportedMembers, 1625 }, 1626 } 1627 exportedMembers[p.symbols[s.Name.Ref.InnerIndex].OriginalName] = member 1628 p.refToTSNamespaceMemberData[s.Name.Ref] = member.Data 1629 } 1630 } 1631 } 1632 1633 case *js_ast.SEnum: 1634 if s.IsExport { 1635 if memberData, ok := p.refToTSNamespaceMemberData[s.Name.Ref]; ok { 1636 if nsMemberData, ok := memberData.(*js_ast.TSNamespaceMemberNamespace); ok { 1637 member := js_ast.TSNamespaceMember{ 1638 Loc: s.Name.Loc, 1639 Data: &js_ast.TSNamespaceMemberNamespace{ 1640 ExportedMembers: nsMemberData.ExportedMembers, 1641 }, 1642 } 1643 exportedMembers[p.symbols[s.Name.Ref.InnerIndex].OriginalName] = member 1644 p.refToTSNamespaceMemberData[s.Name.Ref] = member.Data 1645 } 1646 } 1647 } 1648 1649 case *js_ast.SLocal: 1650 if s.IsExport { 1651 js_ast.ForEachIdentifierBindingInDecls(s.Decls, func(loc logger.Loc, b *js_ast.BIdentifier) { 1652 name := p.symbols[b.Ref.InnerIndex].OriginalName 1653 member := js_ast.TSNamespaceMember{ 1654 Loc: loc, 1655 Data: &js_ast.TSNamespaceMemberProperty{}, 1656 } 1657 exportedMembers[name] = member 1658 p.refToTSNamespaceMemberData[b.Ref] = member.Data 1659 }) 1660 } 1661 } 1662 } 1663 1664 // Import assignments may be only used in type expressions, not value 1665 // expressions. If this is the case, the TypeScript compiler removes 1666 // them entirely from the output. That can cause the namespace itself 1667 // to be considered empty and thus be removed. 1668 importEqualsCount := 0 1669 for _, stmt := range stmts { 1670 if local, ok := stmt.Data.(*js_ast.SLocal); ok && local.WasTSImportEquals && !local.IsExport { 1671 importEqualsCount++ 1672 } 1673 } 1674 1675 // TypeScript omits namespaces without values. These namespaces 1676 // are only allowed to be used in type expressions. They are 1677 // allowed to be exported, but can also only be used in type 1678 // expressions when imported. So we shouldn't count them as a 1679 // real export either. 1680 // 1681 // TypeScript also strangely counts namespaces containing only 1682 // "export declare" statements as non-empty even though "declare" 1683 // statements are only type annotations. We cannot omit the namespace 1684 // in that case. See https://github.com/evanw/esbuild/issues/1158. 1685 if (len(stmts) == importEqualsCount && !hasNonLocalExportDeclareInsideNamespace) || opts.isTypeScriptDeclare { 1686 p.popAndDiscardScope(scopeIndex) 1687 if opts.isModuleScope { 1688 p.localTypeNames[nameText] = true 1689 } 1690 return js_ast.Stmt{Loc: loc, Data: js_ast.STypeScriptShared} 1691 } 1692 1693 if !opts.isTypeScriptDeclare { 1694 // Avoid a collision with the namespace closure argument variable if the 1695 // namespace exports a symbol with the same name as the namespace itself: 1696 // 1697 // namespace foo { 1698 // export let foo = 123 1699 // console.log(foo) 1700 // } 1701 // 1702 // TypeScript generates the following code in this case: 1703 // 1704 // var foo; 1705 // (function (foo_1) { 1706 // foo_1.foo = 123; 1707 // console.log(foo_1.foo); 1708 // })(foo || (foo = {})); 1709 // 1710 if _, ok := p.currentScope.Members[nameText]; ok { 1711 // Add a "_" to make tests easier to read, since non-bundler tests don't 1712 // run the renamer. For external-facing things the renamer will avoid 1713 // collisions automatically so this isn't important for correctness. 1714 tsNamespace.ArgRef = p.newSymbol(ast.SymbolHoisted, "_"+nameText) 1715 p.currentScope.Generated = append(p.currentScope.Generated, tsNamespace.ArgRef) 1716 } else { 1717 tsNamespace.ArgRef = p.declareSymbol(ast.SymbolHoisted, nameLoc, nameText) 1718 } 1719 p.refToTSNamespaceMemberData[tsNamespace.ArgRef] = nsMemberData 1720 } 1721 1722 p.popScope() 1723 if !opts.isTypeScriptDeclare { 1724 name.Ref = p.declareSymbol(ast.SymbolTSNamespace, nameLoc, nameText) 1725 p.refToTSNamespaceMemberData[name.Ref] = nsMemberData 1726 } 1727 return js_ast.Stmt{Loc: loc, Data: &js_ast.SNamespace{ 1728 Name: name, 1729 Arg: tsNamespace.ArgRef, 1730 Stmts: stmts, 1731 IsExport: opts.isExport, 1732 }} 1733 } 1734 1735 func (p *parser) generateClosureForTypeScriptNamespaceOrEnum( 1736 stmts []js_ast.Stmt, stmtLoc logger.Loc, isExport bool, nameLoc logger.Loc, 1737 nameRef ast.Ref, argRef ast.Ref, stmtsInsideClosure []js_ast.Stmt, 1738 ) []js_ast.Stmt { 1739 // Follow the link chain in case symbols were merged 1740 symbol := p.symbols[nameRef.InnerIndex] 1741 for symbol.Link != ast.InvalidRef { 1742 nameRef = symbol.Link 1743 symbol = p.symbols[nameRef.InnerIndex] 1744 } 1745 1746 // Make sure to only emit a variable once for a given namespace, since there 1747 // can be multiple namespace blocks for the same namespace 1748 if (symbol.Kind == ast.SymbolTSNamespace || symbol.Kind == ast.SymbolTSEnum) && !p.emittedNamespaceVars[nameRef] { 1749 decls := []js_ast.Decl{{Binding: js_ast.Binding{Loc: nameLoc, Data: &js_ast.BIdentifier{Ref: nameRef}}}} 1750 p.emittedNamespaceVars[nameRef] = true 1751 if p.currentScope == p.moduleScope { 1752 // Top-level namespace: "var" 1753 stmts = append(stmts, js_ast.Stmt{Loc: stmtLoc, Data: &js_ast.SLocal{ 1754 Kind: js_ast.LocalVar, 1755 Decls: decls, 1756 IsExport: isExport, 1757 }}) 1758 } else { 1759 // Nested namespace: "let" 1760 stmts = append(stmts, js_ast.Stmt{Loc: stmtLoc, Data: &js_ast.SLocal{ 1761 Kind: js_ast.LocalLet, 1762 Decls: decls, 1763 }}) 1764 } 1765 } 1766 1767 var argExpr js_ast.Expr 1768 if p.options.minifySyntax && !p.options.unsupportedJSFeatures.Has(compat.LogicalAssignment) { 1769 // If the "||=" operator is supported, our minified output can be slightly smaller 1770 if isExport && p.enclosingNamespaceArgRef != nil { 1771 // "name = (enclosing.name ||= {})" 1772 argExpr = js_ast.Assign( 1773 js_ast.Expr{Loc: nameLoc, Data: &js_ast.EIdentifier{Ref: nameRef}}, 1774 js_ast.Expr{Loc: nameLoc, Data: &js_ast.EBinary{ 1775 Op: js_ast.BinOpLogicalOrAssign, 1776 Left: js_ast.Expr{Loc: nameLoc, Data: p.dotOrMangledPropVisit( 1777 js_ast.Expr{Loc: nameLoc, Data: &js_ast.EIdentifier{Ref: *p.enclosingNamespaceArgRef}}, 1778 p.symbols[nameRef.InnerIndex].OriginalName, 1779 nameLoc, 1780 )}, 1781 Right: js_ast.Expr{Loc: nameLoc, Data: &js_ast.EObject{}}, 1782 }}, 1783 ) 1784 p.recordUsage(*p.enclosingNamespaceArgRef) 1785 p.recordUsage(nameRef) 1786 } else { 1787 // "name ||= {}" 1788 argExpr = js_ast.Expr{Loc: nameLoc, Data: &js_ast.EBinary{ 1789 Op: js_ast.BinOpLogicalOrAssign, 1790 Left: js_ast.Expr{Loc: nameLoc, Data: &js_ast.EIdentifier{Ref: nameRef}}, 1791 Right: js_ast.Expr{Loc: nameLoc, Data: &js_ast.EObject{}}, 1792 }} 1793 p.recordUsage(nameRef) 1794 } 1795 } else { 1796 if isExport && p.enclosingNamespaceArgRef != nil { 1797 // "name = enclosing.name || (enclosing.name = {})" 1798 name := p.symbols[nameRef.InnerIndex].OriginalName 1799 argExpr = js_ast.Assign( 1800 js_ast.Expr{Loc: nameLoc, Data: &js_ast.EIdentifier{Ref: nameRef}}, 1801 js_ast.Expr{Loc: nameLoc, Data: &js_ast.EBinary{ 1802 Op: js_ast.BinOpLogicalOr, 1803 Left: js_ast.Expr{Loc: nameLoc, Data: p.dotOrMangledPropVisit( 1804 js_ast.Expr{Loc: nameLoc, Data: &js_ast.EIdentifier{Ref: *p.enclosingNamespaceArgRef}}, 1805 name, 1806 nameLoc, 1807 )}, 1808 Right: js_ast.Assign( 1809 js_ast.Expr{Loc: nameLoc, Data: p.dotOrMangledPropVisit( 1810 js_ast.Expr{Loc: nameLoc, Data: &js_ast.EIdentifier{Ref: *p.enclosingNamespaceArgRef}}, 1811 name, 1812 nameLoc, 1813 )}, 1814 js_ast.Expr{Loc: nameLoc, Data: &js_ast.EObject{}}, 1815 ), 1816 }}, 1817 ) 1818 p.recordUsage(*p.enclosingNamespaceArgRef) 1819 p.recordUsage(*p.enclosingNamespaceArgRef) 1820 p.recordUsage(nameRef) 1821 } else { 1822 // "name || (name = {})" 1823 argExpr = js_ast.Expr{Loc: nameLoc, Data: &js_ast.EBinary{ 1824 Op: js_ast.BinOpLogicalOr, 1825 Left: js_ast.Expr{Loc: nameLoc, Data: &js_ast.EIdentifier{Ref: nameRef}}, 1826 Right: js_ast.Assign( 1827 js_ast.Expr{Loc: nameLoc, Data: &js_ast.EIdentifier{Ref: nameRef}}, 1828 js_ast.Expr{Loc: nameLoc, Data: &js_ast.EObject{}}, 1829 ), 1830 }} 1831 p.recordUsage(nameRef) 1832 p.recordUsage(nameRef) 1833 } 1834 } 1835 1836 // Try to use an arrow function if possible for compactness 1837 var targetExpr js_ast.Expr 1838 args := []js_ast.Arg{{Binding: js_ast.Binding{Loc: nameLoc, Data: &js_ast.BIdentifier{Ref: argRef}}}} 1839 if p.options.unsupportedJSFeatures.Has(compat.Arrow) { 1840 targetExpr = js_ast.Expr{Loc: stmtLoc, Data: &js_ast.EFunction{Fn: js_ast.Fn{ 1841 Args: args, 1842 Body: js_ast.FnBody{Loc: stmtLoc, Block: js_ast.SBlock{Stmts: stmtsInsideClosure}}, 1843 }}} 1844 } else { 1845 // "(() => { foo() })()" => "(() => foo())()" 1846 if p.options.minifySyntax && len(stmtsInsideClosure) == 1 { 1847 if expr, ok := stmtsInsideClosure[0].Data.(*js_ast.SExpr); ok { 1848 stmtsInsideClosure[0].Data = &js_ast.SReturn{ValueOrNil: expr.Value} 1849 } 1850 } 1851 targetExpr = js_ast.Expr{Loc: stmtLoc, Data: &js_ast.EArrow{ 1852 Args: args, 1853 Body: js_ast.FnBody{Loc: stmtLoc, Block: js_ast.SBlock{Stmts: stmtsInsideClosure}}, 1854 PreferExpr: true, 1855 }} 1856 } 1857 1858 // Call the closure with the name object 1859 stmts = append(stmts, js_ast.Stmt{Loc: stmtLoc, Data: &js_ast.SExpr{Value: js_ast.Expr{Loc: stmtLoc, Data: &js_ast.ECall{ 1860 Target: targetExpr, 1861 Args: []js_ast.Expr{argExpr}, 1862 }}}}) 1863 1864 return stmts 1865 } 1866 1867 func (p *parser) generateClosureForTypeScriptEnum( 1868 stmts []js_ast.Stmt, stmtLoc logger.Loc, isExport bool, nameLoc logger.Loc, 1869 nameRef ast.Ref, argRef ast.Ref, exprsInsideClosure []js_ast.Expr, 1870 allValuesArePure bool, 1871 ) []js_ast.Stmt { 1872 // Bail back to the namespace code for enums that aren't at the top level. 1873 // Doing this for nested enums is problematic for two reasons. First of all 1874 // enums inside of namespaces must be property accesses off the namespace 1875 // object instead of variable declarations. Also we'd need to use "let" 1876 // instead of "var" which doesn't allow sibling declarations to be merged. 1877 if p.currentScope != p.moduleScope { 1878 stmtsInsideClosure := []js_ast.Stmt{} 1879 if len(exprsInsideClosure) > 0 { 1880 if p.options.minifySyntax { 1881 // "a; b; c;" => "a, b, c;" 1882 joined := js_ast.JoinAllWithComma(exprsInsideClosure) 1883 stmtsInsideClosure = append(stmtsInsideClosure, js_ast.Stmt{Loc: joined.Loc, Data: &js_ast.SExpr{Value: joined}}) 1884 } else { 1885 for _, expr := range exprsInsideClosure { 1886 stmtsInsideClosure = append(stmtsInsideClosure, js_ast.Stmt{Loc: expr.Loc, Data: &js_ast.SExpr{Value: expr}}) 1887 } 1888 } 1889 } 1890 return p.generateClosureForTypeScriptNamespaceOrEnum( 1891 stmts, stmtLoc, isExport, nameLoc, nameRef, argRef, stmtsInsideClosure) 1892 } 1893 1894 // This uses an output format for enums that's different but equivalent to 1895 // what TypeScript uses. Here is TypeScript's output: 1896 // 1897 // var x; 1898 // (function (x) { 1899 // x[x["y"] = 1] = "y"; 1900 // })(x || (x = {})); 1901 // 1902 // And here's our output: 1903 // 1904 // var x = /* @__PURE__ */ ((x) => { 1905 // x[x["y"] = 1] = "y"; 1906 // return x; 1907 // })(x || {}); 1908 // 1909 // One benefit is that the minified output is smaller: 1910 // 1911 // // Old output minified 1912 // var x;(function(n){n[n.y=1]="y"})(x||(x={})); 1913 // 1914 // // New output minified 1915 // var x=(r=>(r[r.y=1]="y",r))(x||{}); 1916 // 1917 // Another benefit is that the @__PURE__ annotation means it automatically 1918 // works with tree-shaking, even with more advanced features such as sibling 1919 // enum declarations and enum/namespace merges. Ideally all uses of the enum 1920 // are just direct references to enum members (and are therefore inlined as 1921 // long as the enum value is a constant) and the enum definition itself is 1922 // unused and can be removed as dead code. 1923 1924 // Follow the link chain in case symbols were merged 1925 symbol := p.symbols[nameRef.InnerIndex] 1926 for symbol.Link != ast.InvalidRef { 1927 nameRef = symbol.Link 1928 symbol = p.symbols[nameRef.InnerIndex] 1929 } 1930 1931 // Generate the body of the closure, including a return statement at the end 1932 stmtsInsideClosure := []js_ast.Stmt{} 1933 argExpr := js_ast.Expr{Loc: nameLoc, Data: &js_ast.EIdentifier{Ref: argRef}} 1934 if p.options.minifySyntax { 1935 // "a; b; return c;" => "return a, b, c;" 1936 joined := js_ast.JoinAllWithComma(exprsInsideClosure) 1937 joined = js_ast.JoinWithComma(joined, argExpr) 1938 stmtsInsideClosure = append(stmtsInsideClosure, js_ast.Stmt{Loc: joined.Loc, Data: &js_ast.SReturn{ValueOrNil: joined}}) 1939 } else { 1940 for _, expr := range exprsInsideClosure { 1941 stmtsInsideClosure = append(stmtsInsideClosure, js_ast.Stmt{Loc: expr.Loc, Data: &js_ast.SExpr{Value: expr}}) 1942 } 1943 stmtsInsideClosure = append(stmtsInsideClosure, js_ast.Stmt{Loc: argExpr.Loc, Data: &js_ast.SReturn{ValueOrNil: argExpr}}) 1944 } 1945 1946 // Try to use an arrow function if possible for compactness 1947 var targetExpr js_ast.Expr 1948 args := []js_ast.Arg{{Binding: js_ast.Binding{Loc: nameLoc, Data: &js_ast.BIdentifier{Ref: argRef}}}} 1949 if p.options.unsupportedJSFeatures.Has(compat.Arrow) { 1950 targetExpr = js_ast.Expr{Loc: stmtLoc, Data: &js_ast.EFunction{Fn: js_ast.Fn{ 1951 Args: args, 1952 Body: js_ast.FnBody{Loc: stmtLoc, Block: js_ast.SBlock{Stmts: stmtsInsideClosure}}, 1953 }}} 1954 } else { 1955 targetExpr = js_ast.Expr{Loc: stmtLoc, Data: &js_ast.EArrow{ 1956 Args: args, 1957 Body: js_ast.FnBody{Loc: stmtLoc, Block: js_ast.SBlock{Stmts: stmtsInsideClosure}}, 1958 PreferExpr: p.options.minifySyntax, 1959 }} 1960 } 1961 1962 // Call the closure with the name object and store it to the variable 1963 decls := []js_ast.Decl{{ 1964 Binding: js_ast.Binding{Loc: nameLoc, Data: &js_ast.BIdentifier{Ref: nameRef}}, 1965 ValueOrNil: js_ast.Expr{Loc: stmtLoc, Data: &js_ast.ECall{ 1966 Target: targetExpr, 1967 Args: []js_ast.Expr{{Loc: nameLoc, Data: &js_ast.EBinary{ 1968 Op: js_ast.BinOpLogicalOr, 1969 Left: js_ast.Expr{Loc: nameLoc, Data: &js_ast.EIdentifier{Ref: nameRef}}, 1970 Right: js_ast.Expr{Loc: nameLoc, Data: &js_ast.EObject{}}, 1971 }}}, 1972 CanBeUnwrappedIfUnused: allValuesArePure, 1973 }}, 1974 }} 1975 p.recordUsage(nameRef) 1976 1977 // Use a "var" statement since this is a top-level enum, but only use "export" once 1978 stmts = append(stmts, js_ast.Stmt{Loc: stmtLoc, Data: &js_ast.SLocal{ 1979 Kind: js_ast.LocalVar, 1980 Decls: decls, 1981 IsExport: isExport && !p.emittedNamespaceVars[nameRef], 1982 }}) 1983 p.emittedNamespaceVars[nameRef] = true 1984 1985 return stmts 1986 } 1987 1988 func (p *parser) wrapInlinedEnum(value js_ast.Expr, comment string) js_ast.Expr { 1989 if strings.Contains(comment, "*/") { 1990 // Don't wrap with a comment 1991 return value 1992 } 1993 1994 // Wrap with a comment 1995 return js_ast.Expr{Loc: value.Loc, Data: &js_ast.EInlinedEnum{ 1996 Value: value, 1997 Comment: comment, 1998 }} 1999 }