package js_parser import ( "fmt" "math" "reflect" "sort" "strings" "unsafe" "github.com/evanw/esbuild/internal/ast" "github.com/evanw/esbuild/internal/compat" "github.com/evanw/esbuild/internal/config" "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" "github.com/evanw/esbuild/internal/renamer" "github.com/evanw/esbuild/internal/runtime" ) // This parser does two passes: // // 1. Parse the source into an AST, create the scope tree, and declare symbols. // // 2. Visit each node in the AST, bind identifiers to declared symbols, do // constant folding, substitute compile-time variable definitions, and // lower certain syntactic constructs as appropriate given the language // target. // // So many things have been put in so few passes because we want to minimize // the number of full-tree passes to improve performance. However, we need // to have at least two separate passes to handle variable hoisting. See the // comment about scopesInOrder below for more information. type parser struct { options Options log logger.Log source logger.Source tracker logger.LineColumnTracker lexer js_lexer.Lexer allowIn bool allowPrivateIdentifiers bool hasTopLevelReturn bool latestReturnHadSemicolon bool hasImportMeta bool hasESModuleSyntax bool warnedThisIsUndefined bool topLevelAwaitKeyword logger.Range fnOrArrowDataParse fnOrArrowDataParse fnOrArrowDataVisit fnOrArrowDataVisit fnOnlyDataVisit fnOnlyDataVisit allocatedNames []string latestArrowArgLoc logger.Loc forbidSuffixAfterAsLoc logger.Loc currentScope *js_ast.Scope scopesForCurrentPart []*js_ast.Scope symbols []js_ast.Symbol tsUseCounts []uint32 exportsRef js_ast.Ref requireRef js_ast.Ref moduleRef js_ast.Ref importMetaRef js_ast.Ref promiseRef js_ast.Ref findSymbolHelper func(loc logger.Loc, name string) js_ast.Ref symbolForDefineHelper func(int) js_ast.Ref injectedDefineSymbols []js_ast.Ref injectedSymbolSources map[js_ast.Ref]injectedSymbolSource symbolUses map[js_ast.Ref]js_ast.SymbolUse declaredSymbols []js_ast.DeclaredSymbol runtimeImports map[string]js_ast.Ref duplicateCaseChecker duplicateCaseChecker unrepresentableIdentifiers map[string]bool legacyOctalLiterals map[js_ast.E]logger.Range // For strict mode handling hoistedRefForSloppyModeBlockFn map[js_ast.Ref]js_ast.Ref // For lowering private methods weakMapRef js_ast.Ref weakSetRef js_ast.Ref privateGetters map[js_ast.Ref]js_ast.Ref privateSetters map[js_ast.Ref]js_ast.Ref // These are for TypeScript shouldFoldNumericConstants bool emittedNamespaceVars map[js_ast.Ref]bool isExportedInsideNamespace map[js_ast.Ref]js_ast.Ref knownEnumValues map[js_ast.Ref]map[string]float64 localTypeNames map[string]bool // This is the reference to the generated function argument for the namespace, // which is different than the reference to the namespace itself: // // namespace ns { // } // // The code above is transformed into something like this: // // var ns1; // (function(ns2) { // })(ns1 || (ns1 = {})); // // This variable is "ns2" not "ns1". It is only used during the second // "visit" pass. enclosingNamespaceArgRef *js_ast.Ref // Imports (both ES6 and CommonJS) are tracked at the top level importRecords []ast.ImportRecord importRecordsForCurrentPart []uint32 exportStarImportRecords []uint32 // These are for handling ES6 imports and exports es6ImportKeyword logger.Range es6ExportKeyword logger.Range enclosingClassKeyword logger.Range importItemsForNamespace map[js_ast.Ref]map[string]js_ast.LocRef isImportItem map[js_ast.Ref]bool namedImports map[js_ast.Ref]js_ast.NamedImport namedExports map[string]js_ast.NamedExport topLevelSymbolToParts map[js_ast.Ref][]uint32 importNamespaceCCMap map[importNamespaceCall]bool // The parser does two passes and we need to pass the scope tree information // from the first pass to the second pass. That's done by tracking the calls // to pushScopeForParsePass() and popScope() during the first pass in // scopesInOrder. // // Then, when the second pass calls pushScopeForVisitPass() and popScope(), // we consume entries from scopesInOrder and make sure they are in the same // order. This way the second pass can efficiently use the same scope tree // as the first pass without having to attach the scope tree to the AST. // // We need to split this into two passes because the pass that declares the // symbols must be separate from the pass that binds identifiers to declared // symbols to handle declaring a hoisted "var" symbol in a nested scope and // binding a name to it in a parent or sibling scope. scopesInOrder []scopeOrder // These properties are for the visit pass, which runs after the parse pass. // The visit pass binds identifiers to declared symbols, does constant // folding, substitutes compile-time variable definitions, and lowers certain // syntactic constructs as appropriate. stmtExprValue js_ast.E callTarget js_ast.E templateTag js_ast.E deleteTarget js_ast.E loopBody js_ast.S moduleScope *js_ast.Scope isControlFlowDead bool // Inside a TypeScript namespace, an "export declare" statement can be used // to cause a namespace to be emitted even though it has no other observable // effect. This flag is used to implement this feature. // // Specifically, namespaces should be generated for all of the following // namespaces below except for "f", which should not be generated: // // namespace a { export declare const a } // namespace b { export declare let [[b]] } // namespace c { export declare function c() } // namespace d { export declare class d {} } // namespace e { export declare enum e {} } // namespace f { export declare namespace f {} } // // The TypeScript compiler compiles this into the following code (notice "f" // is missing): // // var a; (function (a_1) {})(a || (a = {})); // var b; (function (b_1) {})(b || (b = {})); // var c; (function (c_1) {})(c || (c = {})); // var d; (function (d_1) {})(d || (d = {})); // var e; (function (e_1) {})(e || (e = {})); // // Note that this should not be implemented by declaring symbols for "export // declare" statements because the TypeScript compiler doesn't generate any // code for these statements, so these statements are actually references to // global variables. There is one exception, which is that local variables // *should* be declared as symbols because they are replaced with. This seems // like very arbitrary behavior but it's what the TypeScript compiler does, // so we try to match it. // // Specifically, in the following code below "a" and "b" should be declared // and should be substituted with "ns.a" and "ns.b" but the other symbols // shouldn't. References to the other symbols actually refer to global // variables instead of to symbols that are exported from the namespace. // This is the case as of TypeScript 4.3. I assume this is a TypeScript bug: // // namespace ns { // export declare const a // export declare let [[b]] // export declare function c() // export declare class d { } // export declare enum e { } // console.log(a, b, c, d, e) // } // // The TypeScript compiler compiles this into the following code: // // var ns; // (function (ns) { // console.log(ns.a, ns.b, c, d, e); // })(ns || (ns = {})); // // Relevant issue: https://github.com/evanw/esbuild/issues/1158 hasNonLocalExportDeclareInsideNamespace bool // This helps recognize the "await import()" pattern. When this is present, // warnings about non-string import paths will be omitted inside try blocks. awaitTarget js_ast.E // This helps recognize the "import().catch()" pattern. We also try to avoid // warning about this just like the "try { await import() }" pattern. thenCatchChain thenCatchChain // Temporary variables used for lowering tempRefsToDeclare []tempRef tempRefCount int topLevelTempRefsToDeclare []tempRef topLevelTempRefCount int // When bundling, hoisted top-level local variables declared with "var" in // nested scopes are moved up to be declared in the top-level scope instead. // The old "var" statements are turned into regular assignments instead. This // makes it easier to quickly scan the top-level statements for "var" locals // with the guarantee that all will be found. relocatedTopLevelVars []js_ast.LocRef // ArrowFunction is a special case in the grammar. Although it appears to be // a PrimaryExpression, it's actually an AssignmentExpression. This means if // a AssignmentExpression ends up producing an ArrowFunction then nothing can // come after it other than the comma operator, since the comma operator is // the only thing above AssignmentExpression under the Expression rule: // // AssignmentExpression: // ArrowFunction // ConditionalExpression // LeftHandSideExpression = AssignmentExpression // LeftHandSideExpression AssignmentOperator AssignmentExpression // // Expression: // AssignmentExpression // Expression , AssignmentExpression // afterArrowBodyLoc logger.Loc // We need to lower private names such as "#foo" if they are used in a brand // check such as "#foo in x" even if the private name syntax would otherwise // be supported. This is because private names are a newly-added feature. // // However, this parser operates in only two passes for speed. The first pass // parses things and declares variables, and the second pass lowers things and // resolves references to declared variables. So the existence of a "#foo in x" // expression for a specific "#foo" cannot be used to decide to lower "#foo" // because it's too late by that point. There may be another expression such // as "x.#foo" before that point and that must be lowered as well even though // it has already been visited. // // Instead what we do is track just the names of fields used in private brand // checks during the first pass. This tracks the names themselves, not symbol // references. Then, during the second pass when we are about to enter into // a class, we conservatively decide to lower all private names in that class // which are used in a brand check anywhere in the file. classPrivateBrandChecksToLower map[string]bool // Setting this to true disables warnings about code that is very likely to // be a bug. This is used to ignore issues inside "node_modules" directories. // This has caught real issues in the past. However, it's not esbuild's job // to find bugs in other libraries, and these warnings are problematic for // people using these libraries with esbuild. The only fix is to either // disable all esbuild warnings and not get warnings about your own code, or // to try to get the warning fixed in the affected library. This is // especially annoying if the warning is a false positive as was the case in // https://github.com/firebase/firebase-js-sdk/issues/3814. So these warnings // are now disabled for code inside "node_modules" directories. suppressWarningsAboutWeirdCode bool } type injectedSymbolSource struct { source logger.Source loc logger.Loc } type importNamespaceCallKind uint8 const ( exprKindCall importNamespaceCallKind = iota exprKindNew exprKindJSXTag ) type importNamespaceCall struct { ref js_ast.Ref kind importNamespaceCallKind } type thenCatchChain struct { nextTarget js_ast.E hasMultipleArgs bool hasCatch bool } // This is used as part of an incremental build cache key. Some of these values // can potentially change between builds if they are derived from nearby // "package.json" or "tsconfig.json" files that were changed since the last // build. type Options struct { injectedFiles []config.InjectedFile jsx config.JSXOptions tsTarget *config.TSTarget // This pointer will always be different for each build but the contents // shouldn't ever behave different semantically. We ignore this field for the // equality comparison. defines *config.ProcessedDefines // This is an embedded struct. Always access these directly instead of off // the name "optionsThatSupportStructuralEquality". This is only grouped like // this to make the equality comparison easier and safer (and hopefully faster). optionsThatSupportStructuralEquality } type optionsThatSupportStructuralEquality struct { unsupportedJSFeatures compat.JSFeature originalTargetEnv string // Byte-sized values go here (gathered together here to keep this object compact) ts config.TSOptions mode config.Mode platform config.Platform outputFormat config.Format moduleType config.ModuleType isTargetUnconfigured bool asciiOnly bool keepNames bool mangleSyntax bool minifyIdentifiers bool omitRuntimeForTests bool ignoreDCEAnnotations bool treeShaking bool preserveUnusedImportsTS bool useDefineForClassFields config.MaybeBool } func OptionsFromConfig(options *config.Options) Options { return Options{ injectedFiles: options.InjectedFiles, jsx: options.JSX, defines: options.Defines, tsTarget: options.TSTarget, optionsThatSupportStructuralEquality: optionsThatSupportStructuralEquality{ unsupportedJSFeatures: options.UnsupportedJSFeatures, originalTargetEnv: options.OriginalTargetEnv, ts: options.TS, mode: options.Mode, platform: options.Platform, outputFormat: options.OutputFormat, moduleType: options.ModuleType, isTargetUnconfigured: options.IsTargetUnconfigured, asciiOnly: options.ASCIIOnly, keepNames: options.KeepNames, mangleSyntax: options.MangleSyntax, minifyIdentifiers: options.MinifyIdentifiers, omitRuntimeForTests: options.OmitRuntimeForTests, ignoreDCEAnnotations: options.IgnoreDCEAnnotations, treeShaking: options.TreeShaking, preserveUnusedImportsTS: options.PreserveUnusedImportsTS, useDefineForClassFields: options.UseDefineForClassFields, }, } } func (a *Options) Equal(b *Options) bool { // Compare "optionsThatSupportStructuralEquality" if a.optionsThatSupportStructuralEquality != b.optionsThatSupportStructuralEquality { return false } // Compare "TSTarget" if (a.tsTarget == nil && b.tsTarget != nil) || (a.tsTarget != nil && b.tsTarget == nil) || (a.tsTarget != nil && b.tsTarget != nil && *a.tsTarget != *b.tsTarget) { return false } // Compare "InjectedFiles" if len(a.injectedFiles) != len(b.injectedFiles) { return false } for i, x := range a.injectedFiles { y := b.injectedFiles[i] if x.Source != y.Source || x.DefineName != y.DefineName || len(x.Exports) != len(y.Exports) { return false } for j := range x.Exports { if x.Exports[j] != y.Exports[j] { return false } } } // Compare "JSX" if a.jsx.Parse != b.jsx.Parse || !jsxExprsEqual(a.jsx.Factory, b.jsx.Factory) || !jsxExprsEqual(a.jsx.Fragment, b.jsx.Fragment) { return false } // Do a cheap assert that the defines object hasn't changed if (a.defines != nil || b.defines != nil) && (a.defines == nil || b.defines == nil || len(a.defines.IdentifierDefines) != len(b.defines.IdentifierDefines) || len(a.defines.DotDefines) != len(b.defines.DotDefines)) { panic("Internal error") } return true } func jsxExprsEqual(a config.JSXExpr, b config.JSXExpr) bool { if !stringArraysEqual(a.Parts, b.Parts) { return false } if a.Constant != nil { if b.Constant == nil || !valuesLookTheSame(a.Constant, b.Constant) { return false } } else if b.Constant != nil { return false } return true } func stringArraysEqual(a []string, b []string) bool { if len(a) != len(b) { return false } for i, x := range a { if x != b[i] { return false } } return true } type tempRef struct { ref js_ast.Ref valueOrNil js_ast.Expr } const ( locModuleScope = -1 ) type scopeOrder struct { loc logger.Loc scope *js_ast.Scope } type awaitOrYield uint8 const ( // The keyword is used as an identifier, not a special expression allowIdent awaitOrYield = iota // Declaring the identifier is forbidden, and the keyword is used as a special expression allowExpr // Declaring the identifier is forbidden, and using the identifier is also forbidden forbidAll ) // This is function-specific information used during parsing. It is saved and // restored on the call stack around code that parses nested functions and // arrow expressions. type fnOrArrowDataParse struct { needsAsyncLoc logger.Loc asyncRange logger.Range arrowArgErrors *deferredArrowArgErrors await awaitOrYield yield awaitOrYield allowSuperCall bool allowSuperProperty bool isTopLevel bool isConstructor bool isTypeScriptDeclare bool isClassStaticInit bool // In TypeScript, forward declarations of functions have no bodies allowMissingBodyForTypeScript bool // Allow TypeScript decorators in function arguments allowTSDecorators bool } // This is function-specific information used during visiting. It is saved and // restored on the call stack around code that parses nested functions and // arrow expressions. type fnOrArrowDataVisit struct { isArrow bool isAsync bool isGenerator bool isInsideLoop bool isInsideSwitch bool isOutsideFnOrArrow bool // This is used to silence unresolvable imports due to "require" calls inside // a try/catch statement. The assumption is that the try/catch statement is // there to handle the case where the reference to "require" crashes. tryBodyCount int } // This is function-specific information used during visiting. It is saved and // restored on the call stack around code that parses nested functions (but not // nested arrow functions). type fnOnlyDataVisit struct { superIndexRef *js_ast.Ref shouldLowerSuper bool // This is a reference to the magic "arguments" variable that exists inside // functions in JavaScript. It will be non-nil inside functions and nil // otherwise. argumentsRef *js_ast.Ref // Arrow functions don't capture the value of "this" and "arguments". Instead, // the values are inherited from the surrounding context. If arrow functions // are turned into regular functions due to lowering, we will need to generate // local variables to capture these values so they are preserved correctly. thisCaptureRef *js_ast.Ref argumentsCaptureRef *js_ast.Ref // Inside a static class property initializer, "this" expressions should be // replaced with the class name. thisClassStaticRef *js_ast.Ref // If we're inside an async arrow function and async functions are not // supported, then we will have to convert that arrow function to a generator // function. That means references to "arguments" inside the arrow function // will have to reference a captured variable instead of the real variable. isInsideAsyncArrowFn bool // If false, disallow "new.target" expressions. We disallow all "new.target" // expressions at the top-level of the file (i.e. not inside a function or // a class field). Technically since CommonJS files are wrapped in a function // you can use "new.target" in node as an alias for "undefined" but we don't // support that. isNewTargetAllowed bool // If false, the value for "this" is the top-level module scope "this" value. // That means it's "undefined" for ECMAScript modules and "exports" for // CommonJS modules. We track this information so that we can substitute the // correct value for these top-level "this" references at compile time instead // of passing the "this" expression through to the output and leaving the // interpretation up to the run-time behavior of the generated code. // // If true, the value for "this" is nested inside something (either a function // or a class declaration). That means the top-level module scope "this" value // has been shadowed and is now inaccessible. isThisNested bool } const bloomFilterSize = 251 type duplicateCaseValue struct { hash uint32 value js_ast.Expr } type duplicateCaseChecker struct { bloomFilter [(bloomFilterSize + 7) / 8]byte cases []duplicateCaseValue } func (dc *duplicateCaseChecker) reset() { // Preserve capacity dc.cases = dc.cases[:0] // This should be optimized by the compiler. See this for more information: // https://github.com/golang/go/issues/5373 bytes := dc.bloomFilter for i := range bytes { bytes[i] = 0 } } func (dc *duplicateCaseChecker) check(p *parser, expr js_ast.Expr) { if hash, ok := duplicateCaseHash(expr); ok { bucket := hash % bloomFilterSize entry := &dc.bloomFilter[bucket/8] mask := byte(1) << (bucket % 8) // Check for collisions if (*entry & mask) != 0 { for _, c := range dc.cases { if c.hash == hash { if equals, couldBeIncorrect := duplicateCaseEquals(c.value, expr); equals { r := p.source.RangeOfOperatorBefore(expr.Loc, "case") text := "This case clause will never be evaluated because it duplicates an earlier case clause" if couldBeIncorrect { text = "This case clause may never be evaluated because it likely duplicates an earlier case clause" } if !p.suppressWarningsAboutWeirdCode { p.log.AddRangeWarning(&p.tracker, r, text) } else { p.log.AddRangeDebug(&p.tracker, r, text) } } return } } } *entry |= mask dc.cases = append(dc.cases, duplicateCaseValue{hash: hash, value: expr}) } } func duplicateCaseHash(expr js_ast.Expr) (uint32, bool) { switch e := expr.Data.(type) { case *js_ast.ENull: return 0, true case *js_ast.EUndefined: return 1, true case *js_ast.EBoolean: if e.Value { return helpers.HashCombine(2, 1), true } return helpers.HashCombine(2, 0), true case *js_ast.ENumber: bits := math.Float64bits(e.Value) return helpers.HashCombine(helpers.HashCombine(3, uint32(bits)), uint32(bits>>32)), true case *js_ast.EString: hash := uint32(4) for _, c := range e.Value { hash = helpers.HashCombine(hash, uint32(c)) } return hash, true case *js_ast.EBigInt: hash := uint32(5) for _, c := range e.Value { hash = helpers.HashCombine(hash, uint32(c)) } return hash, true case *js_ast.EIdentifier: return helpers.HashCombine(6, e.Ref.InnerIndex), true case *js_ast.EDot: if target, ok := duplicateCaseHash(e.Target); ok { return helpers.HashCombineString(helpers.HashCombine(7, target), e.Name), true } case *js_ast.EIndex: if target, ok := duplicateCaseHash(e.Target); ok { if index, ok := duplicateCaseHash(e.Index); ok { return helpers.HashCombine(helpers.HashCombine(8, target), index), true } } } return 0, false } func duplicateCaseEquals(left js_ast.Expr, right js_ast.Expr) (equals bool, couldBeIncorrect bool) { switch a := left.Data.(type) { case *js_ast.ENull: _, ok := right.Data.(*js_ast.ENull) return ok, false case *js_ast.EUndefined: _, ok := right.Data.(*js_ast.EUndefined) return ok, false case *js_ast.EBoolean: b, ok := right.Data.(*js_ast.EBoolean) return ok && a.Value == b.Value, false case *js_ast.ENumber: b, ok := right.Data.(*js_ast.ENumber) return ok && a.Value == b.Value, false case *js_ast.EString: b, ok := right.Data.(*js_ast.EString) return ok && js_lexer.UTF16EqualsUTF16(a.Value, b.Value), false case *js_ast.EBigInt: b, ok := right.Data.(*js_ast.EBigInt) return ok && a.Value == b.Value, false case *js_ast.EIdentifier: b, ok := right.Data.(*js_ast.EIdentifier) return ok && a.Ref == b.Ref, false case *js_ast.EDot: if b, ok := right.Data.(*js_ast.EDot); ok && a.OptionalChain == b.OptionalChain && a.Name == b.Name { equals, _ := duplicateCaseEquals(a.Target, b.Target) return equals, true } case *js_ast.EIndex: if b, ok := right.Data.(*js_ast.EIndex); ok && a.OptionalChain == b.OptionalChain { if equals, _ := duplicateCaseEquals(a.Index, b.Index); equals { equals, _ := duplicateCaseEquals(a.Target, b.Target) return equals, true } } } return false, false } func isJumpStatement(data js_ast.S) bool { switch data.(type) { case *js_ast.SBreak, *js_ast.SContinue, *js_ast.SReturn, *js_ast.SThrow: return true } return false } func isPrimitiveToReorder(e js_ast.E) bool { switch e.(type) { case *js_ast.ENull, *js_ast.EUndefined, *js_ast.EString, *js_ast.EBoolean, *js_ast.ENumber, *js_ast.EBigInt: return true } return false } type sideEffects uint8 const ( couldHaveSideEffects sideEffects = iota noSideEffects ) func toNullOrUndefinedWithSideEffects(data js_ast.E) (isNullOrUndefined bool, sideEffects sideEffects, ok bool) { switch e := data.(type) { // Never null or undefined case *js_ast.EBoolean, *js_ast.ENumber, *js_ast.EString, *js_ast.ERegExp, *js_ast.EFunction, *js_ast.EArrow, *js_ast.EBigInt: return false, noSideEffects, true // Never null or undefined case *js_ast.EObject, *js_ast.EArray, *js_ast.EClass: return false, couldHaveSideEffects, true // Always null or undefined case *js_ast.ENull, *js_ast.EUndefined: return true, noSideEffects, true case *js_ast.EUnary: switch e.Op { case // Always number or bigint js_ast.UnOpPos, js_ast.UnOpNeg, js_ast.UnOpCpl, js_ast.UnOpPreDec, js_ast.UnOpPreInc, js_ast.UnOpPostDec, js_ast.UnOpPostInc, // Always boolean js_ast.UnOpNot, js_ast.UnOpTypeof, js_ast.UnOpDelete: return false, couldHaveSideEffects, true // Always undefined case js_ast.UnOpVoid: return true, couldHaveSideEffects, true } case *js_ast.EBinary: switch e.Op { case // Always string or number or bigint js_ast.BinOpAdd, js_ast.BinOpAddAssign, // Always number or bigint js_ast.BinOpSub, js_ast.BinOpMul, js_ast.BinOpDiv, js_ast.BinOpRem, js_ast.BinOpPow, js_ast.BinOpSubAssign, js_ast.BinOpMulAssign, js_ast.BinOpDivAssign, js_ast.BinOpRemAssign, js_ast.BinOpPowAssign, js_ast.BinOpShl, js_ast.BinOpShr, js_ast.BinOpUShr, js_ast.BinOpShlAssign, js_ast.BinOpShrAssign, js_ast.BinOpUShrAssign, js_ast.BinOpBitwiseOr, js_ast.BinOpBitwiseAnd, js_ast.BinOpBitwiseXor, js_ast.BinOpBitwiseOrAssign, js_ast.BinOpBitwiseAndAssign, js_ast.BinOpBitwiseXorAssign, // Always boolean js_ast.BinOpLt, js_ast.BinOpLe, js_ast.BinOpGt, js_ast.BinOpGe, js_ast.BinOpIn, js_ast.BinOpInstanceof, js_ast.BinOpLooseEq, js_ast.BinOpLooseNe, js_ast.BinOpStrictEq, js_ast.BinOpStrictNe: return false, couldHaveSideEffects, true case js_ast.BinOpComma: if isNullOrUndefined, _, ok := toNullOrUndefinedWithSideEffects(e.Right.Data); ok { return isNullOrUndefined, couldHaveSideEffects, true } } } return false, noSideEffects, false } func toBooleanWithSideEffects(data js_ast.E) (boolean bool, sideEffects sideEffects, ok bool) { switch e := data.(type) { case *js_ast.ENull, *js_ast.EUndefined: return false, noSideEffects, true case *js_ast.EBoolean: return e.Value, noSideEffects, true case *js_ast.ENumber: return e.Value != 0 && !math.IsNaN(e.Value), noSideEffects, true case *js_ast.EBigInt: return e.Value != "0", noSideEffects, true case *js_ast.EString: return len(e.Value) > 0, noSideEffects, true case *js_ast.EFunction, *js_ast.EArrow, *js_ast.ERegExp: return true, noSideEffects, true case *js_ast.EObject, *js_ast.EArray, *js_ast.EClass: return true, couldHaveSideEffects, true case *js_ast.EUnary: switch e.Op { case js_ast.UnOpVoid: return false, couldHaveSideEffects, true case js_ast.UnOpTypeof: // Never an empty string return true, couldHaveSideEffects, true case js_ast.UnOpNot: if boolean, sideEffects, ok := toBooleanWithSideEffects(e.Value.Data); ok { return !boolean, sideEffects, true } } case *js_ast.EBinary: switch e.Op { case js_ast.BinOpLogicalOr: // "anything || truthy" is truthy if boolean, _, ok := toBooleanWithSideEffects(e.Right.Data); ok && boolean { return true, couldHaveSideEffects, true } case js_ast.BinOpLogicalAnd: // "anything && falsy" is falsy if boolean, _, ok := toBooleanWithSideEffects(e.Right.Data); ok && !boolean { return false, couldHaveSideEffects, true } case js_ast.BinOpComma: // "anything, truthy/falsy" is truthy/falsy if boolean, _, ok := toBooleanWithSideEffects(e.Right.Data); ok { return boolean, couldHaveSideEffects, true } } } return false, couldHaveSideEffects, false } func toNumberWithoutSideEffects(data js_ast.E) (float64, bool) { switch e := data.(type) { case *js_ast.ENull: return 0, true case *js_ast.EUndefined: return math.NaN(), true case *js_ast.EBoolean: if e.Value { return 1, true } else { return 0, true } case *js_ast.ENumber: return e.Value, true } return 0, false } // Returns true if the result of the "typeof" operator on this expression is // statically determined and this expression has no side effects (i.e. can be // removed without consequence). func typeofWithoutSideEffects(data js_ast.E) (string, bool) { switch data.(type) { case *js_ast.ENull: return "object", true case *js_ast.EUndefined: return "undefined", true case *js_ast.EBoolean: return "boolean", true case *js_ast.ENumber: return "number", true case *js_ast.EBigInt: return "bigint", true case *js_ast.EString: return "string", true case *js_ast.EFunction, *js_ast.EArrow: return "function", true } return "", false } // Returns true if this expression is known to result in a primitive value (i.e. // null, undefined, boolean, number, bigint, or string), even if the expression // cannot be removed due to side effects. func isPrimitiveWithSideEffects(data js_ast.E) bool { switch e := data.(type) { case *js_ast.ENull, *js_ast.EUndefined, *js_ast.EBoolean, *js_ast.ENumber, *js_ast.EBigInt, *js_ast.EString: return true case *js_ast.EUnary: switch e.Op { case // Number or bigint js_ast.UnOpPos, js_ast.UnOpNeg, js_ast.UnOpCpl, js_ast.UnOpPreDec, js_ast.UnOpPreInc, js_ast.UnOpPostDec, js_ast.UnOpPostInc, // Boolean js_ast.UnOpNot, js_ast.UnOpDelete, // Undefined js_ast.UnOpVoid, // String js_ast.UnOpTypeof: return true } case *js_ast.EBinary: switch e.Op { case // Boolean js_ast.BinOpLt, js_ast.BinOpLe, js_ast.BinOpGt, js_ast.BinOpGe, js_ast.BinOpIn, js_ast.BinOpInstanceof, js_ast.BinOpLooseEq, js_ast.BinOpLooseNe, js_ast.BinOpStrictEq, js_ast.BinOpStrictNe, // String, number, or bigint js_ast.BinOpAdd, js_ast.BinOpAddAssign, // Number or bigint js_ast.BinOpSub, js_ast.BinOpMul, js_ast.BinOpDiv, js_ast.BinOpRem, js_ast.BinOpPow, js_ast.BinOpSubAssign, js_ast.BinOpMulAssign, js_ast.BinOpDivAssign, js_ast.BinOpRemAssign, js_ast.BinOpPowAssign, js_ast.BinOpShl, js_ast.BinOpShr, js_ast.BinOpUShr, js_ast.BinOpShlAssign, js_ast.BinOpShrAssign, js_ast.BinOpUShrAssign, js_ast.BinOpBitwiseOr, js_ast.BinOpBitwiseAnd, js_ast.BinOpBitwiseXor, js_ast.BinOpBitwiseOrAssign, js_ast.BinOpBitwiseAndAssign, js_ast.BinOpBitwiseXorAssign: return true // These always return one of the arguments unmodified case js_ast.BinOpLogicalAnd, js_ast.BinOpLogicalOr, js_ast.BinOpNullishCoalescing, js_ast.BinOpLogicalAndAssign, js_ast.BinOpLogicalOrAssign, js_ast.BinOpNullishCoalescingAssign: return isPrimitiveWithSideEffects(e.Left.Data) && isPrimitiveWithSideEffects(e.Right.Data) case js_ast.BinOpComma: return isPrimitiveWithSideEffects(e.Right.Data) } case *js_ast.EIf: return isPrimitiveWithSideEffects(e.Yes.Data) && isPrimitiveWithSideEffects(e.No.Data) } return false } // Returns "equal, ok". If "ok" is false, then nothing is known about the two // values. If "ok" is true, the equality or inequality of the two values is // stored in "equal". func checkEqualityIfNoSideEffects(left js_ast.E, right js_ast.E) (bool, bool) { switch l := left.(type) { case *js_ast.ENull: _, ok := right.(*js_ast.ENull) return ok, ok case *js_ast.EUndefined: _, ok := right.(*js_ast.EUndefined) return ok, ok case *js_ast.EBoolean: r, ok := right.(*js_ast.EBoolean) return ok && l.Value == r.Value, ok case *js_ast.ENumber: r, ok := right.(*js_ast.ENumber) return ok && l.Value == r.Value, ok case *js_ast.EBigInt: r, ok := right.(*js_ast.EBigInt) return ok && l.Value == r.Value, ok case *js_ast.EString: r, ok := right.(*js_ast.EString) return ok && js_lexer.UTF16EqualsUTF16(l.Value, r.Value), ok } return false, false } func valuesLookTheSame(left js_ast.E, right js_ast.E) bool { switch a := left.(type) { case *js_ast.EIdentifier: if b, ok := right.(*js_ast.EIdentifier); ok && a.Ref == b.Ref { return true } case *js_ast.EDot: if b, ok := right.(*js_ast.EDot); ok && a.HasSameFlagsAs(b) && a.Name == b.Name && valuesLookTheSame(a.Target.Data, b.Target.Data) { return true } case *js_ast.EIndex: if b, ok := right.(*js_ast.EIndex); ok && a.HasSameFlagsAs(b) && valuesLookTheSame(a.Target.Data, b.Target.Data) && valuesLookTheSame(a.Index.Data, b.Index.Data) { return true } case *js_ast.EIf: if b, ok := right.(*js_ast.EIf); ok && valuesLookTheSame(a.Test.Data, b.Test.Data) && valuesLookTheSame(a.Yes.Data, b.Yes.Data) && valuesLookTheSame(a.No.Data, b.No.Data) { return true } case *js_ast.EUnary: if b, ok := right.(*js_ast.EUnary); ok && a.Op == b.Op && valuesLookTheSame(a.Value.Data, b.Value.Data) { return true } case *js_ast.EBinary: if b, ok := right.(*js_ast.EBinary); ok && a.Op == b.Op && valuesLookTheSame(a.Left.Data, b.Left.Data) && valuesLookTheSame(a.Right.Data, b.Right.Data) { return true } case *js_ast.ECall: if b, ok := right.(*js_ast.ECall); ok && a.HasSameFlagsAs(b) && len(a.Args) == len(b.Args) && valuesLookTheSame(a.Target.Data, b.Target.Data) { for i := range a.Args { if !valuesLookTheSame(a.Args[i].Data, b.Args[i].Data) { return false } } return true } // Special-case to distinguish between negative an non-negative zero when mangling // "a ? -0 : 0" => "a ? -0 : 0" // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness case *js_ast.ENumber: b, ok := right.(*js_ast.ENumber) if ok && a.Value == 0 && b.Value == 0 && math.Signbit(a.Value) != math.Signbit(b.Value) { return false } } equal, ok := checkEqualityIfNoSideEffects(left, right) return ok && equal } func jumpStmtsLookTheSame(left js_ast.S, right js_ast.S) bool { switch a := left.(type) { case *js_ast.SBreak: b, ok := right.(*js_ast.SBreak) return ok && (a.Label == nil) == (b.Label == nil) && (a.Label == nil || a.Label.Ref == b.Label.Ref) case *js_ast.SContinue: b, ok := right.(*js_ast.SContinue) return ok && (a.Label == nil) == (b.Label == nil) && (a.Label == nil || a.Label.Ref == b.Label.Ref) case *js_ast.SReturn: b, ok := right.(*js_ast.SReturn) return ok && (a.ValueOrNil.Data == nil) == (b.ValueOrNil.Data == nil) && (a.ValueOrNil.Data == nil || valuesLookTheSame(a.ValueOrNil.Data, b.ValueOrNil.Data)) case *js_ast.SThrow: b, ok := right.(*js_ast.SThrow) return ok && valuesLookTheSame(a.Value.Data, b.Value.Data) } return false } func hasValueForThisInCall(expr js_ast.Expr) bool { switch expr.Data.(type) { case *js_ast.EDot, *js_ast.EIndex: return true default: return false } } func (p *parser) selectLocalKind(kind js_ast.LocalKind) js_ast.LocalKind { // Safari workaround: Automatically avoid TDZ issues when bundling if p.options.mode == config.ModeBundle && p.currentScope.Parent == nil { return js_ast.LocalVar } // Optimization: use "let" instead of "const" because it's shorter. This is // only done when bundling because assigning to "const" is only an error when // bundling. if p.options.mode == config.ModeBundle && kind == js_ast.LocalConst && p.options.mangleSyntax { return js_ast.LocalLet } return kind } func (p *parser) pushScopeForParsePass(kind js_ast.ScopeKind, loc logger.Loc) int { parent := p.currentScope scope := &js_ast.Scope{ Kind: kind, Parent: parent, Members: make(map[string]js_ast.ScopeMember), Label: js_ast.LocRef{Ref: js_ast.InvalidRef}, } if parent != nil { parent.Children = append(parent.Children, scope) scope.StrictMode = parent.StrictMode scope.UseStrictLoc = parent.UseStrictLoc } p.currentScope = scope // Enforce that scope locations are strictly increasing to help catch bugs // where the pushed scopes are mistmatched between the first and second passes if len(p.scopesInOrder) > 0 { prevStart := p.scopesInOrder[len(p.scopesInOrder)-1].loc.Start if prevStart >= loc.Start { panic(fmt.Sprintf("Scope location %d must be greater than %d", loc.Start, prevStart)) } } // Copy down function arguments into the function body scope. That way we get // errors if a statement in the function body tries to re-declare any of the // arguments. if kind == js_ast.ScopeFunctionBody { if scope.Parent.Kind != js_ast.ScopeFunctionArgs { panic("Internal error") } for name, member := range scope.Parent.Members { // Don't copy down the optional function expression name. Re-declaring // the name of a function expression is allowed. kind := p.symbols[member.Ref.InnerIndex].Kind if kind != js_ast.SymbolHoistedFunction { scope.Members[name] = member } } } // Remember the length in case we call popAndDiscardScope() later scopeIndex := len(p.scopesInOrder) p.scopesInOrder = append(p.scopesInOrder, scopeOrder{loc, scope}) return scopeIndex } func (p *parser) popScope() { // We cannot rename anything inside a scope containing a direct eval() call if p.currentScope.ContainsDirectEval { for _, member := range p.currentScope.Members { // Using direct eval when bundling is not a good idea in general because // esbuild must assume that it can potentially reach anything in any of // the containing scopes. We try to make it work but this isn't possible // in some cases. // // For example, symbols imported using an ESM import are a live binding // to the underlying symbol in another file. This is emulated during // scope hoisting by erasing the ESM import and just referencing the // underlying symbol in the flattened bundle directly. However, that // symbol may have a different name which could break uses of direct // eval: // // // Before bundling // import { foo as bar } from './foo.js' // console.log(eval('bar')) // // // After bundling // let foo = 123 // The contents of "foo.js" // console.log(eval('bar')) // // There really isn't any way to fix this. You can't just rename "foo" to // "bar" in the example above because there may be a third bundled file // that also contains direct eval and imports the same symbol with a // different conflicting import alias. And there is no way to store a // live binding to the underlying symbol in a variable with the import's // name so that direct eval can access it: // // // After bundling // let foo = 123 // The contents of "foo.js" // const bar = /* cannot express a live binding to "foo" here */ // console.log(eval('bar')) // // Technically a "with" statement could potentially make this work (with // a big hit to performance), but they are deprecated and are unavailable // in strict mode. This is a non-starter since all ESM code is strict mode. // // So while we still try to obey the requirement that all symbol names are // pinned when direct eval is present, we make an exception for top-level // symbols in an ESM file when bundling is enabled. We make no guarantee // that "eval" will be able to reach these symbols and we allow them to be // renamed or removed by tree shaking. if p.options.mode == config.ModeBundle && p.currentScope.Parent == nil && p.hasESModuleSyntax { continue } p.symbols[member.Ref.InnerIndex].MustNotBeRenamed = true } } p.currentScope = p.currentScope.Parent } func (p *parser) popAndDiscardScope(scopeIndex int) { // Move up to the parent scope toDiscard := p.currentScope parent := toDiscard.Parent p.currentScope = parent // Truncate the scope order where we started to pretend we never saw this scope p.scopesInOrder = p.scopesInOrder[:scopeIndex] // Remove the last child from the parent scope last := len(parent.Children) - 1 if parent.Children[last] != toDiscard { panic("Internal error") } parent.Children = parent.Children[:last] } func (p *parser) popAndFlattenScope(scopeIndex int) { // Move up to the parent scope toFlatten := p.currentScope parent := toFlatten.Parent p.currentScope = parent // Erase this scope from the order. This will shift over the indices of all // the scopes that were created after us. However, we shouldn't have to // worry about other code with outstanding scope indices for these scopes. // These scopes were all created in between this scope's push and pop // operations, so they should all be child scopes and should all be popped // by the time we get here. copy(p.scopesInOrder[scopeIndex:], p.scopesInOrder[scopeIndex+1:]) p.scopesInOrder = p.scopesInOrder[:len(p.scopesInOrder)-1] // Remove the last child from the parent scope last := len(parent.Children) - 1 if parent.Children[last] != toFlatten { panic("Internal error") } parent.Children = parent.Children[:last] // Reparent our child scopes into our parent for _, scope := range toFlatten.Children { scope.Parent = parent parent.Children = append(parent.Children, scope) } } // Undo all scopes pushed and popped after this scope index. This assumes that // the scope stack is at the same level now as it was at the given scope index. func (p *parser) discardScopesUpTo(scopeIndex int) { // Remove any direct children from their parent children := p.currentScope.Children for _, child := range p.scopesInOrder[scopeIndex:] { if child.scope.Parent == p.currentScope { for i := len(children) - 1; i >= 0; i-- { if children[i] == child.scope { children = append(children[:i], children[i+1:]...) break } } } } p.currentScope.Children = children // Truncate the scope order where we started to pretend we never saw this scope p.scopesInOrder = p.scopesInOrder[:scopeIndex] } func (p *parser) newSymbol(kind js_ast.SymbolKind, name string) js_ast.Ref { ref := js_ast.Ref{SourceIndex: p.source.Index, InnerIndex: uint32(len(p.symbols))} p.symbols = append(p.symbols, js_ast.Symbol{ Kind: kind, OriginalName: name, Link: js_ast.InvalidRef, }) if p.options.ts.Parse { p.tsUseCounts = append(p.tsUseCounts, 0) } return ref } // This is similar to "js_ast.MergeSymbols" but it works with this parser's // one-level symbol map instead of the linker's two-level symbol map. It also // doesn't handle cycles since they shouldn't come up due to the way this // function is used. func (p *parser) mergeSymbols(old js_ast.Ref, new js_ast.Ref) { oldSymbol := &p.symbols[old.InnerIndex] newSymbol := &p.symbols[new.InnerIndex] oldSymbol.Link = new newSymbol.UseCountEstimate += oldSymbol.UseCountEstimate if oldSymbol.MustNotBeRenamed { newSymbol.MustNotBeRenamed = true } } type mergeResult int const ( mergeForbidden = iota mergeReplaceWithNew mergeOverwriteWithNew mergeKeepExisting mergeBecomePrivateGetSetPair mergeBecomePrivateStaticGetSetPair ) func (p *parser) canMergeSymbols(scope *js_ast.Scope, existing js_ast.SymbolKind, new js_ast.SymbolKind) mergeResult { if existing == js_ast.SymbolUnbound { return mergeReplaceWithNew } // In TypeScript, imports are allowed to silently collide with symbols within // the module. Presumably this is because the imports may be type-only: // // import {Foo} from 'bar' // class Foo {} // if p.options.ts.Parse && existing == js_ast.SymbolImport { return mergeReplaceWithNew } // "enum Foo {} enum Foo {}" // "namespace Foo { ... } enum Foo {}" if new == js_ast.SymbolTSEnum && (existing == js_ast.SymbolTSEnum || existing == js_ast.SymbolTSNamespace) { return mergeReplaceWithNew } // "namespace Foo { ... } namespace Foo { ... }" // "function Foo() {} namespace Foo { ... }" // "enum Foo {} namespace Foo { ... }" if new == js_ast.SymbolTSNamespace { switch existing { case js_ast.SymbolTSNamespace, js_ast.SymbolHoistedFunction, js_ast.SymbolGeneratorOrAsyncFunction, js_ast.SymbolTSEnum, js_ast.SymbolClass: return mergeKeepExisting } } // "var foo; var foo;" // "var foo; function foo() {}" // "function foo() {} var foo;" // "function *foo() {} function *foo() {}" but not "{ function *foo() {} function *foo() {} }" if new.IsHoistedOrFunction() && existing.IsHoistedOrFunction() && (scope.Kind == js_ast.ScopeEntry || scope.Kind == js_ast.ScopeFunctionBody || (new.IsHoisted() && existing.IsHoisted())) { return mergeKeepExisting } // "get #foo() {} set #foo() {}" // "set #foo() {} get #foo() {}" if (existing == js_ast.SymbolPrivateGet && new == js_ast.SymbolPrivateSet) || (existing == js_ast.SymbolPrivateSet && new == js_ast.SymbolPrivateGet) { return mergeBecomePrivateGetSetPair } if (existing == js_ast.SymbolPrivateStaticGet && new == js_ast.SymbolPrivateStaticSet) || (existing == js_ast.SymbolPrivateStaticSet && new == js_ast.SymbolPrivateStaticGet) { return mergeBecomePrivateStaticGetSetPair } // "try {} catch (e) { var e }" if existing == js_ast.SymbolCatchIdentifier && new == js_ast.SymbolHoisted { return mergeReplaceWithNew } // "function() { var arguments }" if existing == js_ast.SymbolArguments && new == js_ast.SymbolHoisted { return mergeKeepExisting } // "function() { let arguments }" if existing == js_ast.SymbolArguments && new != js_ast.SymbolHoisted { return mergeOverwriteWithNew } return mergeForbidden } func (p *parser) declareSymbol(kind js_ast.SymbolKind, loc logger.Loc, name string) js_ast.Ref { p.checkForUnrepresentableIdentifier(loc, name) // Allocate a new symbol ref := p.newSymbol(kind, name) // Check for a collision in the declaring scope if existing, ok := p.currentScope.Members[name]; ok { symbol := &p.symbols[existing.Ref.InnerIndex] switch p.canMergeSymbols(p.currentScope, symbol.Kind, kind) { case mergeForbidden: r := js_lexer.RangeOfIdentifier(p.source, loc) p.log.AddRangeErrorWithNotes(&p.tracker, r, fmt.Sprintf("%q has already been declared", name), []logger.MsgData{logger.RangeData(&p.tracker, js_lexer.RangeOfIdentifier(p.source, existing.Loc), fmt.Sprintf("%q was originally declared here", name))}) return existing.Ref case mergeKeepExisting: ref = existing.Ref case mergeReplaceWithNew: symbol.Link = ref case mergeBecomePrivateGetSetPair: ref = existing.Ref symbol.Kind = js_ast.SymbolPrivateGetSetPair case mergeBecomePrivateStaticGetSetPair: ref = existing.Ref symbol.Kind = js_ast.SymbolPrivateStaticGetSetPair case mergeOverwriteWithNew: } } // Overwrite this name in the declaring scope p.currentScope.Members[name] = js_ast.ScopeMember{Ref: ref, Loc: loc} return ref } func (p *parser) hoistSymbols(scope *js_ast.Scope) { if !scope.Kind.StopsHoisting() { nextMember: for _, member := range scope.Members { symbol := &p.symbols[member.Ref.InnerIndex] if !symbol.Kind.IsHoisted() { continue } // Implement "Block-Level Function Declarations Web Legacy Compatibility // Semantics" from Annex B of the ECMAScript standard version 6+ isSloppyModeBlockLevelFnStmt := false originalMemberRef := member.Ref if symbol.Kind == js_ast.SymbolHoistedFunction { // Block-level function declarations behave like "let" in strict mode if scope.StrictMode != js_ast.SloppyMode { continue } // In sloppy mode, block level functions behave like "let" except with // an assignment to "var", sort of. This code: // // if (x) { // f(); // function f() {} // } // f(); // // behaves like this code: // // if (x) { // let f2 = function() {} // var f = f2; // f2(); // } // f(); // hoistedRef := p.newSymbol(js_ast.SymbolHoisted, symbol.OriginalName) scope.Generated = append(scope.Generated, hoistedRef) if p.hoistedRefForSloppyModeBlockFn == nil { p.hoistedRefForSloppyModeBlockFn = make(map[js_ast.Ref]js_ast.Ref) } p.hoistedRefForSloppyModeBlockFn[member.Ref] = hoistedRef symbol = &p.symbols[hoistedRef.InnerIndex] member.Ref = hoistedRef isSloppyModeBlockLevelFnStmt = true } // Check for collisions that would prevent to hoisting "var" symbols up to the enclosing function scope s := scope.Parent for { // Variable declarations hoisted past a "with" statement may actually end // up overwriting a property on the target of the "with" statement instead // of initializing the variable. We must not rename them or we risk // causing a behavior change. // // var obj = { foo: 1 } // with (obj) { var foo = 2 } // assert(foo === undefined) // assert(obj.foo === 2) // if s.Kind == js_ast.ScopeWith { symbol.MustNotBeRenamed = true } if existingMember, ok := s.Members[symbol.OriginalName]; ok { existingSymbol := &p.symbols[existingMember.Ref.InnerIndex] // We can hoist the symbol from the child scope into the symbol in // this scope if: // // - The symbol is unbound (i.e. a global variable access) // - The symbol is also another hoisted variable // - The symbol is a function of any kind and we're in a function or module scope // // Is this unbound (i.e. a global access) or also hoisted? if existingSymbol.Kind == js_ast.SymbolUnbound || existingSymbol.Kind == js_ast.SymbolHoisted || (existingSymbol.Kind.IsFunction() && (s.Kind == js_ast.ScopeEntry || s.Kind == js_ast.ScopeFunctionBody)) { // Silently merge this symbol into the existing symbol symbol.Link = existingMember.Ref s.Members[symbol.OriginalName] = existingMember continue nextMember } // Otherwise if this isn't a catch identifier, it's a collision if existingSymbol.Kind != js_ast.SymbolCatchIdentifier { // An identifier binding from a catch statement and a function // declaration can both silently shadow another hoisted symbol if symbol.Kind != js_ast.SymbolCatchIdentifier && symbol.Kind != js_ast.SymbolHoistedFunction { if !isSloppyModeBlockLevelFnStmt { r := js_lexer.RangeOfIdentifier(p.source, member.Loc) p.log.AddRangeErrorWithNotes(&p.tracker, r, fmt.Sprintf("%q has already been declared", symbol.OriginalName), []logger.MsgData{logger.RangeData(&p.tracker, js_lexer.RangeOfIdentifier(p.source, existingMember.Loc), fmt.Sprintf("%q was originally declared here", symbol.OriginalName))}) } else if s == scope.Parent { // Never mind about this, turns out it's not needed after all delete(p.hoistedRefForSloppyModeBlockFn, originalMemberRef) } } continue nextMember } // If this is a catch identifier, silently merge the existing symbol // into this symbol but continue hoisting past this catch scope existingSymbol.Link = member.Ref s.Members[symbol.OriginalName] = member } if s.Kind.StopsHoisting() { // Declare the member in the scope that stopped the hoisting s.Members[symbol.OriginalName] = member break } s = s.Parent } } } for _, child := range scope.Children { p.hoistSymbols(child) } } func (p *parser) declareBinding(kind js_ast.SymbolKind, binding js_ast.Binding, opts parseStmtOpts) { switch b := binding.Data.(type) { case *js_ast.BMissing: case *js_ast.BIdentifier: name := p.loadNameFromRef(b.Ref) if !opts.isTypeScriptDeclare || (opts.isNamespaceScope && opts.isExport) { b.Ref = p.declareSymbol(kind, binding.Loc, name) } case *js_ast.BArray: for _, i := range b.Items { p.declareBinding(kind, i.Binding, opts) } case *js_ast.BObject: for _, property := range b.Properties { p.declareBinding(kind, property.Value, opts) } default: panic("Internal error") } } func (p *parser) recordUsage(ref js_ast.Ref) { // The use count stored in the symbol is used for generating symbol names // during minification. These counts shouldn't include references inside dead // code regions since those will be culled. if !p.isControlFlowDead { p.symbols[ref.InnerIndex].UseCountEstimate++ use := p.symbolUses[ref] use.CountEstimate++ p.symbolUses[ref] = use } // The correctness of TypeScript-to-JavaScript conversion relies on accurate // symbol use counts for the whole file, including dead code regions. This is // tracked separately in a parser-only data structure. if p.options.ts.Parse { p.tsUseCounts[ref.InnerIndex]++ } } func (p *parser) ignoreUsage(ref js_ast.Ref) { // Roll back the use count increment in recordUsage() if !p.isControlFlowDead { p.symbols[ref.InnerIndex].UseCountEstimate-- use := p.symbolUses[ref] use.CountEstimate-- if use.CountEstimate == 0 { delete(p.symbolUses, ref) } else { p.symbolUses[ref] = use } } // Don't roll back the "tsUseCounts" increment. This must be counted even if // the value is ignored because that's what the TypeScript compiler does. } func (p *parser) importFromRuntime(loc logger.Loc, name string) js_ast.Expr { ref, ok := p.runtimeImports[name] if !ok { ref = p.newSymbol(js_ast.SymbolOther, name) p.moduleScope.Generated = append(p.moduleScope.Generated, ref) p.runtimeImports[name] = ref } p.recordUsage(ref) return js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}} } func (p *parser) callRuntime(loc logger.Loc, name string, args []js_ast.Expr) js_ast.Expr { return js_ast.Expr{Loc: loc, Data: &js_ast.ECall{ Target: p.importFromRuntime(loc, name), Args: args, }} } func (p *parser) valueToSubstituteForRequire(loc logger.Loc) js_ast.Expr { if p.source.Index != runtime.SourceIndex && config.ShouldCallRuntimeRequire(p.options.mode, p.options.outputFormat) { return p.importFromRuntime(loc, "__require") } p.recordUsage(p.requireRef) return js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: p.requireRef}} } func (p *parser) makePromiseRef() js_ast.Ref { if p.promiseRef == js_ast.InvalidRef { p.promiseRef = p.newSymbol(js_ast.SymbolUnbound, "Promise") } return p.promiseRef } // The name is temporarily stored in the ref until the scope traversal pass // happens, at which point a symbol will be generated and the ref will point // to the symbol instead. // // The scope traversal pass will reconstruct the name using one of two methods. // In the common case, the name is a slice of the file itself. In that case we // can just store the slice and not need to allocate any extra memory. In the // rare case, the name is an externally-allocated string. In that case we store // an index to the string and use that index during the scope traversal pass. func (p *parser) storeNameInRef(name string) js_ast.Ref { c := (*reflect.StringHeader)(unsafe.Pointer(&p.source.Contents)) n := (*reflect.StringHeader)(unsafe.Pointer(&name)) // Is the data in "name" a subset of the data in "p.source.Contents"? if n.Data >= c.Data && n.Data+uintptr(n.Len) < c.Data+uintptr(c.Len) { // The name is a slice of the file contents, so we can just reference it by // length and don't have to allocate anything. This is the common case. // // It's stored as a negative value so we'll crash if we try to use it. That // way we'll catch cases where we've forgotten to call loadNameFromRef(). // The length is the negative part because we know it's non-zero. return js_ast.Ref{SourceIndex: -uint32(n.Len), InnerIndex: uint32(n.Data - c.Data)} } else { // The name is some memory allocated elsewhere. This is either an inline // string constant in the parser or an identifier with escape sequences // in the source code, which is very unusual. Stash it away for later. // This uses allocations but it should hopefully be very uncommon. ref := js_ast.Ref{SourceIndex: 0x80000000, InnerIndex: uint32(len(p.allocatedNames))} p.allocatedNames = append(p.allocatedNames, name) return ref } } // This is the inverse of storeNameInRef() above func (p *parser) loadNameFromRef(ref js_ast.Ref) string { if ref.SourceIndex == 0x80000000 { return p.allocatedNames[ref.InnerIndex] } else { if (ref.SourceIndex & 0x80000000) == 0 { panic("Internal error: invalid symbol reference") } return p.source.Contents[ref.InnerIndex : int32(ref.InnerIndex)-int32(ref.SourceIndex)] } } // Due to ES6 destructuring patterns, there are many cases where it's // impossible to distinguish between an array or object literal and a // destructuring assignment until we hit the "=" operator later on. // This object defers errors about being in one state or the other // until we discover which state we're in. type deferredErrors struct { // These are errors for expressions invalidExprDefaultValue logger.Range invalidExprAfterQuestion logger.Range arraySpreadFeature logger.Range } func (from *deferredErrors) mergeInto(to *deferredErrors) { if from.invalidExprDefaultValue.Len > 0 { to.invalidExprDefaultValue = from.invalidExprDefaultValue } if from.invalidExprAfterQuestion.Len > 0 { to.invalidExprAfterQuestion = from.invalidExprAfterQuestion } if from.arraySpreadFeature.Len > 0 { to.arraySpreadFeature = from.arraySpreadFeature } } func (p *parser) logExprErrors(errors *deferredErrors) { if errors.invalidExprDefaultValue.Len > 0 { p.log.AddRangeError(&p.tracker, errors.invalidExprDefaultValue, "Unexpected \"=\"") } if errors.invalidExprAfterQuestion.Len > 0 { r := errors.invalidExprAfterQuestion p.log.AddRangeError(&p.tracker, r, fmt.Sprintf("Unexpected %q", p.source.Contents[r.Loc.Start:r.Loc.Start+r.Len])) } if errors.arraySpreadFeature.Len > 0 { p.markSyntaxFeature(compat.ArraySpread, errors.arraySpreadFeature) } } // The "await" and "yield" expressions are never allowed in argument lists but // may or may not be allowed otherwise depending on the details of the enclosing // function or module. This needs to be handled when parsing an arrow function // argument list because we don't know if these expressions are not allowed until // we reach the "=>" token (or discover the absence of one). // // Specifically, for await: // // // This is ok // async function foo() { (x = await y) } // // // This is an error // async function foo() { (x = await y) => {} } // // And for yield: // // // This is ok // function* foo() { (x = yield y) } // // // This is an error // function* foo() { (x = yield y) => {} } // type deferredArrowArgErrors struct { invalidExprAwait logger.Range invalidExprYield logger.Range } func (p *parser) logArrowArgErrors(errors *deferredArrowArgErrors) { if errors.invalidExprAwait.Len > 0 { r := errors.invalidExprAwait p.log.AddRangeError(&p.tracker, r, "Cannot use an \"await\" expression here") } if errors.invalidExprYield.Len > 0 { r := errors.invalidExprYield p.log.AddRangeError(&p.tracker, r, "Cannot use a \"yield\" expression here") } } func (p *parser) keyNameForError(key js_ast.Expr) string { switch k := key.Data.(type) { case *js_ast.EString: return fmt.Sprintf("%q", js_lexer.UTF16ToString(k.Value)) case *js_ast.EPrivateIdentifier: return fmt.Sprintf("%q", p.loadNameFromRef(k.Ref)) } return "property" } func (p *parser) checkForLegacyOctalLiteral(e js_ast.E) { if p.lexer.IsLegacyOctalLiteral { if p.legacyOctalLiterals == nil { p.legacyOctalLiterals = make(map[js_ast.E]logger.Range) } p.legacyOctalLiterals[e] = p.lexer.Range() } } // This assumes the caller has already checked for TStringLiteral or TNoSubstitutionTemplateLiteral func (p *parser) parseStringLiteral() js_ast.Expr { var legacyOctalLoc logger.Loc loc := p.lexer.Loc() text := p.lexer.StringLiteral() if p.lexer.LegacyOctalLoc.Start > loc.Start { legacyOctalLoc = p.lexer.LegacyOctalLoc } value := js_ast.Expr{Loc: loc, Data: &js_ast.EString{ Value: text, LegacyOctalLoc: legacyOctalLoc, PreferTemplate: p.lexer.Token == js_lexer.TNoSubstitutionTemplateLiteral, }} p.lexer.Next() return value } type propertyOpts struct { asyncRange logger.Range tsDeclareRange logger.Range isAsync bool isGenerator bool // Class-related options isStatic bool isTSAbstract bool isClass bool classHasExtends bool allowTSDecorators bool tsDecorators []js_ast.Expr } func (p *parser) parseProperty(kind js_ast.PropertyKind, opts propertyOpts, errors *deferredErrors) (js_ast.Property, bool) { var key js_ast.Expr keyRange := p.lexer.Range() isComputed := false preferQuotedKey := false switch p.lexer.Token { case js_lexer.TNumericLiteral: key = js_ast.Expr{Loc: p.lexer.Loc(), Data: &js_ast.ENumber{Value: p.lexer.Number}} p.checkForLegacyOctalLiteral(key.Data) p.lexer.Next() case js_lexer.TStringLiteral: key = p.parseStringLiteral() preferQuotedKey = !p.options.mangleSyntax case js_lexer.TBigIntegerLiteral: key = js_ast.Expr{Loc: p.lexer.Loc(), Data: &js_ast.EBigInt{Value: p.lexer.Identifier}} p.markSyntaxFeature(compat.BigInt, p.lexer.Range()) p.lexer.Next() case js_lexer.TPrivateIdentifier: if !opts.isClass || len(opts.tsDecorators) > 0 { p.lexer.Expected(js_lexer.TIdentifier) } if opts.tsDeclareRange.Len != 0 { p.log.AddRangeError(&p.tracker, opts.tsDeclareRange, "\"declare\" cannot be used with a private identifier") } key = js_ast.Expr{Loc: p.lexer.Loc(), Data: &js_ast.EPrivateIdentifier{Ref: p.storeNameInRef(p.lexer.Identifier)}} p.lexer.Next() case js_lexer.TOpenBracket: isComputed = true p.markSyntaxFeature(compat.ObjectExtensions, p.lexer.Range()) p.lexer.Next() wasIdentifier := p.lexer.Token == js_lexer.TIdentifier expr := p.parseExpr(js_ast.LComma) // Handle index signatures if p.options.ts.Parse && p.lexer.Token == js_lexer.TColon && wasIdentifier && opts.isClass { if _, ok := expr.Data.(*js_ast.EIdentifier); ok { if opts.tsDeclareRange.Len != 0 { p.log.AddRangeError(&p.tracker, opts.tsDeclareRange, "\"declare\" cannot be used with an index signature") } // "[key: string]: any;" p.lexer.Next() p.skipTypeScriptType(js_ast.LLowest) p.lexer.Expect(js_lexer.TCloseBracket) p.lexer.Expect(js_lexer.TColon) p.skipTypeScriptType(js_ast.LLowest) p.lexer.ExpectOrInsertSemicolon() // Skip this property entirely return js_ast.Property{}, false } } p.lexer.Expect(js_lexer.TCloseBracket) key = expr case js_lexer.TAsterisk: if kind != js_ast.PropertyNormal || opts.isGenerator { p.lexer.Unexpected() } p.lexer.Next() opts.isGenerator = true return p.parseProperty(js_ast.PropertyNormal, opts, errors) default: name := p.lexer.Identifier raw := p.lexer.Raw() nameRange := p.lexer.Range() if !p.lexer.IsIdentifierOrKeyword() { p.lexer.Expect(js_lexer.TIdentifier) } p.lexer.Next() // Support contextual keywords if kind == js_ast.PropertyNormal && !opts.isGenerator { // Does the following token look like a key? couldBeModifierKeyword := p.lexer.IsIdentifierOrKeyword() if !couldBeModifierKeyword { switch p.lexer.Token { case js_lexer.TOpenBracket, js_lexer.TNumericLiteral, js_lexer.TStringLiteral, js_lexer.TAsterisk, js_lexer.TPrivateIdentifier: couldBeModifierKeyword = true } } // If so, check for a modifier keyword if couldBeModifierKeyword { switch name { case "get": if !opts.isAsync && raw == name { p.markSyntaxFeature(compat.ObjectAccessors, nameRange) return p.parseProperty(js_ast.PropertyGet, opts, nil) } case "set": if !opts.isAsync && raw == name { p.markSyntaxFeature(compat.ObjectAccessors, nameRange) return p.parseProperty(js_ast.PropertySet, opts, nil) } case "async": if !opts.isAsync && raw == name && !p.lexer.HasNewlineBefore { opts.isAsync = true opts.asyncRange = nameRange p.markLoweredSyntaxFeature(compat.AsyncAwait, nameRange, compat.Generator) return p.parseProperty(kind, opts, nil) } case "static": if !opts.isStatic && !opts.isAsync && opts.isClass && raw == name { opts.isStatic = true return p.parseProperty(kind, opts, nil) } case "declare": if opts.isClass && p.options.ts.Parse && opts.tsDeclareRange.Len == 0 && raw == name { opts.tsDeclareRange = nameRange scopeIndex := len(p.scopesInOrder) if prop, ok := p.parseProperty(kind, opts, nil); ok && prop.Kind == js_ast.PropertyNormal && prop.ValueOrNil.Data == nil { // If this is a well-formed class field with the "declare" keyword, // keep the declaration to preserve its side-effects, which may // include the computed key and/or the TypeScript decorators: // // class Foo { // declare [(console.log('side effect 1'), 'foo')] // @decorator(console.log('side effect 2')) declare bar // } // prop.Kind = js_ast.PropertyDeclare return prop, true } p.discardScopesUpTo(scopeIndex) return js_ast.Property{}, false } case "abstract": if opts.isClass && p.options.ts.Parse && !opts.isTSAbstract && raw == name { opts.isTSAbstract = true scopeIndex := len(p.scopesInOrder) p.parseProperty(kind, opts, nil) p.discardScopesUpTo(scopeIndex) return js_ast.Property{}, false } case "private", "protected", "public", "readonly", "override": // Skip over TypeScript keywords if opts.isClass && p.options.ts.Parse && raw == name { return p.parseProperty(kind, opts, nil) } } } else if p.lexer.Token == js_lexer.TOpenBrace && name == "static" { p.log.AddRangeError(&p.tracker, p.lexer.Range(), "Class static blocks are not supported yet") loc := p.lexer.Loc() p.lexer.Next() oldIsClassStaticInit := p.fnOrArrowDataParse.isClassStaticInit oldAwait := p.fnOrArrowDataParse.await p.fnOrArrowDataParse.isClassStaticInit = true p.fnOrArrowDataParse.await = forbidAll scopeIndex := p.pushScopeForParsePass(js_ast.ScopeClassStaticInit, loc) p.parseStmtsUpTo(js_lexer.TCloseBrace, parseStmtOpts{}) p.popAndDiscardScope(scopeIndex) p.fnOrArrowDataParse.isClassStaticInit = oldIsClassStaticInit p.fnOrArrowDataParse.await = oldAwait p.lexer.Expect(js_lexer.TCloseBrace) } } key = js_ast.Expr{Loc: nameRange.Loc, Data: &js_ast.EString{Value: js_lexer.StringToUTF16(name)}} // Parse a shorthand property if !opts.isClass && kind == js_ast.PropertyNormal && p.lexer.Token != js_lexer.TColon && p.lexer.Token != js_lexer.TOpenParen && p.lexer.Token != js_lexer.TLessThan && !opts.isGenerator && !opts.isAsync && js_lexer.Keywords[name] == js_lexer.T(0) { if (p.fnOrArrowDataParse.await != allowIdent && name == "await") || (p.fnOrArrowDataParse.yield != allowIdent && name == "yield") { p.log.AddRangeError(&p.tracker, nameRange, fmt.Sprintf("Cannot use %q as an identifier here", name)) } ref := p.storeNameInRef(name) value := js_ast.Expr{Loc: key.Loc, Data: &js_ast.EIdentifier{Ref: ref}} // Destructuring patterns have an optional default value var initializerOrNil js_ast.Expr if errors != nil && p.lexer.Token == js_lexer.TEquals { errors.invalidExprDefaultValue = p.lexer.Range() p.lexer.Next() initializerOrNil = p.parseExpr(js_ast.LComma) } return js_ast.Property{ Kind: kind, Key: key, ValueOrNil: value, InitializerOrNil: initializerOrNil, WasShorthand: true, }, true } } if p.options.ts.Parse { // "class X { foo?: number }" // "class X { foo!: number }" if opts.isClass && (p.lexer.Token == js_lexer.TQuestion || p.lexer.Token == js_lexer.TExclamation) { p.lexer.Next() } // "class X { foo?(): T }" // "const x = { foo(): T {} }" p.skipTypeScriptTypeParameters() } // Parse a class field with an optional initial value if opts.isClass && kind == js_ast.PropertyNormal && !opts.isAsync && !opts.isGenerator && p.lexer.Token != js_lexer.TOpenParen { var initializerOrNil js_ast.Expr // Forbid the names "constructor" and "prototype" in some cases if !isComputed { if str, ok := key.Data.(*js_ast.EString); ok && (js_lexer.UTF16EqualsString(str.Value, "constructor") || (opts.isStatic && js_lexer.UTF16EqualsString(str.Value, "prototype"))) { p.log.AddRangeError(&p.tracker, keyRange, fmt.Sprintf("Invalid field name %q", js_lexer.UTF16ToString(str.Value))) } } // Skip over types if p.options.ts.Parse && p.lexer.Token == js_lexer.TColon { p.lexer.Next() p.skipTypeScriptType(js_ast.LLowest) } if p.lexer.Token == js_lexer.TEquals { if opts.tsDeclareRange.Len != 0 { p.log.AddRangeError(&p.tracker, p.lexer.Range(), "Class fields that use \"declare\" cannot be initialized") } p.lexer.Next() // "super" property access is allowed in field initializers p.fnOrArrowDataParse.allowSuperProperty = true initializerOrNil = p.parseExpr(js_ast.LComma) p.fnOrArrowDataParse.allowSuperProperty = false } // Special-case private identifiers if private, ok := key.Data.(*js_ast.EPrivateIdentifier); ok { name := p.loadNameFromRef(private.Ref) if name == "#constructor" { p.log.AddRangeError(&p.tracker, keyRange, fmt.Sprintf("Invalid field name %q", name)) } var declare js_ast.SymbolKind if opts.isStatic { declare = js_ast.SymbolPrivateStaticField } else { declare = js_ast.SymbolPrivateField } private.Ref = p.declareSymbol(declare, key.Loc, name) } p.lexer.ExpectOrInsertSemicolon() return js_ast.Property{ TSDecorators: opts.tsDecorators, Kind: kind, IsComputed: isComputed, PreferQuotedKey: preferQuotedKey, IsStatic: opts.isStatic, Key: key, InitializerOrNil: initializerOrNil, }, true } // Parse a method expression if p.lexer.Token == js_lexer.TOpenParen || kind != js_ast.PropertyNormal || opts.isClass || opts.isAsync || opts.isGenerator { if opts.tsDeclareRange.Len != 0 { what := "method" if kind == js_ast.PropertyGet { what = "getter" } else if kind == js_ast.PropertySet { what = "setter" } p.log.AddRangeError(&p.tracker, opts.tsDeclareRange, "\"declare\" cannot be used with a "+what) } if p.lexer.Token == js_lexer.TOpenParen && kind != js_ast.PropertyGet && kind != js_ast.PropertySet { p.markSyntaxFeature(compat.ObjectExtensions, p.lexer.Range()) } loc := p.lexer.Loc() scopeIndex := p.pushScopeForParsePass(js_ast.ScopeFunctionArgs, loc) isConstructor := false // Forbid the names "constructor" and "prototype" in some cases if opts.isClass && !isComputed { if str, ok := key.Data.(*js_ast.EString); ok { if !opts.isStatic && js_lexer.UTF16EqualsString(str.Value, "constructor") { switch { case kind == js_ast.PropertyGet: p.log.AddRangeError(&p.tracker, keyRange, "Class constructor cannot be a getter") case kind == js_ast.PropertySet: p.log.AddRangeError(&p.tracker, keyRange, "Class constructor cannot be a setter") case opts.isAsync: p.log.AddRangeError(&p.tracker, keyRange, "Class constructor cannot be an async function") case opts.isGenerator: p.log.AddRangeError(&p.tracker, keyRange, "Class constructor cannot be a generator") default: isConstructor = true } } else if opts.isStatic && js_lexer.UTF16EqualsString(str.Value, "prototype") { p.log.AddRangeError(&p.tracker, keyRange, "Invalid static method name \"prototype\"") } } } await := allowIdent yield := allowIdent if opts.isAsync { await = allowExpr } if opts.isGenerator { yield = allowExpr } fn, hadBody := p.parseFn(nil, fnOrArrowDataParse{ needsAsyncLoc: key.Loc, asyncRange: opts.asyncRange, await: await, yield: yield, allowSuperCall: opts.classHasExtends && isConstructor, allowSuperProperty: true, allowTSDecorators: opts.allowTSDecorators, isConstructor: isConstructor, // Only allow omitting the body if we're parsing TypeScript class allowMissingBodyForTypeScript: p.options.ts.Parse && opts.isClass, }) // "class Foo { foo(): void; foo(): void {} }" if !hadBody { // Skip this property entirely p.popAndDiscardScope(scopeIndex) return js_ast.Property{}, false } p.popScope() fn.IsUniqueFormalParameters = true value := js_ast.Expr{Loc: loc, Data: &js_ast.EFunction{Fn: fn}} // Enforce argument rules for accessors switch kind { case js_ast.PropertyGet: if len(fn.Args) > 0 { r := js_lexer.RangeOfIdentifier(p.source, fn.Args[0].Binding.Loc) p.log.AddRangeError(&p.tracker, r, fmt.Sprintf("Getter %s must have zero arguments", p.keyNameForError(key))) } case js_ast.PropertySet: if len(fn.Args) != 1 { r := js_lexer.RangeOfIdentifier(p.source, key.Loc) if len(fn.Args) > 1 { r = js_lexer.RangeOfIdentifier(p.source, fn.Args[1].Binding.Loc) } p.log.AddRangeError(&p.tracker, r, fmt.Sprintf("Setter %s must have exactly one argument", p.keyNameForError(key))) } } // Special-case private identifiers if private, ok := key.Data.(*js_ast.EPrivateIdentifier); ok { var declare js_ast.SymbolKind var suffix string switch kind { case js_ast.PropertyGet: if opts.isStatic { declare = js_ast.SymbolPrivateStaticGet } else { declare = js_ast.SymbolPrivateGet } suffix = "_get" case js_ast.PropertySet: if opts.isStatic { declare = js_ast.SymbolPrivateStaticSet } else { declare = js_ast.SymbolPrivateSet } suffix = "_set" default: if opts.isStatic { declare = js_ast.SymbolPrivateStaticMethod } else { declare = js_ast.SymbolPrivateMethod } suffix = "_fn" } name := p.loadNameFromRef(private.Ref) if name == "#constructor" { p.log.AddRangeError(&p.tracker, keyRange, fmt.Sprintf("Invalid method name %q", name)) } private.Ref = p.declareSymbol(declare, key.Loc, name) methodRef := p.newSymbol(js_ast.SymbolOther, name[1:]+suffix) if kind == js_ast.PropertySet { p.privateSetters[private.Ref] = methodRef } else { p.privateGetters[private.Ref] = methodRef } } return js_ast.Property{ TSDecorators: opts.tsDecorators, Kind: kind, IsComputed: isComputed, PreferQuotedKey: preferQuotedKey, IsMethod: true, IsStatic: opts.isStatic, Key: key, ValueOrNil: value, }, true } // Parse an object key/value pair p.lexer.Expect(js_lexer.TColon) value := p.parseExprOrBindings(js_ast.LComma, errors) return js_ast.Property{ Kind: kind, IsComputed: isComputed, PreferQuotedKey: preferQuotedKey, Key: key, ValueOrNil: value, }, true } func (p *parser) parsePropertyBinding() js_ast.PropertyBinding { var key js_ast.Expr isComputed := false preferQuotedKey := false switch p.lexer.Token { case js_lexer.TDotDotDot: p.lexer.Next() value := js_ast.Binding{Loc: p.lexer.Loc(), Data: &js_ast.BIdentifier{Ref: p.storeNameInRef(p.lexer.Identifier)}} p.lexer.Expect(js_lexer.TIdentifier) return js_ast.PropertyBinding{ IsSpread: true, Value: value, } case js_lexer.TNumericLiteral: key = js_ast.Expr{Loc: p.lexer.Loc(), Data: &js_ast.ENumber{Value: p.lexer.Number}} p.checkForLegacyOctalLiteral(key.Data) p.lexer.Next() case js_lexer.TStringLiteral: key = p.parseStringLiteral() preferQuotedKey = !p.options.mangleSyntax case js_lexer.TBigIntegerLiteral: key = js_ast.Expr{Loc: p.lexer.Loc(), Data: &js_ast.EBigInt{Value: p.lexer.Identifier}} p.markSyntaxFeature(compat.BigInt, p.lexer.Range()) p.lexer.Next() case js_lexer.TOpenBracket: isComputed = true p.lexer.Next() key = p.parseExpr(js_ast.LComma) p.lexer.Expect(js_lexer.TCloseBracket) default: name := p.lexer.Identifier loc := p.lexer.Loc() if !p.lexer.IsIdentifierOrKeyword() { p.lexer.Expect(js_lexer.TIdentifier) } p.lexer.Next() key = js_ast.Expr{Loc: loc, Data: &js_ast.EString{Value: js_lexer.StringToUTF16(name)}} if p.lexer.Token != js_lexer.TColon && p.lexer.Token != js_lexer.TOpenParen { ref := p.storeNameInRef(name) value := js_ast.Binding{Loc: loc, Data: &js_ast.BIdentifier{Ref: ref}} var defaultValueOrNil js_ast.Expr if p.lexer.Token == js_lexer.TEquals { p.lexer.Next() defaultValueOrNil = p.parseExpr(js_ast.LComma) } return js_ast.PropertyBinding{ Key: key, Value: value, DefaultValueOrNil: defaultValueOrNil, } } } p.lexer.Expect(js_lexer.TColon) value := p.parseBinding() var defaultValueOrNil js_ast.Expr if p.lexer.Token == js_lexer.TEquals { p.lexer.Next() defaultValueOrNil = p.parseExpr(js_ast.LComma) } return js_ast.PropertyBinding{ IsComputed: isComputed, PreferQuotedKey: preferQuotedKey, Key: key, Value: value, DefaultValueOrNil: defaultValueOrNil, } } func (p *parser) parseArrowBody(args []js_ast.Arg, data fnOrArrowDataParse) *js_ast.EArrow { arrowLoc := p.lexer.Loc() // Newlines are not allowed before "=>" if p.lexer.HasNewlineBefore { p.log.AddRangeError(&p.tracker, p.lexer.Range(), "Unexpected newline before \"=>\"") panic(js_lexer.LexerPanic{}) } p.lexer.Expect(js_lexer.TEqualsGreaterThan) for _, arg := range args { p.declareBinding(js_ast.SymbolHoisted, arg.Binding, parseStmtOpts{}) } // The ability to use "super" is inherited by arrow functions data.allowSuperCall = p.fnOrArrowDataParse.allowSuperCall data.allowSuperProperty = p.fnOrArrowDataParse.allowSuperProperty if p.lexer.Token == js_lexer.TOpenBrace { body := p.parseFnBody(data) p.afterArrowBodyLoc = p.lexer.Loc() return &js_ast.EArrow{Args: args, Body: body} } p.pushScopeForParsePass(js_ast.ScopeFunctionBody, arrowLoc) defer p.popScope() oldFnOrArrowData := p.fnOrArrowDataParse p.fnOrArrowDataParse = data expr := p.parseExpr(js_ast.LComma) p.fnOrArrowDataParse = oldFnOrArrowData return &js_ast.EArrow{ Args: args, PreferExpr: true, Body: js_ast.FnBody{Loc: arrowLoc, Stmts: []js_ast.Stmt{{Loc: expr.Loc, Data: &js_ast.SReturn{ValueOrNil: expr}}}}, } } func (p *parser) checkForArrowAfterTheCurrentToken() bool { oldLexer := p.lexer p.lexer.IsLogDisabled = true // Implement backtracking by restoring the lexer's memory to its original state defer func() { r := recover() if _, isLexerPanic := r.(js_lexer.LexerPanic); isLexerPanic { p.lexer = oldLexer } else if r != nil { panic(r) } }() p.lexer.Next() isArrowAfterThisToken := p.lexer.Token == js_lexer.TEqualsGreaterThan p.lexer = oldLexer return isArrowAfterThisToken } // This parses an expression. This assumes we've already parsed the "async" // keyword and are currently looking at the following token. func (p *parser) parseAsyncPrefixExpr(asyncRange logger.Range, level js_ast.L, flags exprFlag) js_ast.Expr { // "async function() {}" if !p.lexer.HasNewlineBefore && p.lexer.Token == js_lexer.TFunction { return p.parseFnExpr(asyncRange.Loc, true /* isAsync */, asyncRange) } // Check the precedence level to avoid parsing an arrow function in // "new async () => {}". This also avoids parsing "new async()" as // "new (async())()" instead. if !p.lexer.HasNewlineBefore && level < js_ast.LMember { switch p.lexer.Token { // "async => {}" case js_lexer.TEqualsGreaterThan: if level <= js_ast.LAssign { arg := js_ast.Arg{Binding: js_ast.Binding{Loc: asyncRange.Loc, Data: &js_ast.BIdentifier{Ref: p.storeNameInRef("async")}}} p.pushScopeForParsePass(js_ast.ScopeFunctionArgs, asyncRange.Loc) defer p.popScope() return js_ast.Expr{Loc: asyncRange.Loc, Data: p.parseArrowBody([]js_ast.Arg{arg}, fnOrArrowDataParse{ needsAsyncLoc: asyncRange.Loc, })} } // "async x => {}" case js_lexer.TIdentifier: if level <= js_ast.LAssign { // See https://github.com/tc39/ecma262/issues/2034 for details isArrowFn := true if (flags&exprFlagForLoopInit) != 0 && p.lexer.Identifier == "of" { // "for (async of" is only an arrow function if the next token is "=>" isArrowFn = p.checkForArrowAfterTheCurrentToken() // Do not allow "for (async of []) ;" but do allow "for await (async of []) ;" if !isArrowFn && (flags&exprFlagForAwaitLoopInit) == 0 && p.lexer.Raw() == "of" { r := logger.Range{Loc: asyncRange.Loc, Len: p.lexer.Range().End() - asyncRange.Loc.Start} p.log.AddRangeError(&p.tracker, r, "For loop initializers cannot start with \"async of\"") panic(js_lexer.LexerPanic{}) } } if isArrowFn { p.markLoweredSyntaxFeature(compat.AsyncAwait, asyncRange, compat.Generator) ref := p.storeNameInRef(p.lexer.Identifier) arg := js_ast.Arg{Binding: js_ast.Binding{Loc: p.lexer.Loc(), Data: &js_ast.BIdentifier{Ref: ref}}} p.lexer.Next() p.pushScopeForParsePass(js_ast.ScopeFunctionArgs, asyncRange.Loc) defer p.popScope() arrow := p.parseArrowBody([]js_ast.Arg{arg}, fnOrArrowDataParse{ needsAsyncLoc: arg.Binding.Loc, await: allowExpr, }) arrow.IsAsync = true return js_ast.Expr{Loc: asyncRange.Loc, Data: arrow} } } // "async()" // "async () => {}" case js_lexer.TOpenParen: p.lexer.Next() return p.parseParenExpr(asyncRange.Loc, level, parenExprOpts{isAsync: true, asyncRange: asyncRange}) // "async()" // "async () => {}" case js_lexer.TLessThan: if p.options.ts.Parse && p.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking() { p.lexer.Next() return p.parseParenExpr(asyncRange.Loc, level, parenExprOpts{isAsync: true, asyncRange: asyncRange}) } } } // "async" // "async + 1" return js_ast.Expr{Loc: asyncRange.Loc, Data: &js_ast.EIdentifier{Ref: p.storeNameInRef("async")}} } func (p *parser) parseFnExpr(loc logger.Loc, isAsync bool, asyncRange logger.Range) js_ast.Expr { p.lexer.Next() isGenerator := p.lexer.Token == js_lexer.TAsterisk if isGenerator { p.markSyntaxFeature(compat.Generator, p.lexer.Range()) p.lexer.Next() } else if isAsync { p.markLoweredSyntaxFeature(compat.AsyncAwait, asyncRange, compat.Generator) } var name *js_ast.LocRef p.pushScopeForParsePass(js_ast.ScopeFunctionArgs, loc) defer p.popScope() // The name is optional if p.lexer.Token == js_lexer.TIdentifier { // Don't declare the name "arguments" since it's shadowed and inaccessible name = &js_ast.LocRef{Loc: p.lexer.Loc()} if text := p.lexer.Identifier; text != "arguments" { name.Ref = p.declareSymbol(js_ast.SymbolHoistedFunction, name.Loc, text) } else { name.Ref = p.newSymbol(js_ast.SymbolHoistedFunction, text) } p.lexer.Next() } // Even anonymous functions can have TypeScript type parameters if p.options.ts.Parse { p.skipTypeScriptTypeParameters() } await := allowIdent yield := allowIdent if isAsync { await = allowExpr } if isGenerator { yield = allowExpr } fn, _ := p.parseFn(name, fnOrArrowDataParse{ needsAsyncLoc: loc, asyncRange: asyncRange, await: await, yield: yield, }) p.validateFunctionName(fn, fnExpr) return js_ast.Expr{Loc: loc, Data: &js_ast.EFunction{Fn: fn}} } type parenExprOpts struct { asyncRange logger.Range isAsync bool forceArrowFn bool } // This assumes that the open parenthesis has already been parsed by the caller func (p *parser) parseParenExpr(loc logger.Loc, level js_ast.L, opts parenExprOpts) js_ast.Expr { items := []js_ast.Expr{} errors := deferredErrors{} arrowArgErrors := deferredArrowArgErrors{} spreadRange := logger.Range{} typeColonRange := logger.Range{} commaAfterSpread := logger.Loc{} // Push a scope assuming this is an arrow function. It may not be, in which // case we'll need to roll this change back. This has to be done ahead of // parsing the arguments instead of later on when we hit the "=>" token and // we know it's an arrow function because the arguments may have default // values that introduce new scopes and declare new symbols. If this is an // arrow function, then those new scopes will need to be parented under the // scope of the arrow function itself. scopeIndex := p.pushScopeForParsePass(js_ast.ScopeFunctionArgs, loc) // Allow "in" inside parentheses oldAllowIn := p.allowIn p.allowIn = true // Forbid "await" and "yield", but only for arrow functions oldFnOrArrowData := p.fnOrArrowDataParse p.fnOrArrowDataParse.arrowArgErrors = &arrowArgErrors // Scan over the comma-separated arguments or expressions for p.lexer.Token != js_lexer.TCloseParen { itemLoc := p.lexer.Loc() isSpread := p.lexer.Token == js_lexer.TDotDotDot if isSpread { spreadRange = p.lexer.Range() p.markSyntaxFeature(compat.RestArgument, spreadRange) p.lexer.Next() } // We don't know yet whether these are arguments or expressions, so parse // a superset of the expression syntax. Errors about things that are valid // in one but not in the other are deferred. p.latestArrowArgLoc = p.lexer.Loc() item := p.parseExprOrBindings(js_ast.LComma, &errors) if isSpread { item = js_ast.Expr{Loc: itemLoc, Data: &js_ast.ESpread{Value: item}} } // Skip over types if p.options.ts.Parse && p.lexer.Token == js_lexer.TColon { typeColonRange = p.lexer.Range() p.lexer.Next() p.skipTypeScriptType(js_ast.LLowest) } // There may be a "=" after the type (but not after an "as" cast) if p.options.ts.Parse && p.lexer.Token == js_lexer.TEquals && p.lexer.Loc() != p.forbidSuffixAfterAsLoc { p.lexer.Next() item = js_ast.Assign(item, p.parseExpr(js_ast.LComma)) } items = append(items, item) if p.lexer.Token != js_lexer.TComma { break } // Spread arguments must come last. If there's a spread argument followed // by a comma, throw an error if we use these expressions as bindings. if isSpread { commaAfterSpread = p.lexer.Loc() } // Eat the comma token p.lexer.Next() } // The parenthetical construct must end with a close parenthesis p.lexer.Expect(js_lexer.TCloseParen) // Restore "in" operator status before we parse the arrow function body p.allowIn = oldAllowIn // Also restore "await" and "yield" expression errors p.fnOrArrowDataParse = oldFnOrArrowData // Are these arguments to an arrow function? if p.lexer.Token == js_lexer.TEqualsGreaterThan || opts.forceArrowFn || (p.options.ts.Parse && p.lexer.Token == js_lexer.TColon) { // Arrow functions are not allowed inside certain expressions if level > js_ast.LAssign { p.lexer.Unexpected() } var invalidLog invalidLog args := []js_ast.Arg{} if opts.isAsync { p.markLoweredSyntaxFeature(compat.AsyncAwait, opts.asyncRange, compat.Generator) } // First, try converting the expressions to bindings for _, item := range items { isSpread := false if spread, ok := item.Data.(*js_ast.ESpread); ok { item = spread.Value isSpread = true } binding, initializerOrNil, log := p.convertExprToBindingAndInitializer(item, invalidLog, isSpread) invalidLog = log args = append(args, js_ast.Arg{Binding: binding, DefaultOrNil: initializerOrNil}) } // Avoid parsing TypeScript code like "a ? (1 + 2) : (3 + 4)" as an arrow // function. The ":" after the ")" may be a return type annotation, so we // attempt to convert the expressions to bindings first before deciding // whether this is an arrow function, and only pick an arrow function if // there were no conversion errors. if p.lexer.Token == js_lexer.TEqualsGreaterThan || (len(invalidLog.invalidTokens) == 0 && p.trySkipTypeScriptArrowReturnTypeWithBacktracking()) || opts.forceArrowFn { if commaAfterSpread.Start != 0 { p.log.AddRangeError(&p.tracker, logger.Range{Loc: commaAfterSpread, Len: 1}, "Unexpected \",\" after rest pattern") } p.logArrowArgErrors(&arrowArgErrors) // Now that we've decided we're an arrow function, report binding pattern // conversion errors if len(invalidLog.invalidTokens) > 0 { for _, token := range invalidLog.invalidTokens { p.log.AddRangeError(&p.tracker, token, "Invalid binding pattern") } panic(js_lexer.LexerPanic{}) } // Also report syntax features used in bindings for _, entry := range invalidLog.syntaxFeatures { p.markSyntaxFeature(entry.feature, entry.token) } await := allowIdent if opts.isAsync { await = allowExpr } arrow := p.parseArrowBody(args, fnOrArrowDataParse{ needsAsyncLoc: loc, await: await, }) arrow.IsAsync = opts.isAsync arrow.HasRestArg = spreadRange.Len > 0 p.popScope() return js_ast.Expr{Loc: loc, Data: arrow} } } // If we get here, it's not an arrow function so undo the pushing of the // scope we did earlier. This needs to flatten any child scopes into the // parent scope as if the scope was never pushed in the first place. p.popAndFlattenScope(scopeIndex) // If this isn't an arrow function, then types aren't allowed if typeColonRange.Len > 0 { p.log.AddRangeError(&p.tracker, typeColonRange, "Unexpected \":\"") panic(js_lexer.LexerPanic{}) } // Are these arguments for a call to a function named "async"? if opts.isAsync { p.logExprErrors(&errors) async := js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: p.storeNameInRef("async")}} return js_ast.Expr{Loc: loc, Data: &js_ast.ECall{ Target: async, Args: items, }} } // Is this a chain of expressions and comma operators? if len(items) > 0 { p.logExprErrors(&errors) if spreadRange.Len > 0 { p.log.AddRangeError(&p.tracker, spreadRange, "Unexpected \"...\"") panic(js_lexer.LexerPanic{}) } value := js_ast.JoinAllWithComma(items) p.markExprAsParenthesized(value) return value } // Indicate that we expected an arrow function p.lexer.Expected(js_lexer.TEqualsGreaterThan) return js_ast.Expr{} } type invalidLog struct { invalidTokens []logger.Range syntaxFeatures []syntaxFeature } type syntaxFeature struct { feature compat.JSFeature token logger.Range } func (p *parser) convertExprToBindingAndInitializer( expr js_ast.Expr, invalidLog invalidLog, isSpread bool, ) (js_ast.Binding, js_ast.Expr, invalidLog) { var initializerOrNil js_ast.Expr if assign, ok := expr.Data.(*js_ast.EBinary); ok && assign.Op == js_ast.BinOpAssign { initializerOrNil = assign.Right expr = assign.Left } binding, invalidLog := p.convertExprToBinding(expr, invalidLog) if initializerOrNil.Data != nil { equalsRange := p.source.RangeOfOperatorBefore(initializerOrNil.Loc, "=") if isSpread { p.log.AddRangeError(&p.tracker, equalsRange, "A rest argument cannot have a default initializer") } else { invalidLog.syntaxFeatures = append(invalidLog.syntaxFeatures, syntaxFeature{ feature: compat.DefaultArgument, token: equalsRange, }) } } return binding, initializerOrNil, invalidLog } // Note: do not write to "p.log" in this function. Any errors due to conversion // from expression to binding should be written to "invalidLog" instead. That // way we can potentially keep this as an expression if it turns out it's not // needed as a binding after all. func (p *parser) convertExprToBinding(expr js_ast.Expr, invalidLog invalidLog) (js_ast.Binding, invalidLog) { switch e := expr.Data.(type) { case *js_ast.EMissing: return js_ast.Binding{Loc: expr.Loc, Data: js_ast.BMissingShared}, invalidLog case *js_ast.EIdentifier: return js_ast.Binding{Loc: expr.Loc, Data: &js_ast.BIdentifier{Ref: e.Ref}}, invalidLog case *js_ast.EArray: if e.CommaAfterSpread.Start != 0 { invalidLog.invalidTokens = append(invalidLog.invalidTokens, logger.Range{Loc: e.CommaAfterSpread, Len: 1}) } if e.IsParenthesized { invalidLog.invalidTokens = append(invalidLog.invalidTokens, p.source.RangeOfOperatorBefore(expr.Loc, "(")) } p.markSyntaxFeature(compat.Destructuring, p.source.RangeOfOperatorAfter(expr.Loc, "[")) items := []js_ast.ArrayBinding{} isSpread := false for _, item := range e.Items { if i, ok := item.Data.(*js_ast.ESpread); ok { isSpread = true item = i.Value if _, ok := item.Data.(*js_ast.EIdentifier); !ok { p.markSyntaxFeature(compat.NestedRestBinding, p.source.RangeOfOperatorAfter(item.Loc, "[")) } } binding, initializerOrNil, log := p.convertExprToBindingAndInitializer(item, invalidLog, isSpread) invalidLog = log items = append(items, js_ast.ArrayBinding{Binding: binding, DefaultValueOrNil: initializerOrNil}) } return js_ast.Binding{Loc: expr.Loc, Data: &js_ast.BArray{ Items: items, HasSpread: isSpread, IsSingleLine: e.IsSingleLine, }}, invalidLog case *js_ast.EObject: if e.CommaAfterSpread.Start != 0 { invalidLog.invalidTokens = append(invalidLog.invalidTokens, logger.Range{Loc: e.CommaAfterSpread, Len: 1}) } if e.IsParenthesized { invalidLog.invalidTokens = append(invalidLog.invalidTokens, p.source.RangeOfOperatorBefore(expr.Loc, "(")) } p.markSyntaxFeature(compat.Destructuring, p.source.RangeOfOperatorAfter(expr.Loc, "{")) properties := []js_ast.PropertyBinding{} for _, item := range e.Properties { if item.IsMethod || item.Kind == js_ast.PropertyGet || item.Kind == js_ast.PropertySet { invalidLog.invalidTokens = append(invalidLog.invalidTokens, js_lexer.RangeOfIdentifier(p.source, item.Key.Loc)) continue } binding, initializerOrNil, log := p.convertExprToBindingAndInitializer(item.ValueOrNil, invalidLog, false) invalidLog = log if initializerOrNil.Data == nil { initializerOrNil = item.InitializerOrNil } properties = append(properties, js_ast.PropertyBinding{ IsSpread: item.Kind == js_ast.PropertySpread, IsComputed: item.IsComputed, Key: item.Key, Value: binding, DefaultValueOrNil: initializerOrNil, }) } return js_ast.Binding{Loc: expr.Loc, Data: &js_ast.BObject{ Properties: properties, IsSingleLine: e.IsSingleLine, }}, invalidLog default: invalidLog.invalidTokens = append(invalidLog.invalidTokens, logger.Range{Loc: expr.Loc}) return js_ast.Binding{}, invalidLog } } type exprFlag uint8 const ( exprFlagTSDecorator exprFlag = 1 << iota exprFlagForLoopInit exprFlagForAwaitLoopInit ) func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprFlag) js_ast.Expr { loc := p.lexer.Loc() switch p.lexer.Token { case js_lexer.TSuper: superRange := p.lexer.Range() p.lexer.Next() switch p.lexer.Token { case js_lexer.TOpenParen: if level < js_ast.LCall && p.fnOrArrowDataParse.allowSuperCall { return js_ast.Expr{Loc: loc, Data: js_ast.ESuperShared} } case js_lexer.TDot, js_lexer.TOpenBracket: if p.fnOrArrowDataParse.allowSuperProperty { return js_ast.Expr{Loc: loc, Data: js_ast.ESuperShared} } } p.log.AddRangeError(&p.tracker, superRange, "Unexpected \"super\"") return js_ast.Expr{Loc: loc, Data: js_ast.ESuperShared} case js_lexer.TOpenParen: p.lexer.Next() // Arrow functions aren't allowed in the middle of expressions if level > js_ast.LAssign { // Allow "in" inside parentheses oldAllowIn := p.allowIn p.allowIn = true value := p.parseExpr(js_ast.LLowest) p.markExprAsParenthesized(value) p.lexer.Expect(js_lexer.TCloseParen) p.allowIn = oldAllowIn return value } value := p.parseParenExpr(loc, level, parenExprOpts{}) return value 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.TThis: p.lexer.Next() return js_ast.Expr{Loc: loc, Data: js_ast.EThisShared} case js_lexer.TPrivateIdentifier: if !p.allowPrivateIdentifiers || !p.allowIn || level >= js_ast.LCompare { p.lexer.Unexpected() } name := p.lexer.Identifier p.lexer.Next() // Check for "#foo in bar" if p.lexer.Token != js_lexer.TIn { p.lexer.Expected(js_lexer.TIn) } // Make sure to lower all matching private names if p.options.unsupportedJSFeatures.Has(compat.ClassPrivateBrandCheck) { if p.classPrivateBrandChecksToLower == nil { p.classPrivateBrandChecksToLower = make(map[string]bool) } p.classPrivateBrandChecksToLower[name] = true } return js_ast.Expr{Loc: loc, Data: &js_ast.EPrivateIdentifier{Ref: p.storeNameInRef(name)}} case js_lexer.TIdentifier: name := p.lexer.Identifier nameRange := p.lexer.Range() raw := p.lexer.Raw() p.lexer.Next() // Handle async and await expressions switch name { case "async": if raw == "async" { return p.parseAsyncPrefixExpr(nameRange, level, flags) } case "await": switch p.fnOrArrowDataParse.await { case forbidAll: p.log.AddRangeError(&p.tracker, nameRange, "The keyword \"await\" cannot be used here") case allowExpr: if raw != "await" { p.log.AddRangeError(&p.tracker, nameRange, "The keyword \"await\" cannot be escaped") } else { if p.fnOrArrowDataParse.isTopLevel { p.topLevelAwaitKeyword = nameRange p.markSyntaxFeature(compat.TopLevelAwait, nameRange) } if p.fnOrArrowDataParse.arrowArgErrors != nil { p.fnOrArrowDataParse.arrowArgErrors.invalidExprAwait = nameRange } value := p.parseExpr(js_ast.LPrefix) if p.lexer.Token == js_lexer.TAsteriskAsterisk { p.lexer.Unexpected() } return js_ast.Expr{Loc: loc, Data: &js_ast.EAwait{Value: value}} } case allowIdent: p.lexer.PrevTokenWasAwaitKeyword = true p.lexer.AwaitKeywordLoc = loc p.lexer.FnOrArrowStartLoc = p.fnOrArrowDataParse.needsAsyncLoc } case "yield": switch p.fnOrArrowDataParse.yield { case forbidAll: p.log.AddRangeError(&p.tracker, nameRange, "The keyword \"yield\" cannot be used here") case allowExpr: if raw != "yield" { p.log.AddRangeError(&p.tracker, nameRange, "The keyword \"yield\" cannot be escaped") } else { if level > js_ast.LAssign { p.log.AddRangeError(&p.tracker, nameRange, "Cannot use a \"yield\" expression here without parentheses") } if p.fnOrArrowDataParse.arrowArgErrors != nil { p.fnOrArrowDataParse.arrowArgErrors.invalidExprYield = nameRange } return p.parseYieldExpr(loc) } case allowIdent: if !p.lexer.HasNewlineBefore { // Try to gracefully recover if "yield" is used in the wrong place switch p.lexer.Token { case js_lexer.TNull, js_lexer.TIdentifier, js_lexer.TFalse, js_lexer.TTrue, js_lexer.TNumericLiteral, js_lexer.TBigIntegerLiteral, js_lexer.TStringLiteral: p.log.AddRangeError(&p.tracker, nameRange, "Cannot use \"yield\" outside a generator function") return p.parseYieldExpr(loc) } } } } // Handle the start of an arrow expression if p.lexer.Token == js_lexer.TEqualsGreaterThan && level <= js_ast.LAssign { ref := p.storeNameInRef(name) arg := js_ast.Arg{Binding: js_ast.Binding{Loc: loc, Data: &js_ast.BIdentifier{Ref: ref}}} p.pushScopeForParsePass(js_ast.ScopeFunctionArgs, loc) defer p.popScope() return js_ast.Expr{Loc: loc, Data: p.parseArrowBody([]js_ast.Arg{arg}, fnOrArrowDataParse{ needsAsyncLoc: loc, })} } ref := p.storeNameInRef(name) return js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}} case js_lexer.TStringLiteral, js_lexer.TNoSubstitutionTemplateLiteral: return p.parseStringLiteral() case js_lexer.TTemplateHead: var legacyOctalLoc logger.Loc headLoc := p.lexer.Loc() head := p.lexer.StringLiteral() if p.lexer.LegacyOctalLoc.Start > loc.Start { legacyOctalLoc = p.lexer.LegacyOctalLoc } parts, tailLegacyOctalLoc := p.parseTemplateParts(false /* includeRaw */) if tailLegacyOctalLoc.Start > 0 { legacyOctalLoc = tailLegacyOctalLoc } return js_ast.Expr{Loc: loc, Data: &js_ast.ETemplate{ HeadLoc: headLoc, HeadCooked: head, Parts: parts, LegacyOctalLoc: legacyOctalLoc, }} case js_lexer.TNumericLiteral: value := js_ast.Expr{Loc: loc, Data: &js_ast.ENumber{Value: p.lexer.Number}} p.checkForLegacyOctalLiteral(value.Data) p.lexer.Next() return value case js_lexer.TBigIntegerLiteral: value := p.lexer.Identifier p.markSyntaxFeature(compat.BigInt, p.lexer.Range()) p.lexer.Next() return js_ast.Expr{Loc: loc, Data: &js_ast.EBigInt{Value: value}} case js_lexer.TSlash, js_lexer.TSlashEquals: p.lexer.ScanRegExp() value := p.lexer.Raw() p.lexer.Next() return js_ast.Expr{Loc: loc, Data: &js_ast.ERegExp{Value: value}} case js_lexer.TVoid: p.lexer.Next() value := p.parseExpr(js_ast.LPrefix) if p.lexer.Token == js_lexer.TAsteriskAsterisk { p.lexer.Unexpected() } return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{Op: js_ast.UnOpVoid, Value: value}} case js_lexer.TTypeof: p.lexer.Next() value := p.parseExpr(js_ast.LPrefix) if p.lexer.Token == js_lexer.TAsteriskAsterisk { p.lexer.Unexpected() } return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{Op: js_ast.UnOpTypeof, Value: value}} case js_lexer.TDelete: p.lexer.Next() value := p.parseExpr(js_ast.LPrefix) if p.lexer.Token == js_lexer.TAsteriskAsterisk { p.lexer.Unexpected() } if index, ok := value.Data.(*js_ast.EIndex); ok { if private, ok := index.Index.Data.(*js_ast.EPrivateIdentifier); ok { name := p.loadNameFromRef(private.Ref) r := logger.Range{Loc: index.Index.Loc, Len: int32(len(name))} p.log.AddRangeError(&p.tracker, r, fmt.Sprintf("Deleting the private name %q is forbidden", name)) } } return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{Op: js_ast.UnOpDelete, Value: value}} case js_lexer.TPlus: p.lexer.Next() value := p.parseExpr(js_ast.LPrefix) if p.lexer.Token == js_lexer.TAsteriskAsterisk { p.lexer.Unexpected() } return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{Op: js_ast.UnOpPos, Value: value}} case js_lexer.TMinus: p.lexer.Next() value := p.parseExpr(js_ast.LPrefix) if p.lexer.Token == js_lexer.TAsteriskAsterisk { p.lexer.Unexpected() } return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{Op: js_ast.UnOpNeg, Value: value}} case js_lexer.TTilde: p.lexer.Next() value := p.parseExpr(js_ast.LPrefix) if p.lexer.Token == js_lexer.TAsteriskAsterisk { p.lexer.Unexpected() } return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{Op: js_ast.UnOpCpl, Value: value}} case js_lexer.TExclamation: p.lexer.Next() value := p.parseExpr(js_ast.LPrefix) if p.lexer.Token == js_lexer.TAsteriskAsterisk { p.lexer.Unexpected() } return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{Op: js_ast.UnOpNot, Value: value}} case js_lexer.TMinusMinus: p.lexer.Next() return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{Op: js_ast.UnOpPreDec, Value: p.parseExpr(js_ast.LPrefix)}} case js_lexer.TPlusPlus: p.lexer.Next() return js_ast.Expr{Loc: loc, Data: &js_ast.EUnary{Op: js_ast.UnOpPreInc, Value: p.parseExpr(js_ast.LPrefix)}} case js_lexer.TFunction: return p.parseFnExpr(loc, false /* isAsync */, logger.Range{}) case js_lexer.TClass: classKeyword := p.lexer.Range() p.markSyntaxFeature(compat.Class, classKeyword) p.lexer.Next() var name *js_ast.LocRef p.pushScopeForParsePass(js_ast.ScopeClassName, loc) // Parse an optional class name if p.lexer.Token == js_lexer.TIdentifier { if nameText := p.lexer.Identifier; !p.options.ts.Parse || nameText != "implements" { if p.fnOrArrowDataParse.await != allowIdent && nameText == "await" { p.log.AddRangeError(&p.tracker, p.lexer.Range(), "Cannot use \"await\" as an identifier here") } name = &js_ast.LocRef{Loc: p.lexer.Loc(), Ref: p.newSymbol(js_ast.SymbolOther, nameText)} p.lexer.Next() } } // Even anonymous classes can have TypeScript type parameters if p.options.ts.Parse { p.skipTypeScriptTypeParameters() } class := p.parseClass(classKeyword, name, parseClassOpts{}) p.popScope() return js_ast.Expr{Loc: loc, Data: &js_ast.EClass{Class: class}} case js_lexer.TNew: p.lexer.Next() // Special-case the weird "new.target" expression here if p.lexer.Token == js_lexer.TDot { p.lexer.Next() if p.lexer.Token != js_lexer.TIdentifier || p.lexer.Raw() != "target" { p.lexer.Unexpected() } r := logger.Range{Loc: loc, Len: p.lexer.Range().End() - loc.Start} p.markSyntaxFeature(compat.NewTarget, r) p.lexer.Next() return js_ast.Expr{Loc: loc, Data: &js_ast.ENewTarget{Range: r}} } target := p.parseExprWithFlags(js_ast.LMember, flags) args := []js_ast.Expr{} if p.lexer.Token == js_lexer.TOpenParen { args = p.parseCallArgs() } return js_ast.Expr{Loc: loc, Data: &js_ast.ENew{Target: target, Args: args}} case js_lexer.TOpenBracket: p.lexer.Next() isSingleLine := !p.lexer.HasNewlineBefore items := []js_ast.Expr{} selfErrors := deferredErrors{} commaAfterSpread := logger.Loc{} // Allow "in" inside arrays oldAllowIn := p.allowIn p.allowIn = true for p.lexer.Token != js_lexer.TCloseBracket { switch p.lexer.Token { case js_lexer.TComma: items = append(items, js_ast.Expr{Loc: p.lexer.Loc(), Data: js_ast.EMissingShared}) case js_lexer.TDotDotDot: if errors != nil { errors.arraySpreadFeature = p.lexer.Range() } else { p.markSyntaxFeature(compat.ArraySpread, p.lexer.Range()) } dotsLoc := p.lexer.Loc() p.lexer.Next() item := p.parseExprOrBindings(js_ast.LComma, &selfErrors) items = append(items, js_ast.Expr{Loc: dotsLoc, Data: &js_ast.ESpread{Value: item}}) // Commas are not allowed here when destructuring if p.lexer.Token == js_lexer.TComma { commaAfterSpread = p.lexer.Loc() } default: item := p.parseExprOrBindings(js_ast.LComma, &selfErrors) items = append(items, item) } if p.lexer.Token != js_lexer.TComma { break } if p.lexer.HasNewlineBefore { isSingleLine = false } p.lexer.Next() if p.lexer.HasNewlineBefore { isSingleLine = false } } if p.lexer.HasNewlineBefore { isSingleLine = false } p.lexer.Expect(js_lexer.TCloseBracket) p.allowIn = oldAllowIn if p.willNeedBindingPattern() { // Is this a binding pattern? } else if errors == nil { // Is this an expression? p.logExprErrors(&selfErrors) } else { // In this case, we can't distinguish between the two yet selfErrors.mergeInto(errors) } return js_ast.Expr{Loc: loc, Data: &js_ast.EArray{ Items: items, CommaAfterSpread: commaAfterSpread, IsSingleLine: isSingleLine, }} case js_lexer.TOpenBrace: p.lexer.Next() isSingleLine := !p.lexer.HasNewlineBefore properties := []js_ast.Property{} selfErrors := deferredErrors{} commaAfterSpread := logger.Loc{} // Allow "in" inside object literals oldAllowIn := p.allowIn p.allowIn = true for p.lexer.Token != js_lexer.TCloseBrace { if p.lexer.Token == js_lexer.TDotDotDot { p.lexer.Next() value := p.parseExpr(js_ast.LComma) properties = append(properties, js_ast.Property{ Kind: js_ast.PropertySpread, ValueOrNil: value, }) // Commas are not allowed here when destructuring if p.lexer.Token == js_lexer.TComma { commaAfterSpread = p.lexer.Loc() } } else { // This property may turn out to be a type in TypeScript, which should be ignored if property, ok := p.parseProperty(js_ast.PropertyNormal, propertyOpts{}, &selfErrors); ok { properties = append(properties, property) } } if p.lexer.Token != js_lexer.TComma { break } if p.lexer.HasNewlineBefore { isSingleLine = false } p.lexer.Next() if p.lexer.HasNewlineBefore { isSingleLine = false } } if p.lexer.HasNewlineBefore { isSingleLine = false } p.lexer.Expect(js_lexer.TCloseBrace) p.allowIn = oldAllowIn if p.willNeedBindingPattern() { // Is this a binding pattern? } else if errors == nil { // Is this an expression? p.logExprErrors(&selfErrors) } else { // In this case, we can't distinguish between the two yet selfErrors.mergeInto(errors) } return js_ast.Expr{Loc: loc, Data: &js_ast.EObject{ Properties: properties, CommaAfterSpread: commaAfterSpread, IsSingleLine: isSingleLine, }} case js_lexer.TLessThan: // This is a very complicated and highly ambiguous area of TypeScript // syntax. Many similar-looking things are overloaded. // // TS: // // A type cast: // (x) // <[]>(x) // (x) // // An arrow function with type parameters: // (x) => {} // (x) => {} // (x) => {} // (x) => {} // // TSX: // // A JSX element: // (x) => {} // (x) => {} // (x) => {} // // An arrow function with type parameters: // (x) => {} // (x) => {} // // A syntax error: // <[]>(x) // (x) // (x) => {} // (x) => {} if p.options.ts.Parse && p.options.jsx.Parse && p.isTSArrowFnJSX() { p.skipTypeScriptTypeParameters() p.lexer.Expect(js_lexer.TOpenParen) return p.parseParenExpr(loc, level, parenExprOpts{forceArrowFn: true}) } if p.options.jsx.Parse { // Use NextInsideJSXElement() instead of Next() so we parse "<<" as "<" p.lexer.NextInsideJSXElement() element := p.parseJSXElement(loc) // The call to parseJSXElement() above doesn't consume the last // TGreaterThan because the caller knows what Next() function to call. // Use Next() instead of NextInsideJSXElement() here since the next // token is an expression. p.lexer.Next() return element } if p.options.ts.Parse { // This is either an old-style type cast or a generic lambda function // TypeScript 4.5 introduced the ".mts" and ".cts" extensions that forbid // the use of an expression starting with "<" that would be ambiguous // when the file is in JSX mode. if p.options.ts.NoAmbiguousLessThan && !p.isTSArrowFnJSX() { p.log.AddRangeError(&p.tracker, p.lexer.Range(), "This syntax is not allowed in files with the \".mts\" or \".cts\" extension") } // "(x)" // "(x) => {}" if p.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking() { p.lexer.Expect(js_lexer.TOpenParen) return p.parseParenExpr(loc, level, parenExprOpts{}) } // "x" p.lexer.Next() p.skipTypeScriptType(js_ast.LLowest) p.lexer.ExpectGreaterThan(false /* isInsideJSXElement */) value := p.parsePrefix(level, errors, flags) return value } p.lexer.Unexpected() return js_ast.Expr{} case js_lexer.TImport: p.lexer.Next() return p.parseImportExpr(loc, level) default: p.lexer.Unexpected() return js_ast.Expr{} } } func (p *parser) parseYieldExpr(loc logger.Loc) js_ast.Expr { // Parse a yield-from expression, which yields from an iterator isStar := p.lexer.Token == js_lexer.TAsterisk if isStar { if p.lexer.HasNewlineBefore { p.lexer.Unexpected() } p.lexer.Next() } var valueOrNil js_ast.Expr // The yield expression only has a value in certain cases switch p.lexer.Token { case js_lexer.TCloseBrace, js_lexer.TCloseBracket, js_lexer.TCloseParen, js_lexer.TColon, js_lexer.TComma, js_lexer.TSemicolon: default: if isStar || !p.lexer.HasNewlineBefore { valueOrNil = p.parseExpr(js_ast.LYield) } } return js_ast.Expr{Loc: loc, Data: &js_ast.EYield{ValueOrNil: valueOrNil, IsStar: isStar}} } func (p *parser) willNeedBindingPattern() bool { switch p.lexer.Token { case js_lexer.TEquals: // "[a] = b;" return true case js_lexer.TIn: // "for ([a] in b) {}" return !p.allowIn case js_lexer.TIdentifier: // "for ([a] of b) {}" return !p.allowIn && p.lexer.IsContextualKeyword("of") default: return false } } // Note: The caller has already parsed the "import" keyword func (p *parser) parseImportExpr(loc logger.Loc, level js_ast.L) js_ast.Expr { // Parse an "import.meta" expression if p.lexer.Token == js_lexer.TDot { p.es6ImportKeyword = js_lexer.RangeOfIdentifier(p.source, loc) p.lexer.Next() if p.lexer.IsContextualKeyword("meta") { r := p.lexer.Range() p.lexer.Next() p.hasImportMeta = true if p.options.unsupportedJSFeatures.Has(compat.ImportMeta) { r = logger.Range{Loc: loc, Len: r.End() - loc.Start} p.markSyntaxFeature(compat.ImportMeta, r) } return js_ast.Expr{Loc: loc, Data: js_ast.EImportMetaShared} } else { p.lexer.ExpectedString("\"meta\"") } } if level > js_ast.LCall { r := js_lexer.RangeOfIdentifier(p.source, loc) p.log.AddRangeError(&p.tracker, r, "Cannot use an \"import\" expression here without parentheses") } // Allow "in" inside call arguments oldAllowIn := p.allowIn p.allowIn = true p.lexer.PreserveAllCommentsBefore = true p.lexer.Expect(js_lexer.TOpenParen) comments := p.lexer.CommentsToPreserveBefore p.lexer.PreserveAllCommentsBefore = false value := p.parseExpr(js_ast.LComma) var optionsOrNil js_ast.Expr if p.lexer.Token == js_lexer.TComma { // "import('./foo.json', )" p.lexer.Next() if p.lexer.Token != js_lexer.TCloseParen { // "import('./foo.json', { assert: { type: 'json' } })" optionsOrNil = p.parseExpr(js_ast.LComma) if p.lexer.Token == js_lexer.TComma { // "import('./foo.json', { assert: { type: 'json' } }, )" p.lexer.Next() } } } p.lexer.Expect(js_lexer.TCloseParen) p.allowIn = oldAllowIn return js_ast.Expr{Loc: loc, Data: &js_ast.EImportCall{ Expr: value, OptionsOrNil: optionsOrNil, LeadingInteriorComments: comments, }} } func (p *parser) parseExprOrBindings(level js_ast.L, errors *deferredErrors) js_ast.Expr { return p.parseExprCommon(level, errors, 0) } func (p *parser) parseExpr(level js_ast.L) js_ast.Expr { return p.parseExprCommon(level, nil, 0) } func (p *parser) parseExprWithFlags(level js_ast.L, flags exprFlag) js_ast.Expr { return p.parseExprCommon(level, nil, flags) } func (p *parser) parseExprCommon(level js_ast.L, errors *deferredErrors, flags exprFlag) js_ast.Expr { hadPureCommentBefore := p.lexer.HasPureCommentBefore && !p.options.ignoreDCEAnnotations expr := p.parsePrefix(level, errors, flags) // There is no formal spec for "__PURE__" comments but from reverse- // engineering, it looks like they apply to the next CallExpression or // NewExpression. So in "/* @__PURE__ */ a().b() + c()" the comment applies // to the expression "a().b()". if hadPureCommentBefore && level < js_ast.LCall { expr = p.parseSuffix(expr, js_ast.LCall-1, errors, flags) switch e := expr.Data.(type) { case *js_ast.ECall: e.CanBeUnwrappedIfUnused = true case *js_ast.ENew: e.CanBeUnwrappedIfUnused = true } } return p.parseSuffix(expr, level, errors, flags) } func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredErrors, flags exprFlag) js_ast.Expr { optionalChain := js_ast.OptionalChainNone for { if p.lexer.Loc() == p.afterArrowBodyLoc { for { switch p.lexer.Token { case js_lexer.TComma: if level >= js_ast.LComma { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpComma, Left: left, Right: p.parseExpr(js_ast.LComma)}} default: return left } } } // Stop now if this token is forbidden to follow a TypeScript "as" cast if p.lexer.Loc() == p.forbidSuffixAfterAsLoc { return left } // Reset the optional chain flag by default. That way we won't accidentally // treat "c.d" as OptionalChainContinue in "a?.b + c.d". oldOptionalChain := optionalChain optionalChain = js_ast.OptionalChainNone switch p.lexer.Token { case js_lexer.TDot: p.lexer.Next() if p.lexer.Token == js_lexer.TPrivateIdentifier && p.allowPrivateIdentifiers { // "a.#b" // "a?.b.#c" if _, ok := left.Data.(*js_ast.ESuper); ok { p.lexer.Expected(js_lexer.TIdentifier) } name := p.lexer.Identifier nameLoc := p.lexer.Loc() p.lexer.Next() ref := p.storeNameInRef(name) left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EIndex{ Target: left, Index: js_ast.Expr{Loc: nameLoc, Data: &js_ast.EPrivateIdentifier{Ref: ref}}, OptionalChain: oldOptionalChain, }} } else { // "a.b" // "a?.b.c" if !p.lexer.IsIdentifierOrKeyword() { p.lexer.Expect(js_lexer.TIdentifier) } name := p.lexer.Identifier nameLoc := p.lexer.Loc() p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EDot{ Target: left, Name: name, NameLoc: nameLoc, OptionalChain: oldOptionalChain, }} } optionalChain = oldOptionalChain case js_lexer.TQuestionDot: p.lexer.Next() optionalStart := js_ast.OptionalChainStart // Remove unnecessary optional chains if p.options.mangleSyntax { if isNullOrUndefined, _, ok := toNullOrUndefinedWithSideEffects(left.Data); ok && !isNullOrUndefined { optionalStart = js_ast.OptionalChainNone } } switch p.lexer.Token { case js_lexer.TOpenBracket: // "a?.[b]" p.lexer.Next() // Allow "in" inside the brackets oldAllowIn := p.allowIn p.allowIn = true index := p.parseExpr(js_ast.LLowest) p.allowIn = oldAllowIn p.lexer.Expect(js_lexer.TCloseBracket) left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EIndex{ Target: left, Index: index, OptionalChain: optionalStart, }} case js_lexer.TOpenParen: // "a?.()" if level >= js_ast.LCall { return left } left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.ECall{ Target: left, Args: p.parseCallArgs(), OptionalChain: optionalStart, }} case js_lexer.TLessThan: // "a?.()" if !p.options.ts.Parse { p.lexer.Expected(js_lexer.TIdentifier) } p.skipTypeScriptTypeArguments(false /* isInsideJSXElement */) if p.lexer.Token != js_lexer.TOpenParen { p.lexer.Expected(js_lexer.TOpenParen) } if level >= js_ast.LCall { return left } left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.ECall{ Target: left, Args: p.parseCallArgs(), OptionalChain: optionalStart, }} default: if p.lexer.Token == js_lexer.TPrivateIdentifier && p.allowPrivateIdentifiers { // "a?.#b" name := p.lexer.Identifier nameLoc := p.lexer.Loc() p.lexer.Next() ref := p.storeNameInRef(name) left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EIndex{ Target: left, Index: js_ast.Expr{Loc: nameLoc, Data: &js_ast.EPrivateIdentifier{Ref: ref}}, OptionalChain: optionalStart, }} } else { // "a?.b" if !p.lexer.IsIdentifierOrKeyword() { p.lexer.Expect(js_lexer.TIdentifier) } name := p.lexer.Identifier nameLoc := p.lexer.Loc() p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EDot{ Target: left, Name: name, NameLoc: nameLoc, OptionalChain: optionalStart, }} } } // Only continue if we have started if optionalStart == js_ast.OptionalChainStart { optionalChain = js_ast.OptionalChainContinue } case js_lexer.TNoSubstitutionTemplateLiteral: if oldOptionalChain != js_ast.OptionalChainNone { p.log.AddRangeError(&p.tracker, p.lexer.Range(), "Template literals cannot have an optional chain as a tag") } headLoc := p.lexer.Loc() headCooked, headRaw := p.lexer.CookedAndRawTemplateContents() p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.ETemplate{ TagOrNil: left, HeadLoc: headLoc, HeadCooked: headCooked, HeadRaw: headRaw, }} case js_lexer.TTemplateHead: if oldOptionalChain != js_ast.OptionalChainNone { p.log.AddRangeError(&p.tracker, p.lexer.Range(), "Template literals cannot have an optional chain as a tag") } headLoc := p.lexer.Loc() headCooked, headRaw := p.lexer.CookedAndRawTemplateContents() parts, _ := p.parseTemplateParts(true /* includeRaw */) left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.ETemplate{ TagOrNil: left, HeadLoc: headLoc, HeadCooked: headCooked, HeadRaw: headRaw, Parts: parts, }} case js_lexer.TOpenBracket: // When parsing a decorator, ignore EIndex expressions since they may be // part of a computed property: // // class Foo { // @foo ['computed']() {} // } // // This matches the behavior of the TypeScript compiler. if (flags & exprFlagTSDecorator) != 0 { return left } p.lexer.Next() // Allow "in" inside the brackets oldAllowIn := p.allowIn p.allowIn = true index := p.parseExpr(js_ast.LLowest) p.allowIn = oldAllowIn p.lexer.Expect(js_lexer.TCloseBracket) left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EIndex{ Target: left, Index: index, OptionalChain: oldOptionalChain, }} optionalChain = oldOptionalChain case js_lexer.TOpenParen: if level >= js_ast.LCall { return left } left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.ECall{ Target: left, Args: p.parseCallArgs(), OptionalChain: oldOptionalChain, }} optionalChain = oldOptionalChain case js_lexer.TQuestion: if level >= js_ast.LConditional { return left } p.lexer.Next() // Stop now if we're parsing one of these: // "(a?) => {}" // "(a?: b) => {}" // "(a?, b?) => {}" if p.options.ts.Parse && left.Loc == p.latestArrowArgLoc && (p.lexer.Token == js_lexer.TColon || p.lexer.Token == js_lexer.TCloseParen || p.lexer.Token == js_lexer.TComma) { if errors == nil { p.lexer.Unexpected() } errors.invalidExprAfterQuestion = p.lexer.Range() return left } // Allow "in" in between "?" and ":" oldAllowIn := p.allowIn p.allowIn = true yes := p.parseExpr(js_ast.LComma) p.allowIn = oldAllowIn p.lexer.Expect(js_lexer.TColon) no := p.parseExpr(js_ast.LComma) left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EIf{Test: left, Yes: yes, No: no}} case js_lexer.TExclamation: // Skip over TypeScript non-null assertions if p.lexer.HasNewlineBefore { return left } if !p.options.ts.Parse { p.lexer.Unexpected() } p.lexer.Next() optionalChain = oldOptionalChain case js_lexer.TMinusMinus: if p.lexer.HasNewlineBefore || level >= js_ast.LPostfix { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EUnary{Op: js_ast.UnOpPostDec, Value: left}} case js_lexer.TPlusPlus: if p.lexer.HasNewlineBefore || level >= js_ast.LPostfix { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EUnary{Op: js_ast.UnOpPostInc, Value: left}} case js_lexer.TComma: if level >= js_ast.LComma { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpComma, Left: left, Right: p.parseExpr(js_ast.LComma)}} case js_lexer.TPlus: if level >= js_ast.LAdd { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpAdd, Left: left, Right: p.parseExpr(js_ast.LAdd)}} case js_lexer.TPlusEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpAddAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TMinus: if level >= js_ast.LAdd { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpSub, Left: left, Right: p.parseExpr(js_ast.LAdd)}} case js_lexer.TMinusEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpSubAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TAsterisk: if level >= js_ast.LMultiply { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpMul, Left: left, Right: p.parseExpr(js_ast.LMultiply)}} case js_lexer.TAsteriskAsterisk: if level >= js_ast.LExponentiation { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpPow, Left: left, Right: p.parseExpr(js_ast.LExponentiation - 1)}} case js_lexer.TAsteriskAsteriskEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpPowAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TAsteriskEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpMulAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TPercent: if level >= js_ast.LMultiply { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpRem, Left: left, Right: p.parseExpr(js_ast.LMultiply)}} case js_lexer.TPercentEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpRemAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TSlash: if level >= js_ast.LMultiply { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpDiv, Left: left, Right: p.parseExpr(js_ast.LMultiply)}} case js_lexer.TSlashEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpDivAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TEqualsEquals: if level >= js_ast.LEquals { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpLooseEq, Left: left, Right: p.parseExpr(js_ast.LEquals)}} case js_lexer.TExclamationEquals: if level >= js_ast.LEquals { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpLooseNe, Left: left, Right: p.parseExpr(js_ast.LEquals)}} case js_lexer.TEqualsEqualsEquals: if level >= js_ast.LEquals { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpStrictEq, Left: left, Right: p.parseExpr(js_ast.LEquals)}} case js_lexer.TExclamationEqualsEquals: if level >= js_ast.LEquals { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpStrictNe, Left: left, Right: p.parseExpr(js_ast.LEquals)}} case js_lexer.TLessThan: // TypeScript allows type arguments to be specified with angle brackets // inside an expression. Unlike in other languages, this unfortunately // appears to require backtracking to parse. if p.options.ts.Parse && p.trySkipTypeScriptTypeArgumentsWithBacktracking() { optionalChain = oldOptionalChain continue } if level >= js_ast.LCompare { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpLt, Left: left, Right: p.parseExpr(js_ast.LCompare)}} case js_lexer.TLessThanEquals: if level >= js_ast.LCompare { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpLe, Left: left, Right: p.parseExpr(js_ast.LCompare)}} case js_lexer.TGreaterThan: if level >= js_ast.LCompare { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpGt, Left: left, Right: p.parseExpr(js_ast.LCompare)}} case js_lexer.TGreaterThanEquals: if level >= js_ast.LCompare { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpGe, Left: left, Right: p.parseExpr(js_ast.LCompare)}} case js_lexer.TLessThanLessThan: if level >= js_ast.LShift { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpShl, Left: left, Right: p.parseExpr(js_ast.LShift)}} case js_lexer.TLessThanLessThanEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpShlAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TGreaterThanGreaterThan: if level >= js_ast.LShift { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpShr, Left: left, Right: p.parseExpr(js_ast.LShift)}} case js_lexer.TGreaterThanGreaterThanEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpShrAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TGreaterThanGreaterThanGreaterThan: if level >= js_ast.LShift { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpUShr, Left: left, Right: p.parseExpr(js_ast.LShift)}} case js_lexer.TGreaterThanGreaterThanGreaterThanEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpUShrAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TQuestionQuestion: if level >= js_ast.LNullishCoalescing { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpNullishCoalescing, Left: left, Right: p.parseExpr(js_ast.LNullishCoalescing)}} case js_lexer.TQuestionQuestionEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpNullishCoalescingAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TBarBar: if level >= js_ast.LLogicalOr { return left } // Prevent "||" inside "??" from the right if level == js_ast.LNullishCoalescing { p.lexer.Unexpected() } p.lexer.Next() right := p.parseExpr(js_ast.LLogicalOr) left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpLogicalOr, Left: left, Right: right}} // Prevent "||" inside "??" from the left if level < js_ast.LNullishCoalescing { left = p.parseSuffix(left, js_ast.LNullishCoalescing+1, nil, flags) if p.lexer.Token == js_lexer.TQuestionQuestion { p.lexer.Unexpected() } } case js_lexer.TBarBarEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpLogicalOrAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TAmpersandAmpersand: if level >= js_ast.LLogicalAnd { return left } // Prevent "&&" inside "??" from the right if level == js_ast.LNullishCoalescing { p.lexer.Unexpected() } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpLogicalAnd, Left: left, Right: p.parseExpr(js_ast.LLogicalAnd)}} // Prevent "&&" inside "??" from the left if level < js_ast.LNullishCoalescing { left = p.parseSuffix(left, js_ast.LNullishCoalescing+1, nil, flags) if p.lexer.Token == js_lexer.TQuestionQuestion { p.lexer.Unexpected() } } case js_lexer.TAmpersandAmpersandEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpLogicalAndAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TBar: if level >= js_ast.LBitwiseOr { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpBitwiseOr, Left: left, Right: p.parseExpr(js_ast.LBitwiseOr)}} case js_lexer.TBarEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpBitwiseOrAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TAmpersand: if level >= js_ast.LBitwiseAnd { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpBitwiseAnd, Left: left, Right: p.parseExpr(js_ast.LBitwiseAnd)}} case js_lexer.TAmpersandEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpBitwiseAndAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TCaret: if level >= js_ast.LBitwiseXor { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpBitwiseXor, Left: left, Right: p.parseExpr(js_ast.LBitwiseXor)}} case js_lexer.TCaretEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpBitwiseXorAssign, Left: left, Right: p.parseExpr(js_ast.LAssign - 1)}} case js_lexer.TEquals: if level >= js_ast.LAssign { return left } p.lexer.Next() left = js_ast.Assign(left, p.parseExpr(js_ast.LAssign-1)) case js_lexer.TIn: if level >= js_ast.LCompare || !p.allowIn { return left } // Warn about "!a in b" instead of "!(a in b)" if !p.suppressWarningsAboutWeirdCode { if e, ok := left.Data.(*js_ast.EUnary); ok && e.Op == js_ast.UnOpNot { p.log.AddWarning(&p.tracker, left.Loc, "Suspicious use of the \"!\" operator inside the \"in\" operator") } } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpIn, Left: left, Right: p.parseExpr(js_ast.LCompare)}} case js_lexer.TInstanceof: if level >= js_ast.LCompare { return left } // Warn about "!a instanceof b" instead of "!(a instanceof b)". Here's an // example of code with this problem: https://github.com/mrdoob/three.js/pull/11182. if !p.suppressWarningsAboutWeirdCode { if e, ok := left.Data.(*js_ast.EUnary); ok && e.Op == js_ast.UnOpNot { p.log.AddWarning(&p.tracker, left.Loc, "Suspicious use of the \"!\" operator inside the \"instanceof\" operator") } } p.lexer.Next() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpInstanceof, Left: left, Right: p.parseExpr(js_ast.LCompare)}} default: // Handle the TypeScript "as" operator if p.options.ts.Parse && level < js_ast.LCompare && !p.lexer.HasNewlineBefore && p.lexer.IsContextualKeyword("as") { p.lexer.Next() p.skipTypeScriptType(js_ast.LLowest) // These tokens are not allowed to follow a cast expression. This isn't // an outright error because it may be on a new line, in which case it's // the start of a new expression when it's after a cast: // // x = y as z // (something); // switch p.lexer.Token { case js_lexer.TPlusPlus, js_lexer.TMinusMinus, js_lexer.TNoSubstitutionTemplateLiteral, js_lexer.TTemplateHead, js_lexer.TOpenParen, js_lexer.TOpenBracket, js_lexer.TQuestionDot: p.forbidSuffixAfterAsLoc = p.lexer.Loc() return left } if p.lexer.Token.IsAssign() { p.forbidSuffixAfterAsLoc = p.lexer.Loc() return left } continue } return left } } } func (p *parser) parseExprOrLetStmt(opts parseStmtOpts) (js_ast.Expr, js_ast.Stmt, []js_ast.Decl) { letRange := p.lexer.Range() raw := p.lexer.Raw() if p.lexer.Token != js_lexer.TIdentifier || raw != "let" { var flags exprFlag if opts.isForLoopInit { flags |= exprFlagForLoopInit } if opts.isForAwaitLoopInit { flags |= exprFlagForAwaitLoopInit } return p.parseExprCommon(js_ast.LLowest, nil, flags), js_ast.Stmt{}, nil } p.lexer.Next() switch p.lexer.Token { case js_lexer.TIdentifier, js_lexer.TOpenBracket, js_lexer.TOpenBrace: if opts.lexicalDecl == lexicalDeclAllowAll || !p.lexer.HasNewlineBefore || p.lexer.Token == js_lexer.TOpenBracket { if opts.lexicalDecl != lexicalDeclAllowAll { p.forbidLexicalDecl(letRange.Loc) } p.markSyntaxFeature(compat.Let, letRange) decls := p.parseAndDeclareDecls(js_ast.SymbolOther, opts) return js_ast.Expr{}, js_ast.Stmt{Loc: letRange.Loc, Data: &js_ast.SLocal{ Kind: js_ast.LocalLet, Decls: decls, IsExport: opts.isExport, }}, decls } } ref := p.storeNameInRef(raw) expr := js_ast.Expr{Loc: letRange.Loc, Data: &js_ast.EIdentifier{Ref: ref}} return p.parseSuffix(expr, js_ast.LLowest, nil, 0), js_ast.Stmt{}, nil } func (p *parser) parseCallArgs() []js_ast.Expr { // Allow "in" inside call arguments oldAllowIn := p.allowIn p.allowIn = true args := []js_ast.Expr{} p.lexer.Expect(js_lexer.TOpenParen) for p.lexer.Token != js_lexer.TCloseParen { loc := p.lexer.Loc() isSpread := p.lexer.Token == js_lexer.TDotDotDot if isSpread { p.markSyntaxFeature(compat.RestArgument, p.lexer.Range()) p.lexer.Next() } arg := p.parseExpr(js_ast.LComma) if isSpread { arg = js_ast.Expr{Loc: loc, Data: &js_ast.ESpread{Value: arg}} } args = append(args, arg) if p.lexer.Token != js_lexer.TComma { break } p.lexer.Next() } p.lexer.Expect(js_lexer.TCloseParen) p.allowIn = oldAllowIn return args } func (p *parser) parseJSXTag() (logger.Range, string, js_ast.Expr) { loc := p.lexer.Loc() // A missing tag is a fragment if p.lexer.Token == js_lexer.TGreaterThan { return logger.Range{Loc: loc, Len: 0}, "", js_ast.Expr{} } // The tag is an identifier name := p.lexer.Identifier tagRange := p.lexer.Range() p.lexer.ExpectInsideJSXElement(js_lexer.TIdentifier) // Certain identifiers are strings if strings.ContainsAny(name, "-:") || (p.lexer.Token != js_lexer.TDot && name[0] >= 'a' && name[0] <= 'z') { return tagRange, name, js_ast.Expr{Loc: loc, Data: &js_ast.EString{Value: js_lexer.StringToUTF16(name)}} } // Otherwise, this is an identifier tag := js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: p.storeNameInRef(name)}} // Parse a member expression chain for p.lexer.Token == js_lexer.TDot { p.lexer.NextInsideJSXElement() memberRange := p.lexer.Range() member := p.lexer.Identifier p.lexer.ExpectInsideJSXElement(js_lexer.TIdentifier) // Dashes are not allowed in member expression chains index := strings.IndexByte(member, '-') if index >= 0 { p.log.AddError(&p.tracker, logger.Loc{Start: memberRange.Loc.Start + int32(index)}, "Unexpected \"-\"") panic(js_lexer.LexerPanic{}) } name += "." + member tag = js_ast.Expr{Loc: loc, Data: &js_ast.EDot{ Target: tag, Name: member, NameLoc: memberRange.Loc, }} tagRange.Len = memberRange.Loc.Start + memberRange.Len - tagRange.Loc.Start } return tagRange, name, tag } func (p *parser) parseJSXElement(loc logger.Loc) js_ast.Expr { // Parse the tag startRange, startText, startTagOrNil := p.parseJSXTag() // The tag may have TypeScript type arguments: "/>" if p.options.ts.Parse { // Pass a flag to the type argument skipper because we need to call // js_lexer.NextInsideJSXElement() after we hit the closing ">". The next // token after the ">" might be an attribute name with a dash in it // like this: " data-disabled/>" p.skipTypeScriptTypeArguments(true /* isInsideJSXElement */) } // Parse attributes var previousStringWithBackslashLoc logger.Loc properties := []js_ast.Property{} if startTagOrNil.Data != nil { parseAttributes: for { switch p.lexer.Token { case js_lexer.TIdentifier: // Parse the key keyRange := p.lexer.Range() key := js_ast.Expr{Loc: keyRange.Loc, Data: &js_ast.EString{Value: js_lexer.StringToUTF16(p.lexer.Identifier)}} p.lexer.NextInsideJSXElement() // Parse the value var value js_ast.Expr wasShorthand := false if p.lexer.Token != js_lexer.TEquals { // Implicitly true value wasShorthand = true value = js_ast.Expr{Loc: logger.Loc{Start: keyRange.Loc.Start + keyRange.Len}, Data: &js_ast.EBoolean{Value: true}} } else { // Use NextInsideJSXElement() not Next() so we can parse a JSX-style string literal p.lexer.NextInsideJSXElement() if p.lexer.Token == js_lexer.TStringLiteral { stringLoc := p.lexer.Loc() if p.lexer.PreviousBackslashQuoteInJSX.Loc.Start > stringLoc.Start { previousStringWithBackslashLoc = stringLoc } value = js_ast.Expr{Loc: stringLoc, Data: &js_ast.EString{Value: p.lexer.StringLiteral()}} p.lexer.NextInsideJSXElement() } else { // Use Expect() not ExpectInsideJSXElement() so we can parse expression tokens p.lexer.Expect(js_lexer.TOpenBrace) value = p.parseExpr(js_ast.LLowest) p.lexer.ExpectInsideJSXElement(js_lexer.TCloseBrace) } } // Add a property properties = append(properties, js_ast.Property{ Key: key, ValueOrNil: value, WasShorthand: wasShorthand, }) case js_lexer.TOpenBrace: // Use Next() not ExpectInsideJSXElement() so we can parse "..." p.lexer.Next() p.lexer.Expect(js_lexer.TDotDotDot) value := p.parseExpr(js_ast.LComma) properties = append(properties, js_ast.Property{ Kind: js_ast.PropertySpread, ValueOrNil: value, }) // Use NextInsideJSXElement() not Next() so we can parse ">>" as ">" p.lexer.NextInsideJSXElement() default: break parseAttributes } } } // People sometimes try to use the output of "JSON.stringify()" as a JSX // attribute when automatically-generating JSX code. Doing so is incorrect // because JSX strings work like XML instead of like JS (since JSX is XML-in- // JS). Specifically, using a backslash before a quote does not cause it to // be escaped: // // JSX ends the "content" attribute here and sets "content" to 'some so-called \\' // v //