| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Alexander Akait @alexander-akait
- */
- "use strict";
- // cspell:ignore yuku binop prec Prec
- const { Parser: BaseParser, tokTypes } = require("acorn");
- // acorn exports its token-context table but leaves it out of its public types
- const tokContexts =
- /** @type {Record<string, unknown>} */
- (
- /** @type {{ tokContexts: Record<string, unknown> }} */
- (/** @type {unknown} */ (require("acorn"))).tokContexts
- );
- /** @typedef {{ token: string, isExpr: boolean, preserveSpace?: boolean, override?: unknown }} TokContextShim acorn TokContext fields read by the owned tokenizer */
- // acorn's token contexts used by the inlined finishToken context updates
- const CTX_B_STAT = /** @type {TokContextShim} */ (tokContexts.b_stat);
- const CTX_B_EXPR = /** @type {TokContextShim} */ (tokContexts.b_expr);
- const CTX_P_STAT = /** @type {TokContextShim} */ (tokContexts.p_stat);
- const CTX_P_EXPR = /** @type {TokContextShim} */ (tokContexts.p_expr);
- const CTX_F_STAT = /** @type {TokContextShim} */ (tokContexts.f_stat);
- const CTX_F_EXPR = /** @type {TokContextShim} */ (tokContexts.f_expr);
- // acorn exports its keyword→TokenType map but leaves it out of its public
- // types; used by the word-classification lookups below.
- const keywordTypes =
- /** @type {Record<string, TokenType>} */
- (
- /** @type {{ keywordTypes: Record<string, TokenType> }} */
- (/** @type {unknown} */ (require("acorn"))).keywordTypes
- );
- // acorn exports these Unicode/character helpers at runtime but leaves them out
- // of its public types; the ported cold-path readers below reuse them so their
- // classification stays byte-identical to acorn's tokenizer.
- const {
- isIdentifierChar,
- isIdentifierStart,
- isNewLine,
- lineBreak,
- nonASCIIwhitespace
- } =
- /** @type {{ isIdentifierStart: (code: number, astral?: boolean) => boolean, isIdentifierChar: (code: number, astral?: boolean) => boolean, isNewLine: (code: number) => boolean, lineBreak: RegExp, nonASCIIwhitespace: RegExp }} */
- (/** @type {unknown} */ (require("acorn")));
- /**
- * @param {number} code code point
- * @returns {string} the string for a single code point
- */
- const codePointToString = (code) => String.fromCodePoint(code);
- /**
- * acorn's `stringToNumber`: legacy octal parses in base 8, everything else via
- * `parseFloat` after dropping `_` separators.
- * @param {string} str numeric literal text (may contain `_` separators)
- * @param {boolean} isLegacyOctal whether it is a legacy octal literal
- * @returns {number} numeric value
- */
- const stringToNumber = (str, isLegacyOctal) =>
- isLegacyOctal
- ? Number.parseInt(str, 8)
- : Number.parseFloat(str.replace(/_/g, ""));
- /**
- * acorn's `stringToBigInt`, minus the pre-BigInt fallback (every supported
- * Node has `BigInt`).
- * @param {string} str numeric literal text (may contain `_` separators)
- * @returns {bigint} bigint value
- */
- const stringToBigInt = (str) => BigInt(str.replace(/_/g, ""));
- /**
- * @import {
- * Options,
- * Position,
- * Node,
- * Identifier,
- * ImportAttribute,
- * ImportDefaultSpecifier,
- * ImportExpression,
- * Expression,
- * TokenType
- * } from "acorn"
- */
- /** @typedef {import("acorn").ImportSpecifier | import("acorn").ImportDefaultSpecifier | import("acorn").ImportNamespaceSpecifier} AnyImportSpecifier */
- /** @typedef {TokenType & { beforeExpr: boolean, isAssign?: boolean, prefix?: boolean, postfix?: boolean, binop: number | null, updateContext: ((prevType: TokenType) => void) | null }} TokenTypeInternal acorn's internal TokenType fields, absent from its public types */
- /** @typedef {[number, number]} Range */
- /** @typedef {"defer" | "source"} ImportPhase */
- /** @typedef {import("estree").Comment & { start: number, end: number }} CollectedComment comment as JavascriptParser exposes it */
- // Symbol-keyed so they stay out of for-in, Object.keys and JSON.stringify
- // over AST nodes.
- // Per-TokContext "the owned token loop must step aside" flag, cached on the
- // context itself so the hot guard reads one slot instead of two.
- const kSlowContext = Symbol("slow context");
- const kSource = Symbol("source");
- const kRange = Symbol("range");
- const kText = Symbol("text");
- const kTextStart = Symbol("text start");
- // Marks import attributes parsed from the legacy `assert {...}` syntax.
- const LEGACY_ASSERT_ATTRIBUTES = Symbol("assert");
- // acorn's binding types and scope flags, stable across acorn 8
- const BIND_NONE = 0;
- const BIND_VAR = 1;
- const BIND_LEXICAL = 2;
- const BIND_OUTSIDE = 5;
- const SCOPE_TOP = 1;
- const SCOPE_FUNCTION = 2;
- const SCOPE_ASYNC = 4;
- const SCOPE_GENERATOR = 8;
- const SCOPE_ARROW = 16;
- const SCOPE_SIMPLE_CATCH = 32;
- // SCOPE_TOP | SCOPE_FUNCTION | SCOPE_CLASS_STATIC_BLOCK
- const SCOPE_SWITCH = 1024;
- const SCOPE_VAR = 0b100000011;
- // acorn's parseFunction statement bit flags
- const FUNC_STATEMENT = 1;
- const FUNC_HANGING_STATEMENT = 2;
- const FUNC_NULLABLE_ID = 4;
- // ASCII identifier-continuation chars ($ 0-9 A-Z _ a-z); css/html-style
- // Uint8Array table so the tokenizer fast path is one load per char
- const IDENT_CHAR = new Uint8Array(128);
- IDENT_CHAR[36] = 1;
- IDENT_CHAR[95] = 1;
- for (let i = 48; i <= 57; i++) IDENT_CHAR[i] = 1;
- for (let i = 65; i <= 90; i++) IDENT_CHAR[i] = 1;
- for (let i = 97; i <= 122; i++) IDENT_CHAR[i] = 1;
- // ASCII identifier-start chars (IDENT_CHAR minus 0-9), for token dispatch in
- // the owned `nextToken` loop.
- const IDENT_START = new Uint8Array(128);
- IDENT_START[36] = 1;
- IDENT_START[95] = 1;
- for (let i = 65; i <= 90; i++) IDENT_START[i] = 1;
- for (let i = 97; i <= 122; i++) IDENT_START[i] = 1;
- // Single-char punctuators that acorn's `getTokenFromCode` reads as just
- // `++pos; finishToken(type)` (no value, no operator state machine). Dispatching
- // them from `nextToken`'s char table skips the extra `getTokenFromCode` call and
- // its switch for the commonest tokens in JS ( ) { } [ ] ; , : — `0` is "not a
- // simple punctuator" since token types are truthy objects.
- const SIMPLE_PUNCT = Array.from({ length: 128 });
- SIMPLE_PUNCT[40] = tokTypes.parenL;
- SIMPLE_PUNCT[41] = tokTypes.parenR;
- SIMPLE_PUNCT[59] = tokTypes.semi;
- SIMPLE_PUNCT[44] = tokTypes.comma;
- SIMPLE_PUNCT[91] = tokTypes.bracketL;
- SIMPLE_PUNCT[93] = tokTypes.bracketR;
- SIMPLE_PUNCT[123] = tokTypes.braceL;
- SIMPLE_PUNCT[125] = tokTypes.braceR;
- SIMPLE_PUNCT[58] = tokTypes.colon;
- // Characters that can only start a token which cannot continue an expression,
- // so an atom directly followed by one is the whole expression (see
- // `parseMaybeAssign`'s fast path). `}` included: it only ever closes a block,
- // an object or a template substitution.
- const EXPRESSION_END_CHAR = new Uint8Array(128);
- EXPRESSION_END_CHAR[41] = 1; // )
- EXPRESSION_END_CHAR[44] = 1; // ,
- EXPRESSION_END_CHAR[58] = 1; // :
- EXPRESSION_END_CHAR[59] = 1; // ;
- EXPRESSION_END_CHAR[93] = 1; // ]
- EXPRESSION_END_CHAR[125] = 1; // }
- // Char classification for the owned `nextToken` (yuku's ws_class): one table
- // load steers both the whitespace skip loop and the token dispatch. Token
- // classes sort below CLS_SPACE so the skip loop exits on a single compare.
- const CLS_OTHER = 0;
- const CLS_IDENT = 1;
- const CLS_PUNCT = 2;
- const CLS_DOT = 3;
- const CLS_EQ = 4;
- const CLS_UNICODE = 5;
- const CLS_SPACE = 6;
- const CLS_NEWLINE = 7;
- const CLS_SLASH = 8;
- // Full `charCodeAt` range so the scan loop needs no `code > 127` branch per
- // character: every non-ASCII code unit classifies as CLS_UNICODE (which sorts
- // below CLS_SPACE, so the loop exits on the same single compare) and the
- // dispatch delegates it to acorn's unicode-aware paths.
- const CHAR_CLASS = new Uint8Array(0x10000).fill(CLS_UNICODE, 128);
- for (let i = 0; i < 128; i++) {
- if (IDENT_START[i] === 1 || i === 92) CHAR_CLASS[i] = CLS_IDENT;
- else if (SIMPLE_PUNCT[i] !== undefined) CHAR_CLASS[i] = CLS_PUNCT;
- }
- CHAR_CLASS[46] = CLS_DOT;
- CHAR_CLASS[61] = CLS_EQ;
- CHAR_CLASS[32] = CLS_SPACE;
- CHAR_CLASS[9] = CLS_SPACE;
- CHAR_CLASS[11] = CLS_SPACE;
- CHAR_CLASS[12] = CLS_SPACE;
- CHAR_CLASS[10] = CLS_NEWLINE;
- CHAR_CLASS[13] = CLS_NEWLINE;
- CHAR_CLASS[47] = CLS_SLASH;
- /**
- * Drop-in replacement for acorn's `Node` that materializes `loc` and `range`
- * on first access instead of allocating them during parsing. Most nodes never
- * get either read, which saves three objects and an array per node.
- */
- class LazyLocNode {
- /**
- * @param {number} pos start offset
- */
- constructor(pos) {
- this.type = "";
- this.start = pos;
- this.end = 0;
- }
- /**
- * Memoized in a symbol slot — a plain store is far cheaper than making the
- * property own via defineProperty, and the slot stays invisible to for-in,
- * Object.keys and JSON.stringify. No `loc` is served at all — locations
- * are derived from offsets via `JavascriptParser#getLocation`.
- * @returns {Range} source range
- */
- get range() {
- const cached = this[kRange];
- if (cached !== undefined) return cached;
- /** @type {Range} */
- const range = [this.start, this.end];
- if (this.end > 0) this[kRange] = range;
- return range;
- }
- /**
- * @param {Range} value source range
- */
- set range(value) {
- this[kRange] = value;
- }
- }
- /**
- * Single-shape `Identifier`, the most common node: all fields are assigned in
- * one constructor, so every instance is born on its final hidden class instead
- * of transitioning through acorn's start-empty-then-mutate construction.
- */
- class IdentifierNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {string} name identifier name
- */
- constructor(start, end, name) {
- this.type = "Identifier";
- this.start = start;
- this.end = end;
- this.name = name;
- }
- }
- /**
- * Single-shape `Literal`; `bigint` and `regex` stay post-construction
- * additions since both are rare.
- */
- class LiteralNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {unknown} value literal value
- * @param {string} raw literal source text
- */
- constructor(start, end, value, raw) {
- this.type = "Literal";
- this.start = start;
- this.end = end;
- this.value = value;
- this.raw = raw;
- }
- }
- /**
- * Single-shape `MemberExpression`. `optional` is a real field on every
- * instance since webpack always parses with `ecmaVersion >= 11`.
- */
- class MemberExpressionNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression} object object expression
- * @param {Node} property property node
- * @param {boolean} computed whether the access is computed (`a[b]`)
- * @param {boolean} optional whether the access is optional (`a?.b`)
- */
- constructor(start, end, object, property, computed, optional) {
- this.type = "MemberExpression";
- this.start = start;
- this.end = end;
- this.object = object;
- this.property = property;
- this.computed = computed;
- this.optional = optional;
- }
- }
- /**
- * Single-shape `CallExpression`; `optional` as in `MemberExpressionNode`.
- */
- class CallExpressionNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression} callee callee expression
- * @param {Node[]} args call arguments
- * @param {boolean} optional whether the call is optional (`a?.()`)
- */
- constructor(start, end, callee, args, optional) {
- this.type = "CallExpression";
- this.start = start;
- this.end = end;
- this.callee = callee;
- this.arguments = args;
- this.optional = optional;
- }
- }
- /**
- * Single-shape `ThisExpression`.
- */
- class ThisNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- */
- constructor(start, end) {
- this.type = "ThisExpression";
- this.start = start;
- this.end = end;
- }
- }
- /**
- * Single-shape `BinaryExpression`/`LogicalExpression` — identical field sets,
- * so both node types share one hidden class.
- */
- class BinaryNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {"BinaryExpression" | "LogicalExpression"} type node type
- * @param {Expression} left left operand
- * @param {string} operator operator text
- * @param {Expression} right right operand
- */
- constructor(start, end, type, left, operator, right) {
- this.type = type;
- this.start = start;
- this.end = end;
- this.left = left;
- this.operator = operator;
- this.right = right;
- }
- }
- /**
- * Single-shape `AssignmentExpression`.
- */
- class AssignmentNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {string} operator assignment operator text
- * @param {Node} left assignment target
- * @param {Expression} right assigned value
- */
- constructor(start, end, operator, left, right) {
- this.type = "AssignmentExpression";
- this.start = start;
- this.end = end;
- this.operator = operator;
- this.left = left;
- this.right = right;
- }
- }
- /**
- * Single-shape `UnaryExpression`/`UpdateExpression` — identical field sets,
- * so both node types share one hidden class.
- */
- class UnaryNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {"UnaryExpression" | "UpdateExpression"} type node type
- * @param {string} operator operator text
- * @param {boolean} prefix whether the operator is prefixed
- * @param {Expression} argument operand
- */
- constructor(start, end, type, operator, prefix, argument) {
- this.type = type;
- this.start = start;
- this.end = end;
- this.operator = operator;
- this.prefix = prefix;
- this.argument = argument;
- }
- }
- /**
- * Single-shape `VariableDeclaration` (statement position; `for` heads keep the
- * generic node since their caller finishes them).
- */
- class VariableDeclarationNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Node[]} declarations declarators
- * @param {string} kind declaration kind (`var`/`let`/`const`/`using`)
- */
- constructor(start, end, declarations, kind) {
- this.type = "VariableDeclaration";
- this.start = start;
- this.end = end;
- this.declarations = declarations;
- this.kind = kind;
- }
- }
- /**
- * Single-shape `VariableDeclarator`.
- */
- class VariableDeclaratorNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Node} id binding target
- * @param {Expression | null} init initializer
- */
- constructor(start, end, id, init) {
- this.type = "VariableDeclarator";
- this.start = start;
- this.end = end;
- this.id = id;
- this.init = init;
- }
- }
- /**
- * Single-shape `ExpressionStatement`.
- */
- class ExpressionStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression} expression the statement's expression
- */
- constructor(start, end, expression) {
- this.type = "ExpressionStatement";
- this.start = start;
- this.end = end;
- this.expression = expression;
- }
- }
- /**
- * Single-shape `BlockStatement`.
- */
- class BlockStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Node[]} body statements
- */
- constructor(start, end, body) {
- this.type = "BlockStatement";
- this.start = start;
- this.end = end;
- this.body = body;
- }
- }
- /**
- * Single-shape `IfStatement`.
- */
- class IfStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression} test condition
- * @param {Node} consequent then-branch
- * @param {Node | null} alternate else-branch
- */
- constructor(start, end, test, consequent, alternate) {
- this.type = "IfStatement";
- this.start = start;
- this.end = end;
- this.test = test;
- this.consequent = consequent;
- this.alternate = alternate;
- }
- }
- /**
- * Single-shape `ReturnStatement`.
- */
- class ReturnStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression | null} argument returned expression
- */
- constructor(start, end, argument) {
- this.type = "ReturnStatement";
- this.start = start;
- this.end = end;
- this.argument = argument;
- }
- }
- /**
- * Single-shape `ConditionalExpression`.
- */
- class ConditionalExpressionNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression} test condition
- * @param {Expression} consequent then-value
- * @param {Expression} alternate else-value
- */
- constructor(start, end, test, consequent, alternate) {
- this.type = "ConditionalExpression";
- this.start = start;
- this.end = end;
- this.test = test;
- this.consequent = consequent;
- this.alternate = alternate;
- }
- }
- /**
- * Single-shape `NewExpression`.
- */
- class NewExpressionNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression} callee constructed expression
- * @param {Expression[]} args constructor arguments
- */
- constructor(start, end, callee, args) {
- this.type = "NewExpression";
- this.start = start;
- this.end = end;
- this.callee = callee;
- this.arguments = args;
- }
- }
- /**
- * Single-shape `ArrayExpression`.
- */
- class ArrayExpressionNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {(Expression | null)[]} elements array elements (`null` for holes)
- */
- constructor(start, end, elements) {
- this.type = "ArrayExpression";
- this.start = start;
- this.end = end;
- this.elements = elements;
- }
- }
- /**
- * Single-shape `TemplateLiteral`.
- */
- class TemplateLiteralNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression[]} expressions substitution expressions
- * @param {Node[]} quasis template chunks
- */
- constructor(start, end, expressions, quasis) {
- this.type = "TemplateLiteral";
- this.start = start;
- this.end = end;
- this.expressions = expressions;
- this.quasis = quasis;
- }
- }
- /**
- * Single-shape `TemplateElement`.
- */
- class TemplateElementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {{ raw: string, cooked: string | null }} value chunk text
- * @param {boolean} tail whether this is the closing chunk
- */
- constructor(start, end, value, tail) {
- this.type = "TemplateElement";
- this.start = start;
- this.end = end;
- this.value = value;
- this.tail = tail;
- }
- }
- /**
- * Single-shape `ObjectExpression`/`ObjectPattern` — identical field sets, so
- * both node types share one hidden class.
- */
- class ObjectNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {"ObjectExpression" | "ObjectPattern"} type node type
- * @param {Node[]} properties properties
- */
- constructor(start, end, type, properties) {
- this.type = type;
- this.start = start;
- this.end = end;
- this.properties = properties;
- }
- }
- /**
- * Pre-shaped `Property`: acorn fills property nodes through shared
- * subroutines (`parsePropertyName`/`parsePropertyValue`), so instead of
- * rebuilding that flow the fields are all declared up-front and acorn's
- * writes land in existing slots — one hidden class, no transitions (yuku's
- * decoder emits `Property` with this fixed shape). Every non-throwing acorn
- * branch assigns `computed`, `key`, `value` and `kind`; `finishNode` sets
- * `type` and `end`.
- */
- class PropertyNode {
- /**
- * @param {number} start start offset
- */
- constructor(start) {
- this.type = "";
- this.start = start;
- this.end = 0;
- this.method = false;
- this.shorthand = false;
- this.computed = false;
- /** @type {Node | null} */
- this.key = null;
- /** @type {Node | null} */
- this.value = null;
- this.kind = "";
- }
- }
- /**
- * Single-shape `ForStatement`.
- */
- class ForStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Node | null} init init statement or expression
- * @param {Expression | null} test loop condition
- * @param {Expression | null} update update expression
- * @param {Node} body loop body
- */
- constructor(start, end, init, test, update, body) {
- this.type = "ForStatement";
- this.start = start;
- this.end = end;
- this.init = init;
- this.test = test;
- this.update = update;
- this.body = body;
- }
- }
- /**
- * Single-shape `ForInStatement` (no `await` slot, matching acorn).
- */
- class ForInStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Node} left loop target
- * @param {Expression} right iterated expression
- * @param {Node} body loop body
- */
- constructor(start, end, left, right, body) {
- this.type = "ForInStatement";
- this.start = start;
- this.end = end;
- this.left = left;
- this.right = right;
- this.body = body;
- }
- }
- /**
- * Single-shape `ForOfStatement`; `await` leads, matching acorn's write order.
- */
- class ForOfStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {boolean} isAwait whether this is `for await`
- * @param {Node} left loop target
- * @param {Expression} right iterated expression
- * @param {Node} body loop body
- */
- constructor(start, end, isAwait, left, right, body) {
- this.type = "ForOfStatement";
- this.start = start;
- this.end = end;
- this.await = isAwait;
- this.left = left;
- this.right = right;
- this.body = body;
- }
- }
- /**
- * Single-shape `WhileStatement`.
- */
- class WhileStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression} test loop condition
- * @param {Node} body loop body
- */
- constructor(start, end, test, body) {
- this.type = "WhileStatement";
- this.start = start;
- this.end = end;
- this.test = test;
- this.body = body;
- }
- }
- /**
- * Single-shape `SwitchStatement`.
- */
- class SwitchStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression} discriminant switched expression
- * @param {Node[]} cases case clauses
- */
- constructor(start, end, discriminant, cases) {
- this.type = "SwitchStatement";
- this.start = start;
- this.end = end;
- this.discriminant = discriminant;
- this.cases = cases;
- }
- }
- /**
- * Pre-shaped `SwitchCase`: filled in acorn's write order (`consequent` before
- * `test`), finished via `finishNode` like acorn's.
- */
- class SwitchCaseNode {
- /**
- * @param {number} start start offset
- */
- constructor(start) {
- this.type = "";
- this.start = start;
- this.end = 0;
- /** @type {Node[] | null} */
- this.consequent = null;
- /** @type {Expression | null} */
- this.test = null;
- }
- }
- /**
- * Single-shape `ThrowStatement`.
- */
- class ThrowStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Expression} argument thrown expression
- */
- constructor(start, end, argument) {
- this.type = "ThrowStatement";
- this.start = start;
- this.end = end;
- this.argument = argument;
- }
- }
- /**
- * Single-shape `BreakStatement`/`ContinueStatement` — identical field sets, so
- * both node types share one hidden class.
- */
- class BreakContinueNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {"BreakStatement" | "ContinueStatement"} type node type
- * @param {Identifier | null} label target label
- */
- constructor(start, end, type, label) {
- this.type = type;
- this.start = start;
- this.end = end;
- this.label = label;
- }
- }
- /**
- * Single-shape `TryStatement`.
- */
- class TryStatementNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Node} block try block
- * @param {Node | null} handler catch clause
- * @param {Node | null} finalizer finally block
- */
- constructor(start, end, block, handler, finalizer) {
- this.type = "TryStatement";
- this.start = start;
- this.end = end;
- this.block = block;
- this.handler = handler;
- this.finalizer = finalizer;
- }
- }
- /**
- * Single-shape `CatchClause`.
- */
- class CatchClauseNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {Node | null} param catch parameter
- * @param {Node} body catch block
- */
- constructor(start, end, param, body) {
- this.type = "CatchClause";
- this.start = start;
- this.end = end;
- this.param = param;
- this.body = body;
- }
- }
- /**
- * Pre-shaped `FunctionDeclaration`/`FunctionExpression`/
- * `ArrowFunctionExpression`: acorn fills function nodes through shared
- * subroutines (`parseFunctionParams`/`parseFunctionBody`), so like
- * `PropertyNode` the fields are all declared up-front — in acorn's exact write
- * order (`initFunction` assigns `expression` before `generator`) so JSON key
- * order is unchanged — and acorn's writes land in existing slots: one hidden
- * class for all three function node types, no transitions.
- */
- class FunctionNode {
- /**
- * @param {number} start start offset
- */
- constructor(start) {
- this.type = "";
- this.start = start;
- this.end = 0;
- /** @type {Identifier | null} */
- this.id = null;
- this.expression = false;
- this.generator = false;
- this.async = false;
- /** @type {Node[] | null} */
- this.params = null;
- /** @type {Node | null} */
- this.body = null;
- }
- }
- /**
- * Single-shape `SpreadElement`/`RestElement` — identical field sets, so both
- * node types share one hidden class.
- */
- class RestSpreadNode {
- /**
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {"SpreadElement" | "RestElement"} type node type
- * @param {Node} argument spread/rest argument
- */
- constructor(start, end, type, argument) {
- this.type = type;
- this.start = start;
- this.end = end;
- this.argument = argument;
- }
- }
- // Mirrors of acorn's module-level `loopLabel`/`switchLabel`.
- const LOOP_LABEL = { kind: "loop" };
- const SWITCH_LABEL = { kind: "switch" };
- // Shared zero-length arguments array for `new X` without parens, mirroring
- // acorn's module-level `empty`.
- /** @type {Expression[]} */
- const EMPTY_NEW_ARGS = [];
- /**
- * Mirror of acorn's module-level `isLocalVariableAccess`.
- * @param {Node} node checked node
- * @returns {boolean} whether the node reads a local variable
- */
- const isLocalVariableAccess = (node) =>
- node.type === "Identifier" ||
- (node.type === "ParenthesizedExpression" &&
- isLocalVariableAccess(
- /** @type {Node} */ (
- /** @type {Node & { expression?: Node }} */ (node).expression
- )
- ));
- /**
- * Mirror of acorn's module-level `isPrivateFieldAccess`.
- * @param {Node} node checked node
- * @returns {boolean} whether the node accesses a private field
- */
- const isPrivateFieldAccess = (node) =>
- (node.type === "MemberExpression" &&
- /** @type {Node} */ (
- /** @type {Node & { property?: Node }} */ (node).property
- ).type === "PrivateIdentifier") ||
- (node.type === "ChainExpression" &&
- isPrivateFieldAccess(
- /** @type {Node} */ (
- /** @type {Node & { expression?: Node }} */ (node).expression
- )
- )) ||
- (node.type === "ParenthesizedExpression" &&
- isPrivateFieldAccess(
- /** @type {Node} */ (
- /** @type {Node & { expression?: Node }} */ (node).expression
- )
- ));
- // the dedicated node classes serve `range` exactly like LazyLocNode
- for (const NodeClass of [
- IdentifierNode,
- LiteralNode,
- MemberExpressionNode,
- CallExpressionNode,
- ThisNode,
- BinaryNode,
- AssignmentNode,
- UnaryNode,
- VariableDeclarationNode,
- VariableDeclaratorNode,
- ExpressionStatementNode,
- BlockStatementNode,
- IfStatementNode,
- ReturnStatementNode,
- ConditionalExpressionNode,
- NewExpressionNode,
- ArrayExpressionNode,
- TemplateLiteralNode,
- TemplateElementNode,
- ObjectNode,
- PropertyNode,
- RestSpreadNode,
- FunctionNode,
- ForStatementNode,
- ForInStatementNode,
- ForOfStatementNode,
- WhileStatementNode,
- SwitchStatementNode,
- SwitchCaseNode,
- ThrowStatementNode,
- BreakContinueNode,
- TryStatementNode,
- CatchClauseNode
- ]) {
- for (const key of ["range"]) {
- Object.defineProperty(
- NodeClass.prototype,
- key,
- /** @type {PropertyDescriptor} */
- (Object.getOwnPropertyDescriptor(LazyLocNode.prototype, key))
- );
- }
- }
- /**
- * Comment collected without slicing its text out of the source: only magic
- * comments and pure annotations ever get their text read, and only binary
- * searches around magic-comment sites read `range`, so the text slice and the
- * range array are both deferred to first access and memoized like `loc`.
- */
- class LazyComment {
- /**
- * @param {boolean} block whether this is a block comment
- * @param {number} textStart offset right after the comment opener
- * @param {number} start start offset
- * @param {number} end end offset
- * @param {string} source full source text for the lazy `value` slice
- */
- constructor(block, textStart, start, end, source) {
- /** @type {"Block" | "Line"} */
- this.type = block ? "Block" : "Line";
- this.start = start;
- this.end = end;
- this[kSource] = source;
- this[kTextStart] = textStart;
- }
- /**
- * Memoized like `LazyLocNode#range`; offsets are final at construction so
- * the slot is cached unconditionally.
- * @returns {Range} source range
- */
- get range() {
- const cached = this[kRange];
- if (cached !== undefined) return cached;
- /** @type {Range} */
- const range = [this.start, this.end];
- return (this[kRange] = range);
- }
- /**
- * @param {Range} value source range
- */
- set range(value) {
- this[kRange] = value;
- }
- /**
- * @returns {string} comment text without the delimiters
- */
- get value() {
- const cached = this[kText];
- if (cached !== undefined) return cached;
- return (this[kText] = this[kSource].slice(
- this[kTextStart],
- this.type === "Block" ? this.end - 2 : this.end
- ));
- }
- /**
- * @param {string} value comment text
- */
- set value(value) {
- this[kText] = value;
- }
- }
- /**
- * Replaces acorn's array-backed `Scope`: membership checks in `declareName`
- * are `indexOf` there, which goes quadratic on files with thousands of
- * bindings per scope (bundled or minified inputs). The three Sets are
- * allocated lazily — most scopes declare into only one (module `functions` is
- * always empty), so ~⅔ of the Sets are never needed.
- */
- class Scope {
- /**
- * @param {number} flags scope flags
- */
- constructor(flags) {
- this.flags = flags;
- /** @type {Set<string> | undefined} */
- this.var = undefined;
- /** @type {Set<string> | undefined} */
- this.lexical = undefined;
- /** @type {Set<string> | undefined} */
- this.functions = undefined;
- // first lexically-declared name; stands in for acorn's `lexical[0]`
- // (the catch parameter of a simple catch scope)
- /** @type {string | undefined} */
- this.firstLexical = undefined;
- }
- }
- /**
- * Acorn's methods and state used by `WebpackParser` but missing from its
- * public types, plus `WebpackParser`'s own fields, so overridden methods can
- * declare `this` precisely.
- * @typedef {import("acorn").Parser & {
- * type: TokenType,
- * value: unknown,
- * start: number,
- * startLoc?: Position,
- * containsEsc: boolean,
- * exprAllowed: boolean,
- * options: Options,
- * end: number,
- * lastTokEnd: number,
- * canInsertSemicolon: () => boolean,
- * nextToken: () => void,
- * next: (ignoreEscapeSequenceInKeyword?: boolean) => void,
- * eat: (type: TokenType) => boolean,
- * expect: (type: TokenType) => void,
- * afterTrailingComma: (type: TokenType, notNext?: boolean) => boolean,
- * unexpected: (pos?: number) => never,
- * raise: (pos: number, message: string) => never,
- * raiseRecoverable: (pos: number, message: string) => void,
- * isContextual: (name: string) => boolean,
- * parseIdent: (liberal?: boolean) => Identifier,
- * parseLiteral: (value: unknown) => Node,
- * awaitIdentPos: number,
- * lastTokStart: number,
- * lastTokStartLoc?: Position,
- * yieldPos: number,
- * awaitPos: number,
- * parseExpression: (forInit?: boolean | string, refDestructuringErrors?: DestructuringErrorsShim | null) => Expression,
- * parseSpread: (refDestructuringErrors?: DestructuringErrorsShim | null) => Node,
- * braceIsBlock: (prevType: TokenType) => boolean,
- * _gapHasNewline: () => boolean,
- * parseExprList: (close: TokenType, allowTrailingComma: boolean, allowEmpty: boolean, refDestructuringErrors?: DestructuringErrorsShim | null) => Expression[],
- * parsePrivateIdent: () => Node,
- * parseTemplate: (opts: { isTagged: boolean }) => Node,
- * shouldParseAsyncArrow: () => boolean,
- * parseSubscriptAsyncArrow: (startPos: number, startLoc: Position | undefined, exprList: Expression[], forInit: boolean | string) => Expression,
- * checkPatternErrors: (refDestructuringErrors: DestructuringErrorsShim, isAssign: boolean) => void,
- * checkYieldAwaitInDefaultParams: () => void,
- * checkExpressionErrors: (refDestructuringErrors?: DestructuringErrorsShim | null, andThrow?: boolean) => boolean,
- * parseSubscript: (base: Expression, startPos: number, startLoc: Position | undefined, noCalls: boolean | undefined, maybeAsyncArrow: boolean, optionalChained: boolean, forInit?: boolean | string) => Expression,
- * parseExprAtom: (refDestructuringErrors?: DestructuringErrorsShim | null, forInit?: boolean | string, forNew?: boolean) => Expression,
- * buildBinary: (startPos: number, startLoc: Position | undefined, left: Expression, right: Expression, op: string, logical: boolean) => Expression,
- * parseMaybeAssign: (forInit?: boolean | string, refDestructuringErrors?: DestructuringErrorsShim | null, afterLeftParse?: (this: unknown, left: Expression, startPos: number, startLoc?: Position) => Expression) => Expression,
- * _parseTrivialAtom: (forInit?: boolean | string) => Expression | null,
- * parseMaybeConditional: (forInit?: boolean | string, refDestructuringErrors?: DestructuringErrorsShim | null) => Expression,
- * parseMaybeUnary: (refDestructuringErrors: DestructuringErrorsShim | null, sawUnary: boolean, incDec: boolean, forInit?: boolean | string) => Expression,
- * parseExprSubscripts: (refDestructuringErrors?: DestructuringErrorsShim | null, forInit?: boolean | string) => Expression,
- * parseAwait: (forInit?: boolean | string) => Expression,
- * canAwait: boolean,
- * privateNameStack: unknown[],
- * semicolon: () => void,
- * exitScope: () => void,
- * parseStatement: (context: string | null, topLevel?: boolean, exports?: unknown) => Node,
- * parseBindingAtom: () => Node,
- * parseVarStatement: (node: Node, kind: string, allowMissingInitializer?: boolean) => Node,
- * parseVar: (node: Node, isFor: boolean, kind: string, allowMissingInitializer?: boolean) => Node,
- * parseExpressionStatement: (node: Node, expr: Expression) => Node,
- * parseParenExpression: () => Expression,
- * parseIfStatement: (node: Node) => Node,
- * parseReturnStatement: (node: Node) => Node,
- * insertSemicolon: () => boolean,
- * allowReturn: boolean,
- * allowNewDotTarget: boolean,
- * parseExprOps: (forInit?: boolean | string, refDestructuringErrors?: DestructuringErrorsShim | null) => Expression,
- * parseExprOp: (left: Expression, leftStartPos: number, leftStartLoc: Position | undefined, minPrec: number, forInit?: boolean | string) => Expression,
- * _deStack: DestructuringErrorsShim[],
- * _deDepth: number,
- * _ecmaVersion: number,
- * _noLocations: boolean,
- * _validRegexpFlags: string,
- * _propHashFastPath: boolean,
- * _propHashStack: { proto: boolean }[],
- * _propHashDepth: number,
- * _acquireDestructuringErrors: () => DestructuringErrorsShim,
- * _releaseDestructuringErrors: () => void,
- * _arrStack: EXPECTED_ANY[][],
- * _arrDepth: number,
- * _acquireScratch: () => EXPECTED_ANY[],
- * _releaseScratch: (scratch: EXPECTED_ANY[], count: number) => EXPECTED_ANY[],
- * parseRestBinding: () => Node,
- * parseBindingListItem: (param: Node) => Node,
- * parseAssignableListItem: (allowModifiers?: boolean) => Node,
- * parseParenItem: (item: Expression | Node) => Expression,
- * shouldParseArrow: (exprList: Expression[]) => boolean,
- * parseParenArrowList: (startPos: number, startLoc: Position | undefined, exprList: Expression[], forInit?: boolean | string) => Expression,
- * finishNodeAt: (node: Node, type: string, pos: number, loc?: Position) => Node,
- * parseParenAndDistinguishExpression: (canBeArrow: boolean, forInit?: boolean | string) => Expression,
- * parseSubscripts: (base: Expression, startPos: number, startLoc: Position | undefined, noCalls?: boolean, forInit?: boolean | string) => Expression,
- * parseNew: () => Expression,
- * parseTemplateElement: (opts: { isTagged: boolean }) => Node,
- * parseBlock: (createNewLexicalScope?: boolean, node?: Node, exitStrict?: boolean) => Node,
- * parseYield: (forInit?: boolean | string) => Expression,
- * toAssignable: (node: Node, isBinding?: boolean, refDestructuringErrors?: DestructuringErrorsShim | null) => Node,
- * checkLValPattern: (expr: Node, bindingType?: number, checkClashes?: unknown) => void,
- * checkUnreserved: (ref: Identifier) => void,
- * enterScope: (flags: number) => void,
- * readRegexp: () => void,
- * potentialArrowAt: number,
- * potentialArrowInForAwait: boolean,
- * overrideContext: (tokenCtx: unknown) => void,
- * parseFunction: (node: Node, statement: number, allowExpressionBody?: boolean, isAsync?: boolean, forInit?: boolean | string) => Expression,
- * parseArrowExpression: (node: Node, params: Node[], isAsync: boolean, forInit?: boolean | string) => Expression,
- * _subscriptFastPath: boolean,
- * checkLValSimple: (expr: Node, bindingType?: number, checkClashes?: Record<string, boolean> | null) => void,
- * checkLValInnerPattern: (expr: Node, bindingType?: number, checkClashes?: Record<string, boolean> | null) => void,
- * declareName: (name: string, bindingType: number, pos: number) => void,
- * startNode: () => Node,
- * startNodeAt: (pos: number, loc?: Position) => Node,
- * finishNode: (node: Node, type: string) => Node,
- * readWord1: () => string,
- * readWord: () => void,
- * readToken: (code: number) => void,
- * getTokenFromCode: (code: number) => void,
- * fullCharCodeAtPos: () => number,
- * skipSpace: () => void,
- * skipLineComment: (startSkip: number) => void,
- * skipBlockComment: () => void,
- * readString: (quote: number) => void,
- * readNumber: (startsWithDot: boolean) => void,
- * readRadixNumber: (radix: number) => void,
- * readTmplToken: () => void,
- * invalidStringToken: (position: number, message: string) => void,
- * _readInt: (radix: number, len?: number, maybeLegacyOctal?: boolean) => number | null,
- * _readCodePoint: () => number,
- * _readHexChar: (len: number) => number,
- * _readEscapedChar: (inTemplate: boolean) => string,
- * _readStringCold: (quote: number) => void,
- * _readTmplTokenCold: () => void,
- * _readNumberCold: (startsWithDot: boolean) => void,
- * _readRadixNumber: (radix: number) => void,
- * _readWord1Cold: () => string,
- * _finishWordSlow: (word: string) => void,
- * _skipSpaceCold: () => void,
- * _getUnknownOrPrivate: (code: number) => void,
- * finishToken: (type: TokenType, value?: unknown) => void,
- * context: TokContextShim[],
- * pos: number,
- * input: string,
- * scopeStack: Scope[],
- * currentScope: () => Scope,
- * currentThisScope: () => Scope,
- * currentVarScope: () => Scope,
- * keywords: RegExp,
- * reservedWords: RegExp,
- * reservedWordsStrict: RegExp,
- * reservedWordsStrictBind: RegExp,
- * strict: boolean,
- * inGenerator: boolean,
- * inGeneratorContext: () => boolean,
- * inAsync: boolean,
- * inClassStaticBlock: boolean,
- * _wordLookups: WordLookups,
- * treatFunctionsAsVar: boolean,
- * treatFunctionsAsVarInScope: (scope: Scope) => boolean,
- * inModule: boolean,
- * undefinedExports: Record<string, Node>,
- * parseObj: (isPattern: boolean, refDestructuringErrors?: DestructuringErrorsShim | null) => Node,
- * parseProperty: (isPattern: boolean, refDestructuringErrors?: DestructuringErrorsShim | null) => Node,
- * parsePropertyName: (prop: Node) => Node,
- * parsePropertyValue: (prop: Node, isPattern: boolean, isGenerator: boolean, isAsync: boolean, startPos: number | undefined, startLoc: Position | undefined, refDestructuringErrors: DestructuringErrorsShim | null | undefined, containsEsc: boolean) => void,
- * isAsyncProp: (prop: Node) => boolean,
- * checkPropClash: (prop: Node, propHash: Record<string, unknown>, refDestructuringErrors?: DestructuringErrorsShim | null) => void,
- * parseImport: (node: Node) => Node,
- * parseExport: (node: Node, exports: unknown) => Node,
- * parseImportSpecifiers: () => AnyImportSpecifier[],
- * parseImportAttribute: () => ImportAttribute,
- * parseExprImport: (forNew: boolean) => Expression,
- * parseImportMeta: (node: Node) => Expression,
- * parseDynamicImport: (node: Node) => Expression,
- * updateContext: (prevType: TokenType) => void,
- * finishOp: (type: TokenType, size: number) => void,
- * readToken_dot: () => void,
- * readToken_slash: () => void,
- * readToken_mult_modulo_exp: (code: number) => void,
- * readToken_pipe_amp: (code: number) => void,
- * readToken_caret: () => void,
- * readToken_plus_min: (code: number) => void,
- * readToken_lt_gt: (code: number) => void,
- * readToken_eq_excl: (code: number) => void,
- * readToken_question: () => void,
- * readToken_numberSign: () => void,
- * _tokenFastPath: boolean,
- * _inlineFinish: boolean,
- * _lazy: boolean,
- * _importPhase: ImportPhase | null,
- * _importPhasesEnabled: boolean,
- * _lazyComments: CollectedComment[] | undefined,
- * _newlineBefore: 0 | 1 | 2,
- * _fullTokenFastPath: boolean,
- * _stmtFastPath: boolean,
- * _lastArrow: Expression | null,
- * _arrowFastPath: boolean,
- * labels: { kind?: string | null, name?: string, statementStart?: number }[],
- * initFunction: (node: Node) => void,
- * parseFunctionStatement: (node: Node, isAsync: boolean, declarationPosition: boolean) => Node,
- * parseFunctionBody: (node: Node, isArrowFunction: boolean, isMethod: boolean, forInit?: boolean | string) => void,
- * parseFunctionParams: (node: Node) => void,
- * isSimpleParamList: (params: Node[]) => boolean,
- * checkParams: (node: Node, allowDuplicates: boolean) => void,
- * adaptDirectivePrologue: (statements: Node[]) => void,
- * toAssignableList: (exprList: Node[], isBinding: boolean) => Node[],
- * parseBindingList: (close: TokenType, allowEmpty: boolean, allowTrailingComma: boolean, allowModifiers?: boolean) => Node[],
- * strictDirective: (start: number) => boolean,
- * _funcFastPath: boolean,
- * _funcStmtOwn: boolean,
- * _parseFunctionAt: (start: number, statement: number, allowExpressionBody: boolean, isAsync: boolean, forInit?: boolean | string) => Expression,
- * isLet: (context?: string | null) => boolean,
- * parseLabeledStatement: (node: Node, maybeName: string, expr: Identifier, context: string | null) => Node,
- * _parseVarInto: (declarations: Node[], isFor: boolean, kind: string, allowMissingInitializer?: boolean) => number,
- * _parseVarStatementAt: (start: number, kind: string, allowMissingInitializer?: boolean) => Node,
- * _parseIfStatementAt: (start: number) => Node,
- * _parseReturnStatementAt: (start: number) => Node,
- * _parseExpressionStatementAt: (start: number, expr: Expression) => Node,
- * eatContextual: (name: string) => boolean,
- * isUsing: (isFor: boolean) => boolean,
- * isAwaitUsing: (isFor: boolean) => boolean,
- * parseForStatement: (node: Node) => Node,
- * parseFor: (node: Node, init: Node | null) => Node,
- * parseForIn: (node: Node, init: Node) => Node,
- * parseForAfterInit: (node: Node, init: Node, awaitAt: number) => Node,
- * parseWhileStatement: (node: Node) => Node,
- * parseSwitchStatement: (node: Node) => Node,
- * parseThrowStatement: (node: Node) => Node,
- * parseTryStatement: (node: Node) => Node,
- * parseBreakContinueStatement: (node: Node, keyword: string) => Node,
- * parseCatchClauseParam: () => Node,
- * _stmt2FastPath: boolean,
- * _exprFastPath: boolean,
- * _parseForStatementAt: (start: number) => Node,
- * _parseForAt: (start: number, init: Node | null) => Node,
- * _parseForInAt: (start: number, isAwait: boolean, init: Node) => Node,
- * _parseForAfterInitAt: (start: number, init: Node, awaitAt: number) => Node,
- * _parseWhileStatementAt: (start: number) => Node,
- * _parseSwitchStatementAt: (start: number) => Node,
- * _parseThrowStatementAt: (start: number) => Node,
- * _parseTryStatementAt: (start: number) => Node,
- * _parseBreakContinueStatementAt: (start: number, keyword: string) => Node,
- * _moduleFallback: boolean,
- * _moduleSyntaxSeen: boolean,
- * _tryModuleFallback: () => boolean,
- * }} ParserInternals
- */
- // internal methods are absent from acorn's types, so super calls do not
- // type-check; call through a typed view of the base prototype instead
- const base = /** @type {ParserInternals} */ (
- /** @type {unknown} */ (BaseParser.prototype)
- );
- /**
- * Acorn's internal destructuring-errors record; the class itself is not
- * exported. Owned methods must create records with the same hidden class the
- * rest of the expression parser reads, or every record field access there
- * turns polymorphic.
- * @typedef {{ shorthandAssign: number, trailingComma: number, parenthesizedAssign: number, parenthesizedBind: number, doubleProto: number }} DestructuringErrorsShim
- */
- // Capture the class at module load: parse one expression through a probe
- // whose `checkExpressionErrors` sees the record the base parser created.
- /** @type {{ new (): DestructuringErrorsShim } | null} */
- const DestructuringErrorsClass = (() => {
- /** @type {{ new (): DestructuringErrorsShim } | null} */
- let captured = null;
- class Probe extends BaseParser {
- /**
- * @param {DestructuringErrorsShim | null} refDestructuringErrors record to inspect
- * @param {boolean=} andThrow whether to throw on error
- * @returns {boolean} whether an error position was set
- */
- checkExpressionErrors(refDestructuringErrors, andThrow) {
- if (refDestructuringErrors) {
- captured =
- /** @type {{ new (): DestructuringErrorsShim }} */
- (refDestructuringErrors.constructor);
- }
- return /** @type {ParserInternals} */ (
- /** @type {unknown} */ (base)
- ).checkExpressionErrors.call(this, refDestructuringErrors, andThrow);
- }
- }
- Probe.parse("a", { ecmaVersion: 2020 });
- // cast: the closure assignment above is invisible to control-flow analysis
- return /** @type {{ new (): DestructuringErrorsShim } | null} */ (captured);
- })();
- /**
- * @returns {DestructuringErrorsShim} fresh destructuring-errors record on acorn's own class (plain-object fallback if the capture ever fails)
- */
- const createDestructuringErrors = () => {
- const DestructuringErrors = DestructuringErrorsClass;
- return DestructuringErrors !== null
- ? new DestructuringErrors()
- : {
- shorthandAssign: -1,
- trailingComma: -1,
- parenthesizedAssign: -1,
- parenthesizedBind: -1,
- doubleProto: -1
- };
- };
- /**
- * Reserved-word classification for `checkUnreserved`'s single lookup:
- * `1` keyword, `2` reserved in sloppy and strict mode, `3` reserved in strict
- * mode only.
- * @typedef {1 | 2 | 3} ReservedKind
- */
- /**
- * @typedef {object} WordLookups
- * @property {Map<string, TokenType>} keywords keyword name → token type
- * @property {Map<string, ReservedKind>} reservedKinds identifier name → reserved kind
- * @property {number} reservedMaxLen longest key in `reservedKinds`
- * @property {{ test: (name: string) => boolean }} reservedBindTest strict-mode binding check, a Set-backed stand-in for acorn's `reservedWordsStrictBind` regexp
- * @property {Set<string>} reservedBindSet strict-mode binding-reserved names
- * @property {number} reservedBindMinLen shortest key in `reservedBindSet`
- * @property {number} reservedBindMaxLen longest key in `reservedBindSet`
- * @property {number} id owner tag for the shared `WORD_TYPES` classification memo
- */
- // One entry per distinct keyword/reserved-word set; webpack parses with a
- // single option set, making this effectively a one-time build shared across
- // every parse.
- /** @type {Map<string, WordLookups>} */
- const wordLookupsCache = new Map();
- // Direct-mapped identifier cache for `readWord1`: one slot per hash, verified
- // by char compare, overwritten on collision. Shared across parses — hits are
- // content-checked, so a stale entry is merely a miss. Only words short enough
- // to be flat V8 strings (never slices retaining their whole source) are stored.
- const WORD_CACHE_MASK = 0x1fff;
- /** @type {(string | null)[]} */
- const WORD_CACHE = Array.from({ length: WORD_CACHE_MASK + 1 }, () => null);
- const WORD_CACHE_MAX_LEN = 12;
- // Classification memo parallel to `WORD_CACHE`, same slot: every cache write
- // also writes the word's token type plus the id of the keyword set that
- // classified it, so a hit skips `classifyWord` and another option set (or a
- // `readWord1` write, owner 0) invalidates instead of mis-classifying. Keying
- // by the cache's own slot adds no extra string references — the memo can
- // never retain a word the cache itself dropped.
- /** @type {(TokenType | null)[]} */
- const WORD_TYPES = Array.from({ length: WORD_CACHE_MASK + 1 }, () => null);
- const WORD_TYPE_OWNERS = new Int32Array(WORD_CACHE_MASK + 1);
- let nextWordLookupsId = 1;
- /**
- * Keyword-or-name classification: pure in (word, keywords), so it can be
- * memoized per `WORD_CACHE` slot.
- * @param {string} word word
- * @param {Map<string, TokenType>} keywords keyword name → token type
- * @returns {TokenType} token type for `word`
- */
- const classifyWord = (word, keywords) => {
- const len = word.length;
- // every acorn keyword is 2-10 lowercase ASCII chars (`do`…`instanceof`)
- if (len >= 2 && len <= 10) {
- const first = word.charCodeAt(0);
- if (first >= 97 && first <= 122) return keywords.get(word) || tokTypes.name;
- }
- return tokTypes.name;
- };
- // Multi-char operator strings for `finishOp` (`=>`, `===`, `&&=`, …), keyed by
- // their char codes packed 7 bits apart — collision-free for ASCII operators up
- // to acorn's maximum of 4 chars (`>>>=`), so the set stays ~40 entries.
- /** @type {Map<number, string>} */
- const OP_CACHE = new Map();
- // The regexp flag whitelist depends only on the ecmaVersion, so build one
- // string per version instead of a fresh one in every parser constructor.
- /** @type {Map<number, string>} */
- const VALID_REGEXP_FLAGS = new Map();
- /**
- * @param {number} ecmaVersion normalized acorn ecmaVersion
- * @returns {string} the regexp flags allowed at `ecmaVersion`
- */
- const getValidRegexpFlags = (ecmaVersion) => {
- let flags = VALID_REGEXP_FLAGS.get(ecmaVersion);
- if (flags === undefined) {
- flags = `gim${ecmaVersion >= 6 ? "uy" : ""}${ecmaVersion >= 9 ? "s" : ""}${
- ecmaVersion >= 13 ? "d" : ""
- }${ecmaVersion >= 15 ? "v" : ""}`;
- VALID_REGEXP_FLAGS.set(ecmaVersion, flags);
- }
- return flags;
- };
- // Sticky mirrors of acorn's `skipWhiteSpace` / string-literal / `lineBreak`
- // regexes for the owned `strictDirective` (they scan at an offset, no slice).
- const STRICT_SKIP_WS = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g;
- const STRICT_LITERAL = /(?:'((?:\\[^]|[^'\\])*?)'|"((?:\\[^]|[^"\\])*?)")/y;
- const STRICT_LINE_BREAK = /\r\n?|\n|\u2028|\u2029/;
- /**
- * @param {RegExp} re acorn `wordsRegexp` output (`^(?:a|b|c)$`)
- * @returns {Set<string>} the alternation's words
- */
- const wordsRegexpToSet = (re) => {
- const match = /^\^\(\?:(.*)\)\$$/.exec(re.source);
- const body = match ? match[1] : "";
- return new Set(body ? body.split("|") : []);
- };
- // One-entry identity memo in front of the string-keyed cache: acorn's
- // `wordsRegexp` interns its regexps, so identity captures the whole word set,
- // and builds construct thousands of parsers with one option set — this makes
- // the per-construction lookup three compares instead of a long key concat.
- /** @type {RegExp | undefined} */
- let lastKeywordsRe;
- /** @type {RegExp | undefined} */
- let lastReservedRe;
- /** @type {RegExp | undefined} */
- let lastReservedStrictRe;
- /** @type {WordLookups | undefined} */
- let lastWordLookups;
- /**
- * Mirrors acorn's `keywords` / `reservedWords` / `reservedWordsStrict` regexps
- * as Map/Set lookups. Membership is the hot per-word test in `readWord` and
- * `checkUnreserved`, and a hash lookup beats an anchored alternation regexp.
- * @param {ParserInternals} parser parser instance
- * @returns {WordLookups} lookups for this parser's keyword set
- */
- const getWordLookups = (parser) => {
- if (
- parser.keywords === lastKeywordsRe &&
- parser.reservedWords === lastReservedRe &&
- parser.reservedWordsStrict === lastReservedStrictRe
- ) {
- return /** @type {WordLookups} */ (lastWordLookups);
- }
- // module vs script share a keyword set but differ in reserved words, so the
- // key must cover all three regexps
- const key = `${parser.keywords.source}\n${parser.reservedWords.source}\n${parser.reservedWordsStrict.source}`;
- lastKeywordsRe = parser.keywords;
- lastReservedRe = parser.reservedWords;
- lastReservedStrictRe = parser.reservedWordsStrict;
- const cached = wordLookupsCache.get(key);
- if (cached !== undefined) {
- lastWordLookups = cached;
- return cached;
- }
- /** @type {Map<string, TokenType>} */
- const keywords = new Map();
- // acorn's keyword regexp is a subset of keywordTypes for the ecmaVersion
- for (const name of Object.keys(keywordTypes)) {
- if (parser.keywords.test(name)) keywords.set(name, keywordTypes[name]);
- }
- const reserved = wordsRegexpToSet(parser.reservedWords);
- /** @type {Map<string, ReservedKind>} */
- const reservedKinds = new Map();
- for (const name of reserved) reservedKinds.set(name, 2);
- for (const name of wordsRegexpToSet(parser.reservedWordsStrict)) {
- if (!reserved.has(name)) reservedKinds.set(name, 3);
- }
- // keyword classification wins, matching acorn's keyword-first check
- for (const name of keywords.keys()) reservedKinds.set(name, 1);
- const reservedBind = wordsRegexpToSet(parser.reservedWordsStrictBind);
- // length bounds so the owned checkLValSimple can skip the Set probe for
- // most identifiers
- let reservedBindMinLen = 0x7fffffff;
- let reservedBindMaxLen = 0;
- for (const name of reservedBind) {
- if (name.length < reservedBindMinLen) reservedBindMinLen = name.length;
- if (name.length > reservedBindMaxLen) reservedBindMaxLen = name.length;
- }
- let reservedMaxLen = 0;
- for (const name of reservedKinds.keys()) {
- if (name.length > reservedMaxLen) reservedMaxLen = name.length;
- }
- /** @type {WordLookups} */
- const lookups = {
- keywords,
- reservedKinds,
- reservedMaxLen,
- reservedBindTest: { test: (name) => reservedBind.has(name) },
- reservedBindSet: reservedBind,
- reservedBindMinLen,
- reservedBindMaxLen,
- id: nextWordLookupsId++
- };
- wordLookupsCache.set(key, lookups);
- lastWordLookups = lookups;
- return lookups;
- };
- /**
- * webpack's parser: acorn plus lazy `range` (no `loc` at all), Set-based scopes,
- * tokenizer fast paths, import attributes and import phases (with acorn's
- * `!forNew` guard, unlike the former `acorn-import-phases` package).
- */
- class WebpackParser extends BaseParser {
- /**
- * @param {Options & { lazyNodes?: boolean, lazyComments?: CollectedComment[], importPhases?: boolean, moduleFallback?: boolean }} options options
- * @param {string} input source code
- * @param {number=} startPos start position
- */
- constructor(options, input, startPos) {
- const lazy = options.lazyNodes === true;
- // JavascriptParser._parse pre-disables acorn's tracking, so the
- // defensive copy only runs for direct callers
- if (lazy && (options.locations || options.ranges)) {
- options = { ...options, locations: false, ranges: false };
- }
- super(options, input, startPos);
- // acorn sets this.keywords/reservedWords in its constructor; parsing
- // (and thus readWord) only starts later in parse(), so this is ready
- this._wordLookups = getWordLookups(
- /** @type {ParserInternals} */ (/** @type {unknown} */ (this))
- );
- // acorn only calls `.test()` on reservedWordsStrictBind (in
- // checkLValSimple); swap its regexp for the Set-backed check
- /** @type {{ reservedWordsStrictBind: { test: (name: string) => boolean } }} */
- (/** @type {unknown} */ (this)).reservedWordsStrictBind =
- this._wordLookups.reservedBindTest;
- // per-token option probes cached once: acorn normalizes options in
- // `getOptions` before the constructor body runs and never mutates them
- const normalizedOptions = /** @type {ParserInternals} */ (
- /** @type {unknown} */ (this)
- ).options;
- this._ecmaVersion = /** @type {number} */ (normalizedOptions.ecmaVersion);
- this._noLocations = !normalizedOptions.locations;
- // lazy mode: nodes get only offsets, gating the owned tokenizer and
- // statement fast paths
- this._lazy = lazy;
- // lazy comment collection must not race a user-provided onComment
- /** @type {CollectedComment[] | undefined} */
- this._lazyComments =
- lazy && !options.onComment ? options.lazyComments : undefined;
- // acorn skips a hashbang inside its constructor, before `_lazyComments`
- // above exists — reconstruct the comment the override missed
- if (
- this._lazyComments !== undefined &&
- !startPos &&
- this.options.allowHashBang &&
- input.startsWith("#!")
- ) {
- this._lazyComments.push(
- new LazyComment(
- false,
- 2,
- 0,
- /** @type {ParserInternals} */ (/** @type {unknown} */ (this)).pos,
- input
- )
- );
- }
- /** @type {ImportPhase | null} */
- this._importPhase = null;
- this._importPhasesEnabled = options.importPhases === true;
- // auto source type: parse as module first, downgrade to script in place
- // (instead of a second full parse) when script-only syntax is hit
- this._moduleFallback = options.moduleFallback === true;
- // set once a module-only construct is parsed; blocks the downgrade
- this._moduleSyntaxSeen = false;
- // the owned parseSubscript assumes optional chaining exists (it bakes
- // `optional` into the node shape), so gate it on the normalized version
- this._subscriptFastPath = lazy && this._ecmaVersion >= 11;
- const proto = WebpackParser.prototype;
- const self = /** @type {ParserInternals} */ (/** @type {unknown} */ (this));
- // the owned per-token loop (nextToken/finishToken/next) also serves the
- // public non-lazy tokenizer(): locations off, no onToken, and none of the
- // acorn tokenizer methods it inlines or skips overridden by a plugin
- // (`nextToken`'s punct/dot/eq shortcuts bypass `getTokenFromCode`,
- // `finishOp`, `readToken_dot` and `readToken_eq_excl` even when the full
- // token path below is off, so their overrides must gate this loop too)
- this._tokenFastPath =
- lazy ||
- (this._noLocations &&
- !normalizedOptions.onToken &&
- this.nextToken === proto.nextToken &&
- this.finishToken === proto.finishToken &&
- this.next === proto.next &&
- this.skipSpace === proto.skipSpace &&
- this.getTokenFromCode === proto.getTokenFromCode &&
- this.finishOp === proto.finishOp &&
- self.readToken === base.readToken &&
- self.updateContext === base.updateContext &&
- self.readToken_dot === base.readToken_dot &&
- self.readToken_eq_excl === base.readToken_eq_excl);
- // the owned getTokenFromCode bakes in every ES2021 operator (?., ??=,
- // &&=, ...), so it needs at least that version; outside lazy mode it also
- // bypasses acorn's readToken_* family, so any override there turns it off
- this._fullTokenFastPath =
- this._ecmaVersion >= 12 &&
- (lazy ||
- (this._tokenFastPath &&
- self.readToken_slash === base.readToken_slash &&
- self.readToken_mult_modulo_exp === base.readToken_mult_modulo_exp &&
- self.readToken_pipe_amp === base.readToken_pipe_amp &&
- self.readToken_caret === base.readToken_caret &&
- self.readToken_plus_min === base.readToken_plus_min &&
- self.readToken_lt_gt === base.readToken_lt_gt &&
- self.readToken_question === base.readToken_question &&
- self.readToken_numberSign === base.readToken_numberSign &&
- self.readRadixNumber === base.readRadixNumber));
- // the owned nextToken/readWord finish the commonest tokens in place,
- // which skips this.finishToken: needs the fast token loop (readWord runs
- // in every mode) and no finishToken override by a plugin
- this._inlineFinish =
- this._tokenFastPath &&
- this.finishToken === WebpackParser.prototype.finishToken;
- // whether the gap before the current token holds a line terminator:
- // 0 no, 1 yes, 2 unknown (canInsertSemicolon then scans the gap)
- /** @type {0 | 1 | 2} */
- this._newlineBefore = 2;
- // LIFO pool for call-scoped destructuring-errors records; depth resets
- // implicitly since a raise aborts the whole parse
- /** @type {DestructuringErrorsShim[]} */
- this._deStack = [];
- this._deDepth = 0;
- // LIFO pool for `parseObj`'s prop-clash records: acorn's ES6+
- // `checkPropClash` only ever touches `.proto`, so one record per nesting
- // depth suffices; an overriding subclass gets the fresh `{}` acorn expects
- this._propHashFastPath =
- /** @type {ParserInternals} */ (/** @type {unknown} */ (this))
- .checkPropClash === base.checkPropClash;
- /** @type {{ proto: boolean }[]} */
- this._propHashStack = [];
- this._propHashDepth = 0;
- // LIFO pool of scratch arrays for list productions: elements are written
- // by index, then copied out exactly sized — a push-grown array retains
- // ~17 slots of capacity slack per list otherwise
- /** @type {unknown[][]} */
- this._arrStack = [];
- this._arrDepth = 0;
- // `readRegexp`'s flag whitelist depends only on the ecmaVersion
- this._validRegexpFlags = getValidRegexpFlags(this._ecmaVersion);
- // the owned parseStatement inlines these methods, so a parser plugin
- // overriding any of them turns the statement fast path off
- this._stmtFastPath =
- lazy &&
- this.parseVarStatement === proto.parseVarStatement &&
- this.parseVar === proto.parseVar &&
- this.parseIfStatement === proto.parseIfStatement &&
- this.parseReturnStatement === proto.parseReturnStatement &&
- this.parseExpressionStatement === proto.parseExpressionStatement;
- // last arrow finished by parseArrowExpression: `expr === this._lastArrow`
- // replaces the megamorphic `expr.type === "ArrowFunctionExpression"`
- // probes on the expression spine (arrows are created in exactly one place
- // and never backtracked, so identity captures the type test)
- /** @type {Expression | null} */
- this._lastArrow = null;
- // arrows must flow through parseArrowExpression for the identity probe;
- // a plugin overriding it falls back to the type-based probes
- this._arrowFastPath =
- lazy && this.parseArrowExpression === proto.parseArrowExpression;
- // the owned parseFunction/parseFunctionBody inline initFunction and
- // isSimpleParamList; a plugin overriding either turns the fast path off
- const internals = /** @type {ParserInternals} */ (
- /** @type {unknown} */ (this)
- );
- this._funcFastPath =
- lazy &&
- this._ecmaVersion >= 9 &&
- internals.initFunction === base.initFunction &&
- internals.isSimpleParamList === base.isSimpleParamList;
- // the owned parseStatement inlines parseFunctionStatement's body
- this._funcStmtOwn =
- this._funcFastPath &&
- internals.parseFunctionStatement === base.parseFunctionStatement &&
- this.parseFunction === proto.parseFunction;
- // the owned parseStatement inlines these statement parsers too; any
- // override falls back to acorn's dispatch for these heads
- this._stmt2FastPath =
- lazy &&
- this._ecmaVersion >= 9 &&
- internals.parseForStatement === base.parseForStatement &&
- internals.parseFor === base.parseFor &&
- internals.parseForIn === base.parseForIn &&
- internals.parseForAfterInit === base.parseForAfterInit &&
- this.parseVar === proto.parseVar &&
- internals.parseWhileStatement === base.parseWhileStatement &&
- internals.parseSwitchStatement === base.parseSwitchStatement &&
- internals.parseThrowStatement === base.parseThrowStatement &&
- internals.parseTryStatement === base.parseTryStatement &&
- internals.parseBreakContinueStatement ===
- base.parseBreakContinueStatement;
- // `parseMaybeAssign`'s trivial-atom fast path returns the atom without
- // descending the seven-layer expression chain, so every layer it skips
- // must be the owned one
- this._exprFastPath =
- lazy &&
- this.parseMaybeConditional === proto.parseMaybeConditional &&
- this.parseExprOps === proto.parseExprOps &&
- this.parseExprOp === proto.parseExprOp &&
- this.parseMaybeUnary === proto.parseMaybeUnary &&
- this.parseExprSubscripts === proto.parseExprSubscripts &&
- this.parseSubscripts === proto.parseSubscripts &&
- this.parseSubscript === proto.parseSubscript &&
- this.parseExprAtom === proto.parseExprAtom &&
- this.checkExpressionErrors === proto.checkExpressionErrors &&
- internals.isContextual === base.isContextual;
- }
- /**
- * Fetches a destructuring-errors record from the pool: acorn allocates one
- * per expression parse and drops it at the end of the call, so strictly
- * call-scoped users can reuse records instead. Pair every acquire with a
- * `_releaseDestructuringErrors` on each non-throwing exit.
- * @returns {DestructuringErrorsShim} reset record
- * @this {ParserInternals}
- */
- _acquireDestructuringErrors() {
- const stack = this._deStack;
- const depth = this._deDepth++;
- const cached = stack[depth];
- if (cached !== undefined) {
- cached.shorthandAssign =
- cached.trailingComma =
- cached.parenthesizedAssign =
- cached.parenthesizedBind =
- cached.doubleProto =
- -1;
- return cached;
- }
- return (stack[depth] = createDestructuringErrors());
- }
- /**
- * @returns {void}
- * @this {ParserInternals}
- */
- _releaseDestructuringErrors() {
- this._deDepth--;
- }
- /**
- * Fetches a scratch array for a list production: write elements by index,
- * then materialize with `_releaseScratch`. Entries past the caller's write
- * index are stale and meaningless.
- * @returns {EXPECTED_ANY[]} scratch array
- * @this {ParserInternals}
- */
- _acquireScratch() {
- const stack = this._arrStack;
- const depth = this._arrDepth++;
- const cached = stack[depth];
- if (cached !== undefined) return cached;
- return (stack[depth] = []);
- }
- /**
- * @param {EXPECTED_ANY[]} scratch scratch array from `_acquireScratch`
- * @param {number} count number of elements written
- * @returns {EXPECTED_ANY[]} exactly-sized copy of the first `count` entries
- * @this {ParserInternals}
- */
- _releaseScratch(scratch, count) {
- this._arrDepth--;
- return scratch.slice(0, count);
- }
- // ----- tokenizer fast paths -----
- /**
- * Owned per-token loop: acorn's `nextToken` chains `skipSpace` →
- * `fullCharCodeAtPos` → `readToken` → `isIdentifierStart` with a dead
- * `locations` check at each step. For the common non-template context of
- * `_tokenFastPath` mode (lazy, or locations-off with no tokenizer overrides)
- * this folds whitespace and comment skipping and the ASCII token dispatch
- * into one function so nothing re-enters acorn's per-step option checks.
- * Template/`preserveSpace` contexts and other modes use acorn's tokenizer.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @returns {void}
- * @this {ParserInternals}
- */
- nextToken() {
- const context = this.context;
- const curContext = context[context.length - 1];
- let slow;
- if (curContext !== undefined) {
- const flagged =
- /** @type {TokContextShim & { [kSlowContext]?: boolean }} */
- (curContext);
- slow = flagged[kSlowContext];
- if (slow === undefined) {
- slow = flagged[kSlowContext] = Boolean(
- curContext.preserveSpace || curContext.override
- );
- }
- }
- if (!this._tokenFastPath || curContext === undefined || slow) {
- this._newlineBefore = 2;
- return base.nextToken.call(this);
- }
- const input = this.input;
- const len = input.length;
- let pos = this.pos;
- // line terminators are flagged while skipping (yuku's
- // `line_terminator_before`), so ASI checks need no gap re-scan. A
- // delegated path (acorn's html-comment handling) may have consumed part
- // of the gap before re-entering — start at "unknown" then.
- /** @type {0 | 1 | 2} */
- let newline = pos === this.lastTokEnd ? 0 : 2;
- // one CHAR_CLASS load classifies each char for both the skip loop and
- // the token dispatch below (yuku's ws_class/ident/punct tables in one)
- let code = 0;
- let cls = CLS_OTHER;
- while (pos < len) {
- code = input.charCodeAt(pos);
- cls = CHAR_CLASS[code];
- if (cls < CLS_SPACE) {
- if (cls === CLS_UNICODE) {
- // unicode whitespace / line terminators: the cold reader consumes them
- this.pos = pos;
- this._skipSpaceCold();
- pos = this.pos;
- if (newline === 0) newline = 2;
- code = pos < len ? input.charCodeAt(pos) : 0;
- cls = CHAR_CLASS[code];
- }
- break;
- }
- if (cls === CLS_SPACE) {
- // space, tab, VT, FF (no CRLF/line bookkeeping in lazy mode)
- pos++;
- } else if (cls === CLS_NEWLINE) {
- newline = 1;
- pos++;
- } else {
- const next = input.charCodeAt(pos + 1);
- if (next === 42) {
- this.pos = pos;
- this.skipBlockComment();
- pos = this.pos;
- // the comment body may hold a line terminator
- if (newline === 0) newline = 2;
- } else if (next === 47) {
- this.pos = pos;
- this.skipLineComment(2);
- pos = this.pos;
- } else {
- // a division/regexp token, not a comment
- cls = CLS_OTHER;
- break;
- }
- }
- }
- this._newlineBefore = newline;
- this.pos = pos;
- this.start = pos;
- if (pos >= len) return this.finishToken(tokTypes.eof);
- switch (cls) {
- case CLS_IDENT:
- return this.readWord();
- case CLS_PUNCT: {
- const type = /** @type {TokenType} */ (SIMPLE_PUNCT[code]);
- this.pos = pos + 1;
- if (!this._inlineFinish) return this.finishToken(type);
- // finishToken inlined for the simple punctuators (no value, and
- // their context updates are per-char static)
- this.end = pos + 1;
- const prevType = this.type;
- this.type = type;
- this.value = undefined;
- switch (code) {
- case 41:
- case 125: {
- // parenR/braceR.updateContext, inlined
- if (context.length === 1) {
- this.exprAllowed = true;
- break;
- }
- let out = /** @type {TokContextShim} */ (context.pop());
- if (
- out === CTX_B_STAT &&
- /** @type {TokContextShim} */ (context[context.length - 1])
- .token === "function"
- ) {
- out = /** @type {TokContextShim} */ (context.pop());
- }
- this.exprAllowed = !out.isExpr;
- break;
- }
- case 123:
- // braceL.updateContext, inlined
- context.push(this.braceIsBlock(prevType) ? CTX_B_STAT : CTX_B_EXPR);
- this.exprAllowed = true;
- break;
- case 40: {
- // parenL.updateContext, inlined
- const statementParens =
- prevType === tokTypes._if ||
- prevType === tokTypes._for ||
- prevType === tokTypes._with ||
- prevType === tokTypes._while;
- context.push(statementParens ? CTX_P_STAT : CTX_P_EXPR);
- this.exprAllowed = true;
- break;
- }
- case 58:
- // colon.updateContext, inlined
- if (
- /** @type {TokContextShim} */ (context[context.length - 1])
- .token === "function"
- ) {
- context.pop();
- }
- this.exprAllowed = true;
- break;
- case 93:
- // bracketR: no context hook, beforeExpr is false
- this.exprAllowed = false;
- break;
- default:
- // semi, comma, bracketL: no context hook, beforeExpr is true
- this.exprAllowed = true;
- }
- return;
- }
- case CLS_DOT: {
- // `.` not starting `.5` or `...`: skip readToken_dot's re-dispatch
- const next = input.charCodeAt(pos + 1);
- if ((next < 48 || next > 57) && next !== 46) {
- this.pos = pos + 1;
- return this.finishToken(tokTypes.dot);
- }
- return this.getTokenFromCode(code);
- }
- case CLS_EQ: {
- // `=` not starting `==` or `=>`: skip readToken_eq_excl + finishOp slice
- const next = input.charCodeAt(pos + 1);
- if (next !== 61 && next !== 62) {
- this.pos = pos + 1;
- return this.finishToken(tokTypes.eq, "=");
- }
- return this.getTokenFromCode(code);
- }
- case CLS_UNICODE:
- return this.readToken(this.fullCharCodeAtPos());
- default:
- return this.getTokenFromCode(code);
- }
- }
- /**
- * `_tokenFastPath` `finishToken`: acorn probes `options.locations` for a
- * dead `endLoc` write on every token and reaches `updateContext` through an
- * extra method call. Skip the probe and inline acorn's `updateContext` body —
- * this runs once per token. Other modes use acorn's.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @param {TokenType} type token type
- * @param {unknown=} value token value
- * @returns {void}
- * @this {ParserInternals}
- */
- finishToken(type, value) {
- if (!this._tokenFastPath) {
- return base.finishToken.call(this, type, value);
- }
- this.end = this.pos;
- const prevType = this.type;
- this.type = type;
- this.value = value;
- const internal = /** @type {TokenTypeInternal} */ (type);
- // acorn's updateContext, inlined: keyword-after-dot forbids an expression,
- // else the token type's own context hook runs, else `exprAllowed` follows
- // the type's `beforeExpr` (the branch that makes `/` after a value divide)
- if (type === tokTypes.name) {
- // name.updateContext, inlined for the commonest token: only `of` /
- // `yield` (outside a `.` access, ES6+) can re-allow an expression
- this.exprAllowed =
- ((value === "of" && !this.exprAllowed) ||
- (value === "yield" && this.inGeneratorContext())) &&
- prevType !== tokTypes.dot &&
- this._ecmaVersion >= 6;
- } else {
- const update = internal.updateContext;
- if (update === null) {
- // no context hook (most punctuation, operators, literals and
- // keywords): acorn's keyword-after-dot probe, else `beforeExpr` —
- // checked first so this majority skips the per-type compares below
- this.exprAllowed =
- prevType === tokTypes.dot && internal.keyword !== undefined
- ? false
- : internal.beforeExpr;
- } else if (type === tokTypes.parenR || type === tokTypes.braceR) {
- // parenR/braceR.updateContext, inlined
- const context = this.context;
- if (context.length === 1) {
- this.exprAllowed = true;
- } else {
- let out = /** @type {TokContextShim} */ (context.pop());
- if (
- out === CTX_B_STAT &&
- /** @type {TokContextShim} */ (context[context.length - 1])
- .token === "function"
- ) {
- out = /** @type {TokContextShim} */ (context.pop());
- }
- this.exprAllowed = !out.isExpr;
- }
- } else if (type === tokTypes.braceL) {
- // braceL.updateContext, inlined
- this.context.push(
- this.braceIsBlock(prevType) ? CTX_B_STAT : CTX_B_EXPR
- );
- this.exprAllowed = true;
- } else if (type === tokTypes.parenL) {
- // parenL.updateContext, inlined
- const statementParens =
- prevType === tokTypes._if ||
- prevType === tokTypes._for ||
- prevType === tokTypes._with ||
- prevType === tokTypes._while;
- this.context.push(statementParens ? CTX_P_STAT : CTX_P_EXPR);
- this.exprAllowed = true;
- } else if (internal.keyword !== undefined && prevType === tokTypes.dot) {
- // `.function` etc.: keyword-after-dot wins over the type's own hook
- this.exprAllowed = false;
- } else {
- update.call(this, prevType);
- }
- }
- }
- /**
- * Owned `braceIsBlock`, acorn's verbatim except the line-terminator probe:
- * acorn slices the inter-token gap and runs a regexp; `_gapHasNewline`
- * answers from the tokenizer's newline flag (scanning only when unknown).
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokencontext.js
- * @param {TokenType} prevType type of the previous token
- * @returns {boolean} whether a `{` opens a block in this context
- * @this {ParserInternals}
- */
- braceIsBlock(prevType) {
- const context = this.context;
- const parent = /** @type {TokContextShim} */ (context[context.length - 1]);
- if (parent === CTX_F_EXPR || parent === CTX_F_STAT) return true;
- if (
- prevType === tokTypes.colon &&
- (parent === CTX_B_STAT || parent === CTX_B_EXPR)
- ) {
- return !parent.isExpr;
- }
- // after `return`, or after `yield`/`of` (name with exprAllowed), a line
- // terminator decides between block and expression
- if (
- prevType === tokTypes._return ||
- (prevType === tokTypes.name && this.exprAllowed)
- ) {
- return this._gapHasNewline();
- }
- if (
- prevType === tokTypes._else ||
- prevType === tokTypes.semi ||
- prevType === tokTypes.eof ||
- prevType === tokTypes.parenR ||
- prevType === tokTypes.arrow
- ) {
- return true;
- }
- if (prevType === tokTypes.braceL) return parent === CTX_B_STAT;
- if (
- prevType === tokTypes._var ||
- prevType === tokTypes._const ||
- prevType === tokTypes.name
- ) {
- return false;
- }
- return !this.exprAllowed;
- }
- /**
- * Whether the gap before the current token holds a line terminator, served
- * from the owned tokenizer's flag when known.
- * @returns {boolean} whether a line terminator precedes the current token
- * @this {ParserInternals}
- */
- _gapHasNewline() {
- const newlineBefore = this._newlineBefore;
- if (newlineBefore !== 2) return newlineBefore === 1;
- const input = this.input;
- const end = this.start;
- for (let i = this.lastTokEnd; i < end; i++) {
- const ch = input.charCodeAt(i);
- // LF, CR, LS, PS — acorn's `lineBreak` alternation
- if (ch === 10 || ch === 13 || ch === 0x2028 || ch === 0x2029) {
- // memoize: the gap is fixed until the next token is read, and
- // `nextToken` rewrites the flag — ASI probes often repeat per token
- // (e.g. name atoms behind a /*#__PURE__*/ comment)
- this._newlineBefore = 1;
- return true;
- }
- }
- this._newlineBefore = 0;
- return false;
- }
- /**
- * Owned per-token advance: acorn's `next` writes `lastTokEndLoc`/
- * `lastTokStartLoc` and probes `options.onToken` on every token, both dead in
- * `_tokenFastPath` mode (locations off, no token stream), leaving only the
- * two offset writes and the keyword-escape guard. Other modes use acorn's.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @param {boolean=} ignoreEscapeSequenceInKeyword whether an escape in a keyword is allowed here
- * @returns {void}
- * @this {ParserInternals}
- */
- next(ignoreEscapeSequenceInKeyword) {
- if (!this._tokenFastPath) {
- return base.next.call(this, ignoreEscapeSequenceInKeyword);
- }
- const type = this.type;
- // `containsEsc` is a parser field and almost always false; testing it first
- // keeps the TokenType load off the common path.
- if (this.containsEsc && !ignoreEscapeSequenceInKeyword && type.keyword) {
- this.raiseRecoverable(
- this.start,
- `Escape sequence in keyword ${type.keyword}`
- );
- }
- this.lastTokEnd = this.end;
- this.lastTokStart = this.start;
- this.nextToken();
- }
- /**
- * Owned `finishOp`: acorn slices the operator text out of the source for
- * every operator token, allocating a fresh 2-4 char string per `=>`, `===`,
- * `&&` etc. Serve those from `OP_CACHE` instead; single-char operators keep
- * the direct slice, which V8 serves from its single-character table.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @param {TokenType} type token type
- * @param {number} size operator length
- * @returns {void}
- * @this {ParserInternals}
- */
- finishOp(type, size) {
- const pos = this.pos;
- const input = this.input;
- if (size === 1) {
- this.pos = pos + 1;
- return this.finishToken(type, input.slice(pos, pos + 1));
- }
- let key = input.charCodeAt(pos) | (input.charCodeAt(pos + 1) << 7);
- if (size > 2) {
- key |= input.charCodeAt(pos + 2) << 14;
- if (size > 3) key |= input.charCodeAt(pos + 3) << 21;
- }
- let str = OP_CACHE.get(key);
- if (str === undefined) {
- str = input.slice(pos, pos + size);
- OP_CACHE.set(key, str);
- }
- this.pos = pos + size;
- return this.finishToken(type, str);
- }
- /**
- * Owned `getTokenFromCode`: acorn dispatches operators through per-family
- * `readToken_*` methods that each end in `finishOp`'s source slice. Resolve
- * every operator by direct char peeks to a static string instead (yuku's
- * `scanPunctuation`) — no method chain, no slice, no `OP_CACHE` probe. The
- * HTML-comment forms (`<!--`, `-->`) inline acorn's line-comment handling;
- * `#` and unknown chars use the owned cold reader.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @param {number} code current char code
- * @returns {void}
- * @this {ParserInternals}
- */
- getTokenFromCode(code) {
- if (!this._fullTokenFastPath) {
- return base.getTokenFromCode.call(this, code);
- }
- const input = this.input;
- const pos = this.pos;
- switch (code) {
- case 46: {
- // '.': number, ellipsis or plain dot
- const next = input.charCodeAt(pos + 1);
- if (next >= 48 && next <= 57) return this.readNumber(true);
- if (next === 46 && input.charCodeAt(pos + 2) === 46) {
- this.pos = pos + 3;
- return this.finishToken(tokTypes.ellipsis);
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.dot);
- }
- case 47: {
- // '/': regexp in expression position, otherwise /= or /
- if (this.exprAllowed) {
- this.pos = pos + 1;
- return this.readRegexp();
- }
- if (input.charCodeAt(pos + 1) === 61) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.assign, "/=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.slash, "/");
- }
- case 37: {
- // '%': %= or %
- if (input.charCodeAt(pos + 1) === 61) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.assign, "%=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.modulo, "%");
- }
- case 42: {
- // '*': **=, **, *= or *
- const next = input.charCodeAt(pos + 1);
- if (next === 42) {
- if (input.charCodeAt(pos + 2) === 61) {
- this.pos = pos + 3;
- return this.finishToken(tokTypes.assign, "**=");
- }
- this.pos = pos + 2;
- return this.finishToken(tokTypes.starstar, "**");
- }
- if (next === 61) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.assign, "*=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.star, "*");
- }
- case 124: {
- // '|': ||=, ||, |= or |
- const next = input.charCodeAt(pos + 1);
- if (next === 124) {
- if (input.charCodeAt(pos + 2) === 61) {
- this.pos = pos + 3;
- return this.finishToken(tokTypes.assign, "||=");
- }
- this.pos = pos + 2;
- return this.finishToken(tokTypes.logicalOR, "||");
- }
- if (next === 61) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.assign, "|=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.bitwiseOR, "|");
- }
- case 38: {
- // '&': &&=, &&, &= or &
- const next = input.charCodeAt(pos + 1);
- if (next === 38) {
- if (input.charCodeAt(pos + 2) === 61) {
- this.pos = pos + 3;
- return this.finishToken(tokTypes.assign, "&&=");
- }
- this.pos = pos + 2;
- return this.finishToken(tokTypes.logicalAND, "&&");
- }
- if (next === 61) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.assign, "&=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.bitwiseAND, "&");
- }
- case 94: {
- // '^': ^= or ^
- if (input.charCodeAt(pos + 1) === 61) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.assign, "^=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.bitwiseXOR, "^");
- }
- case 43: {
- // '+': ++, += or +
- const next = input.charCodeAt(pos + 1);
- if (next === 43) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.incDec, "++");
- }
- if (next === 61) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.assign, "+=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.plusMin, "+");
- }
- case 45: {
- // '-': --, -= or -; `-->` may open an HTML line comment
- const next = input.charCodeAt(pos + 1);
- if (next === 45) {
- // `-->` opens an HTML line comment only at the start of a line
- // (acorn readToken_plus_min): https://github.com/acornjs/acorn/blob/8.15.0/acorn/src/tokenize.js#L599-L618
- if (
- input.charCodeAt(pos + 2) === 62 &&
- !this.inModule &&
- (this.lastTokEnd === 0 ||
- lineBreak.test(input.slice(this.lastTokEnd, pos)))
- ) {
- this.skipLineComment(3);
- this.skipSpace();
- return this.nextToken();
- }
- this.pos = pos + 2;
- return this.finishToken(tokTypes.incDec, "--");
- }
- if (next === 61) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.assign, "-=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.plusMin, "-");
- }
- case 60: {
- // '<': <<=, <<, <= or <; `<!--` opens an HTML line comment
- const next = input.charCodeAt(pos + 1);
- if (next === 60) {
- if (input.charCodeAt(pos + 2) === 61) {
- this.pos = pos + 3;
- return this.finishToken(tokTypes.assign, "<<=");
- }
- this.pos = pos + 2;
- return this.finishToken(tokTypes.bitShift, "<<");
- }
- if (
- next === 33 &&
- !this.inModule &&
- input.charCodeAt(pos + 2) === 45 &&
- input.charCodeAt(pos + 3) === 45
- ) {
- // `<!--` opens an HTML line comment (acorn readToken_lt_gt):
- // https://github.com/acornjs/acorn/blob/8.15.0/acorn/src/tokenize.js#L620-L649
- this.skipLineComment(4);
- this.skipSpace();
- return this.nextToken();
- }
- if (next === 61) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.relational, "<=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.relational, "<");
- }
- case 62: {
- // '>': >>>=, >>>, >>=, >>, >= or >
- const next = input.charCodeAt(pos + 1);
- if (next === 62) {
- if (input.charCodeAt(pos + 2) === 62) {
- if (input.charCodeAt(pos + 3) === 61) {
- this.pos = pos + 4;
- return this.finishToken(tokTypes.assign, ">>>=");
- }
- this.pos = pos + 3;
- return this.finishToken(tokTypes.bitShift, ">>>");
- }
- if (input.charCodeAt(pos + 2) === 61) {
- this.pos = pos + 3;
- return this.finishToken(tokTypes.assign, ">>=");
- }
- this.pos = pos + 2;
- return this.finishToken(tokTypes.bitShift, ">>");
- }
- if (next === 61) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.relational, ">=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.relational, ">");
- }
- case 61: {
- // '=': ===, ==, => or =
- const next = input.charCodeAt(pos + 1);
- if (next === 61) {
- if (input.charCodeAt(pos + 2) === 61) {
- this.pos = pos + 3;
- return this.finishToken(tokTypes.equality, "===");
- }
- this.pos = pos + 2;
- return this.finishToken(tokTypes.equality, "==");
- }
- if (next === 62) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.arrow);
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.eq, "=");
- }
- case 33: {
- // '!': !==, != or !
- const next = input.charCodeAt(pos + 1);
- if (next === 61) {
- if (input.charCodeAt(pos + 2) === 61) {
- this.pos = pos + 3;
- return this.finishToken(tokTypes.equality, "!==");
- }
- this.pos = pos + 2;
- return this.finishToken(tokTypes.equality, "!=");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.prefix, "!");
- }
- case 63: {
- // '?': ?. (not before a digit), ??=, ?? or ?
- const next = input.charCodeAt(pos + 1);
- if (next === 46) {
- const next2 = input.charCodeAt(pos + 2);
- if (next2 < 48 || next2 > 57) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.questionDot, "?.");
- }
- }
- if (next === 63) {
- if (input.charCodeAt(pos + 2) === 61) {
- this.pos = pos + 3;
- return this.finishToken(tokTypes.assign, "??=");
- }
- this.pos = pos + 2;
- return this.finishToken(tokTypes.coalesce, "??");
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.question, "?");
- }
- case 126: {
- // '~'
- this.pos = pos + 1;
- return this.finishToken(tokTypes.prefix, "~");
- }
- case 96: {
- // '`'
- this.pos = pos + 1;
- return this.finishToken(tokTypes.backQuote);
- }
- case 48: {
- // '0': radix literals split off before the decimal reader
- const next = input.charCodeAt(pos + 1);
- if (next === 120 || next === 88) return this._readRadixNumber(16);
- if (next === 111 || next === 79) return this._readRadixNumber(8);
- if (next === 98 || next === 66) return this._readRadixNumber(2);
- return this.readNumber(false);
- }
- case 49:
- case 50:
- case 51:
- case 52:
- case 53:
- case 54:
- case 55:
- case 56:
- case 57:
- return this.readNumber(false);
- case 34:
- case 39:
- return this.readString(code);
- default:
- return this._getUnknownOrPrivate(code);
- }
- }
- /**
- * ASCII fast path for acorn's `readWord1`, which pays a surrogate-aware
- * method call and a range-check helper per character. Escapes, non-ASCII
- * and astral input restart the owned cold reader from the word start.
- * Words are deduplicated through `WORD_CACHE` so repeated identifiers —
- * which dominate real code — reuse one string instead of slicing a fresh
- * one per occurrence; sharing also keeps their cached string hashes warm
- * for the keyword/scope Map lookups downstream.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @returns {string} the word
- */
- readWord1() {
- const input = this.input;
- const start = this.pos;
- const len = input.length;
- // djb2-style hash folded into the scan loop (yuku scans each lexeme in
- // one pass); computing it for the rare over-long words is cheaper than
- // re-walking every word in a second pass
- let hash = 0;
- let pos = start;
- while (pos < len) {
- const ch = input.charCodeAt(pos);
- if (ch < 128) {
- if (IDENT_CHAR[ch] === 0) {
- // backslash escape: restart cold so escape rules see the word
- if (ch === 92) return this._readWord1Cold();
- break;
- }
- hash = (Math.imul(hash, 33) + ch) | 0;
- pos++;
- } else {
- return this._readWord1Cold();
- }
- }
- this.containsEsc = false;
- this.pos = pos;
- const wordLen = pos - start;
- // Single-char words skip the cache (V8 serves those slices from its
- // single-character table without allocating); long words skip it too.
- if (wordLen >= 2 && wordLen <= WORD_CACHE_MAX_LEN) {
- const slot = hash & WORD_CACHE_MASK;
- const cached = WORD_CACHE[slot];
- if (
- cached !== null &&
- cached.length === wordLen &&
- // length already matched, so a prefix test is an exact compare —
- // one builtin instead of a charCodeAt loop per cached character
- input.startsWith(cached, start)
- ) {
- return cached;
- }
- const word = input.slice(start, pos);
- WORD_CACHE[slot] = word;
- WORD_TYPE_OWNERS[slot] = 0;
- return word;
- }
- return input.slice(start, pos);
- }
- /**
- * String fast path: one scan finds the closing quote and cooks the common
- * single-char escapes (`\n \t \\ \" \'` …) and line continuations inline.
- * Hex/unicode/octal escapes (`\x`, `\u`, `\0`-`\9`) restart the owned cold
- * reader, which owns their readers and strict-mode errors; old ecmaVersions
- * (LS/PS terminate strings there) and location tracking use acorn's own.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @param {number} quote quote char code
- * @returns {void}
- */
- readString(quote) {
- if (!this._noLocations || this._ecmaVersion < 10) {
- return base.readString.call(this, quote);
- }
- const input = this.input;
- const len = input.length;
- const quotePos = this.pos;
- const start = quotePos + 1;
- let pos = start;
- // spans between escapes accumulate here; a plain string never touches
- // `out` and slices once at the end
- let out = "";
- let chunkStart = start;
- for (;;) {
- if (pos >= len) this.raise(this.start, "Unterminated string constant");
- const ch = input.charCodeAt(pos);
- if (ch === quote) break;
- if (ch === 92) {
- const esc = input.charCodeAt(pos + 1);
- // hex/unicode/octal escapes (and a trailing `\`) need the cold
- // reader's escape handling; restart from the quote (this.pos unmoved)
- if (
- pos + 1 >= len ||
- esc === 120 ||
- esc === 117 ||
- (esc >= 48 && esc <= 57)
- ) {
- this.pos = quotePos;
- return this._readStringCold(quote);
- }
- let rep;
- let advance = 2;
- switch (esc) {
- case 110:
- rep = "\n";
- break;
- case 114:
- rep = "\r";
- break;
- case 116:
- rep = "\t";
- break;
- case 98:
- rep = "\b";
- break;
- case 102:
- rep = "\f";
- break;
- case 118:
- rep = "\u000B";
- break;
- case 13:
- // `\` + CR(LF): line continuation cooked away
- rep = "";
- if (input.charCodeAt(pos + 2) === 10) advance = 3;
- break;
- case 10:
- case 0x2028:
- case 0x2029:
- rep = "";
- break;
- default:
- // any other escape is the escaped code unit verbatim (`\\`, `\q`)
- rep = input[pos + 1];
- }
- out += input.slice(chunkStart, pos) + rep;
- pos += advance;
- chunkStart = pos;
- } else if (ch === 10 || ch === 13) {
- // bare LF/CR is unterminated; LS/PS stay valid at ES2019+
- this.raise(this.start, "Unterminated string constant");
- } else {
- pos++;
- }
- }
- this.pos = pos + 1;
- if (chunkStart === start) {
- return this.finishToken(tokTypes.string, input.slice(start, pos));
- }
- this.finishToken(tokTypes.string, out + input.slice(chunkStart, pos));
- }
- /**
- * Number fast path: plain integer literals (no leading zero, up to 15
- * digits so the float is exact) are accumulated numerically — no slice,
- * no parseFloat, no separator handling. Everything else (dots, exponents,
- * bigints, separators, octal forms) restarts the owned cold reader.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @param {boolean} startsWithDot whether the number started with a dot
- * @returns {void}
- */
- readNumber(startsWithDot) {
- if (startsWithDot) return this._readNumberCold(startsWithDot);
- const input = this.input;
- const start = this.pos;
- const len = input.length;
- const first = input.charCodeAt(start);
- let pos;
- if (first === 48) {
- const c1 = start + 1 < len ? input.charCodeAt(start + 1) : 0;
- if (c1 === 46) {
- // `0.<digits>`
- pos = start + 1;
- } else if (c1 > 127 || IDENT_CHAR[c1] === 1) {
- // 0x/0o/0b, 0e…, 0n, 0_, legacy `0NN`, or `0`+identifier: acorn
- return this._readNumberCold(startsWithDot);
- } else {
- // bare `0` before punctuation/operator/whitespace/EOF
- this.pos = start + 1;
- return this.finishToken(tokTypes.num, 0);
- }
- } else if (first > 48 && first <= 57) {
- // integer digits, accumulated numerically for the integer-only case
- let value = first - 48;
- pos = start + 1;
- while (pos < len) {
- const ch = input.charCodeAt(pos);
- if (ch >= 48 && ch <= 57) {
- value = value * 10 + (ch - 48);
- pos++;
- } else {
- break;
- }
- }
- const after = pos < len ? input.charCodeAt(pos) : 0;
- if (after !== 46) {
- // no fraction: exponent, separator, bigint suffix or a trailing
- // identifier char all need acorn's full handling and exact errors
- if (
- after === 101 ||
- after === 69 ||
- after === 95 ||
- after === 110 ||
- after > 127 ||
- IDENT_CHAR[after] === 1
- ) {
- return this._readNumberCold(startsWithDot);
- }
- // 15 digits always fit exactly into a double
- if (pos - start > 15) {
- return this._readNumberCold(startsWithDot);
- }
- this.pos = pos;
- return this.finishToken(tokTypes.num, value);
- }
- // a fraction follows the integer part
- } else {
- return this._readNumberCold(startsWithDot);
- }
- // decimal fraction: `pos` is at the '.'
- pos++;
- while (pos < len) {
- const ch = input.charCodeAt(pos);
- if (ch >= 48 && ch <= 57) pos++;
- else break;
- }
- const after = pos < len ? input.charCodeAt(pos) : 0;
- // exponent, a second dot, separator, bigint suffix or trailing identifier
- if (
- after === 46 ||
- after === 101 ||
- after === 69 ||
- after === 95 ||
- after === 110 ||
- after > 127 ||
- IDENT_CHAR[after] === 1
- ) {
- return this._readNumberCold(startsWithDot);
- }
- this.pos = pos;
- this.finishToken(tokTypes.num, Number.parseFloat(input.slice(start, pos)));
- }
- /**
- * Owned `strictDirective`: acorn's runs an anchored literal regex over
- * `this.input.slice(start)` — a fresh sliced string per (sloppy-mode)
- * function body. Sticky regexes at the offset scan the same grammar with no
- * slice. Same directive-prologue semantics, including the ASI tail checks.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/parseutil.js
- * @param {number} start offset of the function body's first statement
- * @returns {boolean} true when a 'use strict' directive leads the prologue
- * @this {ParserInternals & { input: string, options: { ecmaVersion: number } }}
- */
- strictDirective(start) {
- if (/** @type {number} */ (this.options.ecmaVersion) < 5) return false;
- const input = this.input;
- for (;;) {
- // Skip whitespace and comments (acorn's `skipWhiteSpace`, sticky).
- STRICT_SKIP_WS.lastIndex = start;
- start += /** @type {RegExpExecArray} */ (STRICT_SKIP_WS.exec(input))[0]
- .length;
- STRICT_LITERAL.lastIndex = start;
- const match = STRICT_LITERAL.exec(input);
- if (!match) return false;
- if ((match[1] || match[2]) === "use strict") {
- STRICT_SKIP_WS.lastIndex = start + match[0].length;
- const spaceAfter = /** @type {RegExpExecArray} */ (
- STRICT_SKIP_WS.exec(input)
- );
- const end = spaceAfter.index + spaceAfter[0].length;
- const next = input.charAt(end);
- return (
- next === ";" ||
- next === "}" ||
- (STRICT_LINE_BREAK.test(spaceAfter[0]) &&
- !(
- /[(`.[+\-/*%<>=,?^&]/.test(next) ||
- (next === "!" && input.charAt(end + 1) === "=")
- ))
- );
- }
- start += match[0].length;
- // Skip semicolon, if any.
- STRICT_SKIP_WS.lastIndex = start;
- start += /** @type {RegExpExecArray} */ (STRICT_SKIP_WS.exec(input))[0]
- .length;
- if (input[start] === ";") start++;
- }
- }
- /**
- * Template fast path: when the chunk contains no backslash and no CR, the
- * cooked value is one slice (LF/LS/PS cook to themselves). Escapes and CR
- * normalization restart the owned cold reader (its exact errors); location
- * tracking uses acorn's own.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @returns {void}
- */
- readTmplToken() {
- if (!this._noLocations) return base.readTmplToken.call(this);
- const input = this.input;
- const start = this.pos;
- const len = input.length;
- let pos = start;
- while (pos < len) {
- const ch = input.charCodeAt(pos);
- if (ch === 96 || (ch === 36 && input.charCodeAt(pos + 1) === 123)) {
- if (
- pos === this.start &&
- (this.type === tokTypes.template ||
- this.type === tokTypes.invalidTemplate)
- ) {
- if (ch === 36) {
- this.pos = pos + 2;
- return this.finishToken(tokTypes.dollarBraceL);
- }
- this.pos = pos + 1;
- return this.finishToken(tokTypes.backQuote);
- }
- this.pos = pos;
- return this.finishToken(tokTypes.template, input.slice(start, pos));
- }
- // backslash and CR need the cooked-string building of the cold reader
- if (ch === 92 || ch === 13) {
- return this._readTmplTokenCold();
- }
- pos++;
- }
- this.raise(this.start, "Unterminated template");
- }
- /**
- * Fast path for the common run of plain ASCII whitespace; comments and
- * unicode whitespace use the owned cold reader, location tracking acorn's.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals & { pos: number }}
- * @returns {void}
- */
- skipSpace() {
- if (!this._noLocations) return base.skipSpace.call(this);
- const input = this.input;
- const len = input.length;
- let pos = this.pos;
- while (pos < len) {
- const ch = input.charCodeAt(pos);
- // 9-13 and 32 cover tab, LF, VT, FF, CR and space
- if (ch === 32 || (ch > 8 && ch < 14)) {
- pos++;
- } else if (ch === 47 || ch > 127) {
- // comments or unicode whitespace: hand off to the cold reader
- this.pos = pos;
- return this._skipSpaceCold();
- } else {
- break;
- }
- }
- this.pos = pos;
- }
- // ----- cold-path readers (ported from acorn's tokenizer, reached only from
- // the lazy fast paths above; non-lazy mode keeps acorn's own prototype
- // methods so plugins that patch them still see acorn's tokenizer) -----
- /**
- * acorn `readInt`: reads `radix`-base digits (optionally exactly `len` of
- * them), honoring ES2021 `_` separators.
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @param {number} radix numeric radix
- * @param {number=} len fixed digit count (for escapes), else read greedily
- * @param {boolean=} maybeLegacyOctal whether a leading zero disallows separators
- * @returns {number | null} the integer value, or null when no digits matched
- */
- _readInt(radix, len, maybeLegacyOctal) {
- const input = this.input;
- const allowSeparators = this._ecmaVersion >= 12 && len === undefined;
- const isLegacyOctal =
- maybeLegacyOctal === true && input.charCodeAt(this.pos) === 48;
- const start = this.pos;
- let total = 0;
- let lastCode = 0;
- for (
- let i = 0, e = len === undefined ? Infinity : len;
- i < e;
- ++i, ++this.pos
- ) {
- const code = input.charCodeAt(this.pos);
- let val;
- if (allowSeparators && code === 95) {
- if (isLegacyOctal) {
- this.raiseRecoverable(
- this.pos,
- "Numeric separator is not allowed in legacy octal numeric literals"
- );
- }
- if (lastCode === 95) {
- this.raiseRecoverable(
- this.pos,
- "Numeric separator must be exactly one underscore"
- );
- }
- if (i === 0) {
- this.raiseRecoverable(
- this.pos,
- "Numeric separator is not allowed at the first of digits"
- );
- }
- lastCode = code;
- continue;
- }
- if (code >= 97) val = code - 97 + 10;
- else if (code >= 65) val = code - 65 + 10;
- else if (code >= 48 && code <= 57) val = code - 48;
- else val = Infinity;
- if (val >= radix) break;
- lastCode = code;
- total = total * radix + val;
- }
- if (allowSeparators && lastCode === 95) {
- this.raiseRecoverable(
- this.pos - 1,
- "Numeric separator is not allowed at the last of digits"
- );
- }
- if (this.pos === start || (len !== undefined && this.pos - start !== len)) {
- return null;
- }
- return total;
- }
- /**
- * acorn `readRadixNumber`: `0x`/`0o`/`0b` integer or bigint literals.
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @param {number} radix numeric radix (2/8/16)
- * @returns {void}
- */
- _readRadixNumber(radix) {
- const input = this.input;
- const start = this.pos;
- this.pos += 2;
- let val = this._readInt(radix);
- if (val === null) {
- this.raise(this.start + 2, `Expected number in radix ${radix}`);
- }
- if (this._ecmaVersion >= 11 && input.charCodeAt(this.pos) === 110) {
- val = /** @type {EXPECTED_ANY} */ (
- stringToBigInt(input.slice(start, this.pos))
- );
- ++this.pos;
- } else if (isIdentifierStart(this.fullCharCodeAtPos())) {
- this.raise(this.pos, "Identifier directly after number");
- }
- return this.finishToken(tokTypes.num, val);
- }
- /**
- * acorn `readNumber`: decimal/float/legacy-octal/bigint literals (the fast
- * path handles only plain integers and simple fractions).
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @param {boolean} startsWithDot whether the literal started with `.`
- * @returns {void}
- */
- _readNumberCold(startsWithDot) {
- const input = this.input;
- const start = this.pos;
- if (!startsWithDot && this._readInt(10, undefined, true) === null) {
- this.raise(start, "Invalid number");
- }
- let octal = this.pos - start >= 2 && input.charCodeAt(start) === 48;
- if (octal && this.strict) this.raise(start, "Invalid number");
- let next = input.charCodeAt(this.pos);
- if (!octal && !startsWithDot && this._ecmaVersion >= 11 && next === 110) {
- const val = stringToBigInt(input.slice(start, this.pos));
- ++this.pos;
- if (isIdentifierStart(this.fullCharCodeAtPos())) {
- this.raise(this.pos, "Identifier directly after number");
- }
- return this.finishToken(tokTypes.num, /** @type {EXPECTED_ANY} */ (val));
- }
- if (octal && /[89]/.test(input.slice(start, this.pos))) octal = false;
- if (next === 46 && !octal) {
- ++this.pos;
- this._readInt(10);
- next = input.charCodeAt(this.pos);
- }
- if ((next === 69 || next === 101) && !octal) {
- next = input.charCodeAt(++this.pos);
- if (next === 43 || next === 45) ++this.pos;
- if (this._readInt(10) === null) this.raise(start, "Invalid number");
- }
- if (isIdentifierStart(this.fullCharCodeAtPos())) {
- this.raise(this.pos, "Identifier directly after number");
- }
- const val = stringToNumber(input.slice(start, this.pos), octal);
- return this.finishToken(tokTypes.num, val);
- }
- /**
- * acorn `readCodePoint`: a `\u{...}` or `\uXXXX` escape.
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @returns {number} the escaped code point
- */
- _readCodePoint() {
- const input = this.input;
- const ch = input.charCodeAt(this.pos);
- let code;
- if (ch === 123) {
- if (this._ecmaVersion < 6) this.unexpected();
- const codePos = ++this.pos;
- code = this._readHexChar(input.indexOf("}", this.pos) - this.pos);
- ++this.pos;
- if (code > 0x10ffff) {
- this.invalidStringToken(codePos, "Code point out of bounds");
- }
- } else {
- code = this._readHexChar(4);
- }
- return code;
- }
- /**
- * acorn `readHexChar`: exactly `len` hex digits.
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @param {number} len number of hex digits
- * @returns {number} the parsed code
- */
- _readHexChar(len) {
- const codePos = this.pos;
- const n = this._readInt(16, len);
- if (n === null) {
- this.invalidStringToken(codePos, "Bad character escape sequence");
- }
- return /** @type {number} */ (n);
- }
- /**
- * acorn `readString`: the escape-bearing string reader (the fast path cooks
- * only the common single-char escapes inline).
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @param {number} quote quote char code
- * @returns {void}
- */
- _readStringCold(quote) {
- const input = this.input;
- let out = "";
- let chunkStart = ++this.pos;
- for (;;) {
- if (this.pos >= input.length) {
- this.raise(this.start, "Unterminated string constant");
- }
- const ch = input.charCodeAt(this.pos);
- if (ch === quote) break;
- if (ch === 92) {
- out += input.slice(chunkStart, this.pos);
- out += this._readEscapedChar(false);
- chunkStart = this.pos;
- } else if (ch === 0x2028 || ch === 0x2029) {
- if (this._ecmaVersion < 10) {
- this.raise(this.start, "Unterminated string constant");
- }
- ++this.pos;
- } else {
- if (isNewLine(ch)) {
- this.raise(this.start, "Unterminated string constant");
- }
- ++this.pos;
- }
- }
- out += input.slice(chunkStart, this.pos++);
- return this.finishToken(tokTypes.string, out);
- }
- /**
- * acorn `readTmplToken`: the escape/CR-normalizing template reader. Invalid
- * escapes throw acorn's sentinel via `invalidStringToken`, which acorn's own
- * `tryReadTemplateToken` (not overridden here) catches to re-read raw.
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @returns {void}
- */
- _readTmplTokenCold() {
- const input = this.input;
- let out = "";
- let chunkStart = this.pos;
- for (;;) {
- if (this.pos >= input.length) {
- this.raise(this.start, "Unterminated template");
- }
- const ch = input.charCodeAt(this.pos);
- if (ch === 96 || (ch === 36 && input.charCodeAt(this.pos + 1) === 123)) {
- if (
- this.pos === this.start &&
- (this.type === tokTypes.template ||
- this.type === tokTypes.invalidTemplate)
- ) {
- if (ch === 36) {
- this.pos += 2;
- return this.finishToken(tokTypes.dollarBraceL);
- }
- ++this.pos;
- return this.finishToken(tokTypes.backQuote);
- }
- out += input.slice(chunkStart, this.pos);
- return this.finishToken(tokTypes.template, out);
- }
- if (ch === 92) {
- out += input.slice(chunkStart, this.pos);
- out += this._readEscapedChar(true);
- chunkStart = this.pos;
- } else if (isNewLine(ch)) {
- out += input.slice(chunkStart, this.pos);
- ++this.pos;
- if (ch === 13 && input.charCodeAt(this.pos) === 10) ++this.pos;
- out += "\n";
- chunkStart = this.pos;
- } else {
- ++this.pos;
- }
- }
- }
- /**
- * acorn `readEscapedChar`: cooks one backslash escape in a string/template.
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @param {boolean} inTemplate whether the escape is in a template literal
- * @returns {string} the cooked replacement (may be `""`)
- */
- _readEscapedChar(inTemplate) {
- const input = this.input;
- let ch = input.charCodeAt(++this.pos);
- ++this.pos;
- switch (ch) {
- case 110:
- return "\n";
- case 114:
- return "\r";
- case 120:
- return String.fromCharCode(this._readHexChar(2));
- case 117:
- return codePointToString(this._readCodePoint());
- case 116:
- return "\t";
- case 98:
- return "\b";
- case 118:
- return "\u000B";
- case 102:
- return "\f";
- case 13:
- if (input.charCodeAt(this.pos) === 10) ++this.pos;
- // falls through
- case 10:
- return "";
- case 56:
- case 57:
- if (this.strict) {
- this.invalidStringToken(this.pos - 1, "Invalid escape sequence");
- }
- if (inTemplate) {
- this.invalidStringToken(
- this.pos - 1,
- "Invalid escape sequence in template string"
- );
- }
- // falls through
- default:
- if (ch >= 48 && ch <= 55) {
- let octalStr = /** @type {RegExpMatchArray} */ (
- input.slice(this.pos - 1, this.pos + 2).match(/^[0-7]+/)
- )[0];
- let octal = Number.parseInt(octalStr, 8);
- if (octal > 255) {
- octalStr = octalStr.slice(0, -1);
- octal = Number.parseInt(octalStr, 8);
- }
- this.pos += octalStr.length - 1;
- ch = input.charCodeAt(this.pos);
- if (
- (octalStr !== "0" || ch === 56 || ch === 57) &&
- (this.strict || inTemplate)
- ) {
- this.invalidStringToken(
- this.pos - 1 - octalStr.length,
- inTemplate
- ? "Octal literal in template string"
- : "Octal literal in strict mode"
- );
- }
- return String.fromCharCode(octal);
- }
- if (isNewLine(ch)) return "";
- return String.fromCharCode(ch);
- }
- }
- /**
- * acorn `readWord1`: the escape-/astral-aware identifier reader (the fast
- * path handles pure-ASCII words).
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @returns {string} the (possibly escape-cooked) word
- */
- _readWord1Cold() {
- const input = this.input;
- this.containsEsc = false;
- let word = "";
- let first = true;
- let chunkStart = this.pos;
- const astral = this._ecmaVersion >= 6;
- while (this.pos < input.length) {
- const ch = this.fullCharCodeAtPos();
- if (isIdentifierChar(ch, astral)) {
- this.pos += ch <= 0xffff ? 1 : 2;
- } else if (ch === 92) {
- this.containsEsc = true;
- word += input.slice(chunkStart, this.pos);
- const escStart = this.pos;
- if (input.charCodeAt(++this.pos) !== 117) {
- this.invalidStringToken(
- this.pos,
- "Expecting Unicode escape sequence \\uXXXX"
- );
- }
- ++this.pos;
- const esc = this._readCodePoint();
- if (!(first ? isIdentifierStart : isIdentifierChar)(esc, astral)) {
- this.invalidStringToken(escStart, "Invalid Unicode escape");
- }
- word += codePointToString(esc);
- chunkStart = this.pos;
- } else {
- break;
- }
- first = false;
- }
- return word + input.slice(chunkStart, this.pos);
- }
- /**
- * acorn `skipSpace` (location-free): unicode whitespace and comments. Only
- * reached from the lazy fast paths, which keep no line bookkeeping.
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @returns {void}
- */
- _skipSpaceCold() {
- const input = this.input;
- const len = input.length;
- loop: while (this.pos < len) {
- const ch = input.charCodeAt(this.pos);
- switch (ch) {
- case 32:
- case 160:
- ++this.pos;
- break;
- case 13:
- if (input.charCodeAt(this.pos + 1) === 10) ++this.pos;
- // falls through
- case 10:
- case 8232:
- case 8233:
- ++this.pos;
- break;
- case 47:
- switch (input.charCodeAt(this.pos + 1)) {
- case 42:
- this.skipBlockComment();
- break;
- case 47:
- this.skipLineComment(2);
- break;
- default:
- break loop;
- }
- break;
- default:
- if (
- (ch > 8 && ch < 14) ||
- (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch)))
- ) {
- ++this.pos;
- } else {
- break loop;
- }
- }
- }
- }
- /**
- * acorn `readToken_numberSign` plus `getTokenFromCode`'s default: a private
- * identifier (`#x`) or an unexpected-character error.
- * https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @param {number} code current char code
- * @returns {void}
- */
- _getUnknownOrPrivate(code) {
- if (code === 35 && this._ecmaVersion >= 13) {
- ++this.pos;
- const next = this.fullCharCodeAtPos();
- if (isIdentifierStart(next, true) || next === 92) {
- return this.finishToken(tokTypes.privateId, this.readWord1());
- }
- this.raise(this.pos, `Unexpected character '${codePointToString(next)}'`);
- }
- this.raise(this.pos, `Unexpected character '${codePointToString(code)}'`);
- }
- // ----- word classification (Map/Set lookups, replaces acorn's regexps) -----
- /**
- * Replaces acorn's `readWord`: `readWord1`'s ASCII fast path inlined so the
- * token type can be memoized per `WORD_CACHE` slot (classification is pure in
- * the word and the per-option keyword set) — a cache hit serves the type by
- * identity compare instead of a keywords Map probe.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @returns {void}
- */
- readWord() {
- const input = this.input;
- const start = this.pos;
- const len = input.length;
- let hash = 0;
- let pos = start;
- while (pos < len) {
- const ch = input.charCodeAt(pos);
- if (ch < 128) {
- if (IDENT_CHAR[ch] === 0) {
- // backslash escape: restart cold so escape rules see the word
- if (ch === 92) return this._finishWordSlow(this._readWord1Cold());
- break;
- }
- hash = (Math.imul(hash, 33) + ch) | 0;
- pos++;
- } else {
- return this._finishWordSlow(this._readWord1Cold());
- }
- }
- this.containsEsc = false;
- this.pos = pos;
- const wordLen = pos - start;
- if (wordLen >= 2 && wordLen <= WORD_CACHE_MAX_LEN) {
- const slot = hash & WORD_CACHE_MASK;
- const lookups = this._wordLookups;
- const owner = lookups.id;
- const cached = WORD_CACHE[slot];
- /** @type {string} */
- let word;
- /** @type {TokenType} */
- let type;
- if (
- cached !== null &&
- cached.length === wordLen &&
- // length already matched, so a prefix test is an exact compare —
- // one builtin instead of a charCodeAt loop per cached character
- input.startsWith(cached, start)
- ) {
- word = cached;
- if (WORD_TYPE_OWNERS[slot] === owner) {
- type = /** @type {TokenType} */ (WORD_TYPES[slot]);
- } else {
- type = classifyWord(cached, lookups.keywords);
- WORD_TYPES[slot] = type;
- WORD_TYPE_OWNERS[slot] = owner;
- }
- } else {
- word = input.slice(start, pos);
- WORD_CACHE[slot] = word;
- type = classifyWord(word, lookups.keywords);
- WORD_TYPES[slot] = type;
- WORD_TYPE_OWNERS[slot] = owner;
- }
- if (type === tokTypes.name && this._inlineFinish) {
- // finishToken(name) inlined: offset/type/value stores plus
- // name.updateContext (as in the owned finishToken)
- this.end = pos;
- const prevType = this.type;
- this.type = type;
- this.value = word;
- this.exprAllowed =
- ((word === "of" && !this.exprAllowed) ||
- (word === "yield" && this.inGeneratorContext())) &&
- prevType !== tokTypes.dot &&
- this._ecmaVersion >= 6;
- return;
- }
- return this.finishToken(type, word);
- }
- // Every keyword is 2-10 chars, so a word outside the cacheable range is a
- // plain name and can be neither `of` nor `yield`.
- if (this._inlineFinish) {
- this.end = pos;
- this.type = tokTypes.name;
- this.value = input.slice(start, pos);
- this.exprAllowed = false;
- return;
- }
- return this._finishWordSlow(input.slice(start, pos));
- }
- /**
- * Uncached tail of `readWord`: escaped/non-ASCII, 1-char and over-long words.
- * @this {ParserInternals}
- * @param {string} word the word
- * @returns {void}
- */
- _finishWordSlow(word) {
- this.finishToken(classifyWord(word, this._wordLookups.keywords), word);
- }
- /**
- * Mirror of acorn's `checkUnreserved` with its two per-identifier regexp
- * tests (`keywords` and `reservedWords`/`reservedWordsStrict`) folded into a
- * single `reservedKinds` lookup — one hash probe instead of two, and the
- * common plain identifier misses it and returns. Branches and error
- * messages match acorn exactly.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {Identifier} ref identifier node
- * @this {ParserInternals}
- */
- checkUnreserved(ref) {
- const { start, end, name } = ref;
- // every reserved word and special name below is short lowercase ASCII, so
- // most identifiers exit on this shape gate before any string compare
- // (`yield`/`await`/`arguments` all pass it: lengths 5/5/9 ≤ reservedMaxLen,
- // which keywords like `instanceof` keep at ≥ 10)
- const lookups = this._wordLookups;
- const nameLen = name.length;
- if (nameLen < 2 || nameLen > lookups.reservedMaxLen) return;
- const firstCC = name.charCodeAt(0);
- if (firstCC < 97 || firstCC > 122) return;
- // name-first ordering: acorn's `inGenerator`/`inAsync` are getters that
- // walk the scope stack, so gate them behind the cheap string compare —
- // a plain identifier never triggers them
- if (name === "yield" && this.inGenerator) {
- this.raiseRecoverable(
- start,
- "Cannot use 'yield' as identifier inside a generator"
- );
- } else if (name === "await" && this.inAsync) {
- this.raiseRecoverable(
- start,
- "Cannot use 'await' as identifier inside an async function"
- );
- }
- if (name === "arguments" && !(this.currentThisScope().flags & SCOPE_VAR)) {
- this.raiseRecoverable(
- start,
- "Cannot use 'arguments' in class field initializer"
- );
- }
- if ((name === "arguments" || name === "await") && this.inClassStaticBlock) {
- this.raise(
- start,
- `Cannot use ${name} in class static initialization block`
- );
- }
- const kind = lookups.reservedKinds.get(name);
- if (kind === undefined) return;
- if (kind === 1) {
- this.raise(start, `Unexpected keyword '${name}'`);
- }
- if (
- /** @type {number} */ (this.options.ecmaVersion) < 6 &&
- this.input.slice(start, end).includes("\\")
- ) {
- return;
- }
- if (kind === 2 || (kind === 3 && this.strict)) {
- if (name === "await" && !this.inAsync) {
- this.raiseRecoverable(
- start,
- "Cannot use keyword 'await' outside an async function"
- );
- }
- this.raiseRecoverable(start, `The keyword '${name}' is reserved`);
- }
- }
- /**
- * Owned `checkLValSimple`, an exact-semantics copy of acorn 8's with the
- * strict-bind probe inlined: the constructor-installed Set stand-in is one
- * `has` behind a length gate instead of a closure call per checked name.
- * A runtime-replaced `reservedWordsStrictBind` falls back to `.test()`.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/lval.js
- * @param {Node} expr assignment/binding target
- * @param {number=} bindingType acorn BIND_* binding type
- * @param {Record<string, boolean> | null=} checkClashes param-name clash record
- * @returns {void}
- * @this {ParserInternals}
- */
- checkLValSimple(expr, bindingType, checkClashes) {
- if (bindingType === undefined) bindingType = BIND_NONE;
- const isBind = bindingType !== BIND_NONE;
- const type = expr.type;
- if (type === "Identifier") {
- const name = /** @type {Identifier} */ (expr).name;
- if (this.strict) {
- const lookups = this._wordLookups;
- let reserved;
- if (this.reservedWordsStrictBind === lookups.reservedBindTest) {
- const len = name.length;
- reserved =
- len >= lookups.reservedBindMinLen &&
- len <= lookups.reservedBindMaxLen &&
- lookups.reservedBindSet.has(name);
- } else {
- reserved = this.reservedWordsStrictBind.test(name);
- }
- if (reserved) {
- this.raiseRecoverable(
- expr.start,
- `${isBind ? "Binding " : "Assigning to "}${name} in strict mode`
- );
- }
- }
- if (isBind) {
- if (bindingType === BIND_LEXICAL && name === "let") {
- this.raiseRecoverable(
- expr.start,
- "let is disallowed as a lexically bound name"
- );
- }
- if (checkClashes) {
- if (Object.prototype.hasOwnProperty.call(checkClashes, name)) {
- this.raiseRecoverable(expr.start, "Argument name clash");
- }
- checkClashes[name] = true;
- }
- if (bindingType !== BIND_OUTSIDE) {
- this.declareName(name, bindingType, expr.start);
- }
- }
- return;
- }
- if (type === "ChainExpression") {
- this.raiseRecoverable(
- expr.start,
- "Optional chaining cannot appear in left-hand side"
- );
- return;
- }
- if (type === "MemberExpression") {
- if (isBind) {
- this.raiseRecoverable(expr.start, "Binding member expression");
- }
- return;
- }
- if (type === "ParenthesizedExpression") {
- if (isBind) {
- this.raiseRecoverable(expr.start, "Binding parenthesized expression");
- }
- return this.checkLValSimple(
- /** @type {Node} */ (
- /** @type {Node & { expression?: Node }} */ (expr).expression
- ),
- bindingType,
- checkClashes
- );
- }
- this.raise(expr.start, `${isBind ? "Binding" : "Assigning to"} rvalue`);
- }
- /**
- * Owned `checkLValPattern`, acorn's verbatim, so declarator/assignment
- * targets stay on owned code down to `checkLValSimple`/`declareName`.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/lval.js
- * @param {Node} expr binding pattern
- * @param {number=} bindingType acorn BIND_* binding type
- * @param {Record<string, boolean> | null=} checkClashes param-name clash record
- * @returns {void}
- * @this {ParserInternals}
- */
- checkLValPattern(expr, bindingType, checkClashes) {
- const type = expr.type;
- if (type === "ObjectPattern") {
- const properties = /** @type {Node & { properties: Node[] }} */ (expr)
- .properties;
- for (let i = 0; i < properties.length; i++) {
- this.checkLValInnerPattern(properties[i], bindingType, checkClashes);
- }
- } else if (type === "ArrayPattern") {
- const elements = /** @type {Node & { elements: (Node | null)[] }} */ (
- expr
- ).elements;
- for (let i = 0; i < elements.length; i++) {
- const element = elements[i];
- if (element) {
- this.checkLValInnerPattern(element, bindingType, checkClashes);
- }
- }
- } else {
- this.checkLValSimple(expr, bindingType, checkClashes);
- }
- }
- /**
- * Owned `checkLValInnerPattern`, acorn's verbatim.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/lval.js
- * @param {Node} expr pattern element
- * @param {number=} bindingType acorn BIND_* binding type
- * @param {Record<string, boolean> | null=} checkClashes param-name clash record
- * @returns {void}
- * @this {ParserInternals}
- */
- checkLValInnerPattern(expr, bindingType, checkClashes) {
- const type = expr.type;
- if (type === "Property") {
- // AssignmentProperty has type === "Property"
- this.checkLValInnerPattern(
- /** @type {Node} */ (
- /** @type {Node & { value?: Node }} */ (expr).value
- ),
- bindingType,
- checkClashes
- );
- } else if (type === "AssignmentPattern") {
- this.checkLValPattern(
- /** @type {Node} */ (/** @type {Node & { left?: Node }} */ (expr).left),
- bindingType,
- checkClashes
- );
- } else if (type === "RestElement") {
- this.checkLValPattern(
- /** @type {Node} */ (
- /** @type {Node & { argument?: Node }} */ (expr).argument
- ),
- bindingType,
- checkClashes
- );
- } else {
- this.checkLValPattern(expr, bindingType, checkClashes);
- }
- }
- /**
- * Replaces acorn's `canInsertSemicolon`, whose line-break check slices the
- * inter-token gap and runs a regexp on it for every ASI decision (hundreds
- * of thousands per file). Scan the gap for a line terminator instead — no
- * slice, no regexp.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/parseutil.js
- * @returns {boolean} whether a semicolon may be inserted here
- * @this {ParserInternals}
- */
- canInsertSemicolon() {
- if (this.type === tokTypes.eof || this.type === tokTypes.braceR) {
- return true;
- }
- // the owned nextToken already classified the gap; 2 (comment/unicode in
- // the gap, or a token from acorn's tokenizer) falls back to the scan
- return this._gapHasNewline();
- }
- // ----- comment collection without eager text slicing -----
- /**
- * Replaces acorn's `skipLineComment` when comments are collected lazily:
- * the same scan, but no text slice and no position objects. Acorn calls
- * this for `//`, hashbangs and HTML-style comments (varying `startSkip`).
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @param {number} startSkip length of the comment opener
- * @returns {void}
- */
- skipLineComment(startSkip) {
- const comments = this._lazyComments;
- if (comments === undefined) {
- return base.skipLineComment.call(this, startSkip);
- }
- const input = this.input;
- const start = this.pos;
- const len = input.length;
- let pos = start + startSkip;
- while (pos < len) {
- const ch = input.charCodeAt(pos);
- // LF, CR, LS, PS terminate the comment but are not part of it
- if (ch === 10 || ch === 13 || ch === 0x2028 || ch === 0x2029) break;
- pos++;
- }
- this.pos = pos;
- comments.push(new LazyComment(false, start + startSkip, start, pos, input));
- }
- /**
- * Replaces acorn's `skipBlockComment` when comments are collected lazily.
- * Locations are always off in lazy mode, so line breaks need no handling.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals}
- * @returns {void}
- */
- skipBlockComment() {
- const comments = this._lazyComments;
- if (comments === undefined) {
- return base.skipBlockComment.call(this);
- }
- const start = this.pos;
- const end = this.input.indexOf("*/", (this.pos += 2));
- if (end === -1) this.raise(this.pos - 2, "Unterminated comment");
- this.pos = end + 2;
- comments.push(
- new LazyComment(true, start + 2, start, this.pos, this.input)
- );
- }
- // ----- lazy range -----
- /**
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/node.js
- * @returns {Node} new node
- * @this {ParserInternals}
- */
- startNode() {
- if (!this._lazy) return base.startNode.call(this);
- return new LazyLocNode(this.start);
- }
- /**
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/node.js
- * @param {number} pos start offset
- * @param {Position=} loc start position when acorn tracks locations
- * @returns {Node} new node
- * @this {ParserInternals}
- */
- startNodeAt(pos, loc) {
- if (!this._lazy) return base.startNodeAt.call(this, pos, loc);
- return new LazyLocNode(pos);
- }
- /**
- * Lazy-mode `finishNode`: acorn's `locations`/`ranges` writes are dead when
- * `range` is served lazily and `loc` not at all, so skip them and the `finishNodeAt`
- * indirection. Runs once per node.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/node.js
- * @param {Node} node node to finish
- * @param {string} type node type
- * @returns {Node} the finished node
- * @this {ParserInternals}
- */
- finishNode(node, type) {
- if (!this._lazy) return base.finishNode.call(this, node, type);
- node.type = type;
- node.end = this.lastTokEnd;
- return node;
- }
- /**
- * Mirror of acorn's `copyNode`, which bypasses `startNodeAt` via
- * `new Node(...)` and would otherwise produce non-lazy nodes.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/node.js
- * @param {Node} node node to copy
- * @returns {Node} copied node
- * @this {ParserInternals}
- */
- copyNode(node) {
- const newNode = this.startNodeAt(node.start, this.startLoc);
- const from = /** @type {Record<string, unknown>} */ (
- /** @type {unknown} */ (node)
- );
- const to = /** @type {Record<string, unknown>} */ (
- /** @type {unknown} */ (newNode)
- );
- for (const prop in from) to[prop] = from[prop];
- return newNode;
- }
- // ----- owned node construction (single-shape nodes, replaces acorn's
- // start-empty-then-mutate flow one node type at a time) -----
- /**
- * Owned `parseIdent` for the common name-token case: builds the finished
- * `IdentifierNode` directly, skipping acorn's `parseIdentNode`/`startNode`/
- * `finishNode` chain and its keyword branches. Keyword-as-identifier
- * (`obj.class`) and non-lazy mode delegate to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {boolean=} liberal whether reserved words are allowed
- * @returns {Identifier} identifier node
- * @this {ParserInternals}
- */
- parseIdent(liberal) {
- if (this.type !== tokTypes.name || !this._lazy) {
- return base.parseIdent.call(this, liberal);
- }
- const node = /** @type {Identifier} */ (
- /** @type {unknown} */ (
- new IdentifierNode(
- this.start,
- this.end,
- /** @type {string} */ (this.value)
- )
- )
- );
- this.next(Boolean(liberal));
- if (!liberal) {
- this.checkUnreserved(node);
- if (node.name === "await" && !this.awaitIdentPos) {
- this.awaitIdentPos = node.start;
- }
- }
- return node;
- }
- /**
- * Owned `parseSubscripts`, an exact-semantics copy of acorn 8's with the
- * async-arrow-head probe reordered cheapest-first (all operands are pure)
- * and the arrow exit probe served by `_lastArrow` identity instead of the
- * megamorphic `.type` load. Delegates when either fast path is off.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {Expression} baseExpr subscript base
- * @param {number} startPos expression start offset
- * @param {Position | undefined} startLoc expression start position
- * @param {boolean=} noCalls whether calls are forbidden (`new` callee)
- * @param {boolean | string=} forInit for-init context flag
- * @returns {Expression} expression with subscripts applied
- * @this {ParserInternals}
- */
- parseSubscripts(baseExpr, startPos, startLoc, noCalls, forInit) {
- if (!this._subscriptFastPath || !this._arrowFastPath) {
- return base.parseSubscripts.call(
- this,
- baseExpr,
- startPos,
- startLoc,
- noCalls,
- forInit
- );
- }
- // acorn's probe order is (ecmaVersion, type, name, offsets, ASI,
- // potentialArrowAt); every operand is pure, so the SMI compares run
- // first and the megamorphic `.type` load only when `async` matched
- const maybeAsyncArrow =
- baseExpr.start === this.potentialArrowAt &&
- baseExpr.end - baseExpr.start === 5 &&
- this.lastTokEnd === baseExpr.end &&
- this._ecmaVersion >= 8 &&
- /** @type {Identifier} */ (baseExpr).name === "async" &&
- baseExpr.type === "Identifier" &&
- !this.canInsertSemicolon();
- let optionalChained = false;
- for (;;) {
- const element = this.parseSubscript(
- baseExpr,
- startPos,
- startLoc,
- noCalls,
- maybeAsyncArrow,
- optionalChained,
- forInit
- );
- if (
- /** @type {Expression & { optional?: boolean }} */ (element).optional
- ) {
- optionalChained = true;
- }
- if (element === baseExpr || element === this._lastArrow) {
- if (optionalChained) {
- const chainNode =
- /** @type {Node & { expression?: Expression }} */
- (this.startNodeAt(startPos, startLoc));
- chainNode.expression = element;
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- this.finishNode(
- /** @type {Node} */ (chainNode),
- "ChainExpression"
- )
- )
- );
- }
- return element;
- }
- baseExpr = element;
- }
- }
- /**
- * Owned `parseSubscript`, an exact-semantics copy of acorn 8's with the
- * node construction replaced: member and call nodes are built fully-formed
- * after their property/arguments parse (the half-built node was never
- * reachable during it), landing on `MemberExpressionNode`/
- * `CallExpressionNode`'s single shapes. Pre-optional-chaining ecmaVersions
- * and non-lazy mode delegate to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {Expression} baseExpr subscript base
- * @param {number} startPos expression start offset
- * @param {Position | undefined} startLoc expression start position
- * @param {boolean | undefined} noCalls whether calls are forbidden (`new` callee)
- * @param {boolean} maybeAsyncArrow whether this may be an async arrow head
- * @param {boolean} optionalChained whether the chain is already optional
- * @param {boolean | string} forInit for-init context flag
- * @returns {Expression} subscript element or `baseExpr` when done
- * @this {ParserInternals}
- */
- parseSubscript(
- baseExpr,
- startPos,
- startLoc,
- noCalls,
- maybeAsyncArrow,
- optionalChained,
- forInit
- ) {
- if (!this._subscriptFastPath) {
- return base.parseSubscript.call(
- this,
- baseExpr,
- startPos,
- startLoc,
- noCalls,
- maybeAsyncArrow,
- optionalChained,
- forInit
- );
- }
- const optional = this.eat(tokTypes.questionDot);
- if (noCalls && optional) {
- this.raise(
- this.lastTokStart,
- "Optional chaining cannot appear in the callee of new expressions"
- );
- }
- const computed = this.eat(tokTypes.bracketL);
- if (
- computed ||
- (optional &&
- this.type !== tokTypes.parenL &&
- this.type !== tokTypes.backQuote) ||
- this.eat(tokTypes.dot)
- ) {
- /** @type {Node} */
- let property;
- if (computed) {
- property = this.parseExpression();
- this.expect(tokTypes.bracketR);
- } else if (
- this.type === tokTypes.privateId &&
- /** @type {string} */ (baseExpr.type) !== "Super"
- ) {
- property = this.parsePrivateIdent();
- } else {
- property = this.parseIdent(this.options.allowReserved !== "never");
- }
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- new MemberExpressionNode(
- startPos,
- this.lastTokEnd,
- baseExpr,
- property,
- computed,
- optional
- )
- )
- );
- } else if (!noCalls && this.eat(tokTypes.parenL)) {
- const refDestructuringErrors = this._acquireDestructuringErrors();
- const oldYieldPos = this.yieldPos;
- const oldAwaitPos = this.awaitPos;
- const oldAwaitIdentPos = this.awaitIdentPos;
- this.yieldPos = 0;
- this.awaitPos = 0;
- this.awaitIdentPos = 0;
- const exprList = this.parseExprList(
- tokTypes.parenR,
- true,
- false,
- refDestructuringErrors
- );
- if (maybeAsyncArrow && !optional && this.shouldParseAsyncArrow()) {
- this.checkPatternErrors(refDestructuringErrors, false);
- this._releaseDestructuringErrors();
- this.checkYieldAwaitInDefaultParams();
- if (this.awaitIdentPos > 0) {
- this.raise(
- this.awaitIdentPos,
- "Cannot use 'await' as identifier inside an async function"
- );
- }
- this.yieldPos = oldYieldPos;
- this.awaitPos = oldAwaitPos;
- this.awaitIdentPos = oldAwaitIdentPos;
- return this.parseSubscriptAsyncArrow(
- startPos,
- startLoc,
- exprList,
- forInit
- );
- }
- this.checkExpressionErrors(refDestructuringErrors, true);
- this._releaseDestructuringErrors();
- this.yieldPos = oldYieldPos || this.yieldPos;
- this.awaitPos = oldAwaitPos || this.awaitPos;
- this.awaitIdentPos = oldAwaitIdentPos || this.awaitIdentPos;
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- new CallExpressionNode(
- startPos,
- this.lastTokEnd,
- baseExpr,
- exprList,
- optional
- )
- )
- );
- } else if (this.type === tokTypes.backQuote) {
- if (optional || optionalChained) {
- this.raise(
- this.start,
- "Optional chaining cannot appear in the tag of tagged template expressions"
- );
- }
- const node = this.startNodeAt(startPos, startLoc);
- /** @type {Node & { tag?: Expression, quasi?: Node }} */
- (node).tag = baseExpr;
- /** @type {Node & { tag?: Expression, quasi?: Node }} */
- (node).quasi = this.parseTemplate({ isTagged: true });
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- this.finishNode(node, "TaggedTemplateExpression")
- )
- );
- }
- return baseExpr;
- }
- /**
- * Owned `parseStatement` for the hot statement heads: acorn starts a node
- * before dispatching, but the owned statement parsers build their own
- * single-shape nodes, so that started node was one discarded allocation per
- * statement. Dispatch the common heads (`var`/`let`/`const`, `if`, `return`,
- * blocks and plain expression statements) without it; everything rarer, the
- * `name`-token ambiguities (`let`/`async`/`using`/`await` heads) and
- * plugin-overridden parsers delegate to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {string | null} context statement context
- * @param {boolean=} topLevel whether parsing top-level statements
- * @param {unknown=} exports export tracking object
- * @returns {Node} statement
- * @this {ParserInternals}
- */
- parseStatement(context, topLevel, exports) {
- if (!this._stmtFastPath) {
- return base.parseStatement.call(this, context, topLevel, exports);
- }
- const startType = this.type;
- switch (startType) {
- case tokTypes._var:
- case tokTypes._const: {
- const kind = /** @type {string} */ (this.value);
- if (context && kind !== "var") this.unexpected();
- return this._parseVarStatementAt(this.start, kind);
- }
- case tokTypes._if:
- return this._parseIfStatementAt(this.start);
- case tokTypes._return:
- return this._parseReturnStatementAt(this.start);
- case tokTypes.braceL:
- // the owned parseBlock never reads the started node
- return this.parseBlock(true);
- case tokTypes._function: {
- if (!this._funcStmtOwn) {
- return base.parseStatement.call(this, context, topLevel, exports);
- }
- // acorn's hanging-function guard (context = single-statement position)
- if (
- context &&
- (this.strict || (context !== "if" && context !== "label"))
- ) {
- this.unexpected();
- }
- // parseFunctionStatement, inlined without the started node
- const start = this.start;
- this.next();
- return this._parseFunctionAt(
- start,
- FUNC_STATEMENT | (context ? FUNC_HANGING_STATEMENT : 0),
- false,
- false,
- undefined
- );
- }
- case tokTypes._break:
- case tokTypes._continue:
- if (!this._stmt2FastPath) {
- return base.parseStatement.call(this, context, topLevel, exports);
- }
- return this._parseBreakContinueStatementAt(
- this.start,
- /** @type {string} */ (startType.keyword)
- );
- case tokTypes._for:
- if (!this._stmt2FastPath) {
- return base.parseStatement.call(this, context, topLevel, exports);
- }
- return this._parseForStatementAt(this.start);
- case tokTypes._switch:
- if (!this._stmt2FastPath) {
- return base.parseStatement.call(this, context, topLevel, exports);
- }
- return this._parseSwitchStatementAt(this.start);
- case tokTypes._throw:
- if (!this._stmt2FastPath) {
- return base.parseStatement.call(this, context, topLevel, exports);
- }
- return this._parseThrowStatementAt(this.start);
- case tokTypes._try:
- if (!this._stmt2FastPath) {
- return base.parseStatement.call(this, context, topLevel, exports);
- }
- return this._parseTryStatementAt(this.start);
- case tokTypes._while:
- if (!this._stmt2FastPath) {
- return base.parseStatement.call(this, context, topLevel, exports);
- }
- return this._parseWhileStatementAt(this.start);
- case tokTypes._debugger:
- case tokTypes._do:
- case tokTypes._class:
- case tokTypes._with:
- case tokTypes.semi:
- case tokTypes._export:
- case tokTypes._import:
- return base.parseStatement.call(this, context, topLevel, exports);
- default: {
- if (startType === tokTypes.name) {
- const value = this.value;
- if (value === "let") {
- if (this.isLet(context)) {
- // mirrors acorn's `context && kind !== "var"` rejection
- if (context) this.unexpected();
- return this._parseVarStatementAt(this.start, "let");
- }
- // `let` as a plain identifier: expression/label tail below
- } else if (
- value === "async" ||
- value === "using" ||
- value === "await"
- ) {
- // async functions and using declarations keep acorn's
- // lookahead-heavy classification
- return base.parseStatement.call(this, context, topLevel, exports);
- }
- }
- // unambiguous expression statement, with acorn's label tail
- const start = this.start;
- const maybeName = this.value;
- const expr = this.parseExpression();
- if (
- startType === tokTypes.name &&
- expr.type === "Identifier" &&
- this.eat(tokTypes.colon)
- ) {
- // labels are rare enough to pay for the started node
- return this.parseLabeledStatement(
- this.startNodeAt(start),
- /** @type {string} */ (maybeName),
- /** @type {Identifier} */ (expr),
- context
- );
- }
- return this._parseExpressionStatementAt(start, expr);
- }
- }
- }
- /**
- * Owned `parseVarStatement`: the passed started node is filled by
- * `parseVar` as acorn expects, then the finished statement lands on
- * `VariableDeclarationNode`'s single shape. Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {Node} node started statement node from `parseStatement`
- * @param {string} kind declaration kind
- * @param {boolean=} allowMissingInitializer whether `const x;` is allowed
- * @returns {Node} variable declaration
- * @this {ParserInternals}
- */
- parseVarStatement(node, kind, allowMissingInitializer) {
- if (!this._lazy) {
- return base.parseVarStatement.call(
- this,
- node,
- kind,
- allowMissingInitializer
- );
- }
- return this._parseVarStatementAt(node.start, kind, allowMissingInitializer);
- }
- /**
- * Statement-position `var`/`let`/`const` without a started node: the
- * declaration lands directly on `VariableDeclarationNode`'s single shape.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @param {string} kind declaration kind
- * @param {boolean=} allowMissingInitializer whether `const x;` is allowed
- * @returns {Node} variable declaration
- * @this {ParserInternals}
- */
- _parseVarStatementAt(start, kind, allowMissingInitializer) {
- this.next();
- const scratch = this._acquireScratch();
- const count = this._parseVarInto(
- scratch,
- false,
- kind,
- allowMissingInitializer
- );
- const declarations = this._releaseScratch(scratch, count);
- this.semicolon();
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new VariableDeclarationNode(start, this.lastTokEnd, declarations, kind)
- )
- );
- }
- /**
- * Owned `parseVar`, an exact-semantics copy of acorn 8's that builds each
- * declarator fully-formed on `VariableDeclaratorNode`'s single shape. The
- * passed node keeps receiving `declarations`/`kind` because
- * `parseForStatement` finishes it itself. Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {Node} node declaration node to fill
- * @param {boolean} isFor whether parsing a `for` head
- * @param {string} kind declaration kind
- * @param {boolean=} allowMissingInitializer whether `const x;` is allowed
- * @returns {Node} the filled node
- * @this {ParserInternals}
- */
- parseVar(node, isFor, kind, allowMissingInitializer) {
- if (!this._lazy) {
- return base.parseVar.call(
- this,
- node,
- isFor,
- kind,
- allowMissingInitializer
- );
- }
- const target =
- /** @type {Node & { declarations: Node[] | null, kind: string }} */ (
- node
- );
- // null placeholder keeps acorn's declarations-then-kind key order while
- // the list is built in scratch (the node is a local in every caller
- // until this returns, so the placeholder is unobservable)
- target.declarations = null;
- target.kind = kind;
- const scratch = this._acquireScratch();
- const count = this._parseVarInto(
- scratch,
- isFor,
- kind,
- allowMissingInitializer
- );
- target.declarations = this._releaseScratch(scratch, count);
- return node;
- }
- /**
- * The declarator loop of the owned `parseVar`, shared with the owned
- * `parseStatement`'s node-free statement path.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {Node[]} declarations scratch array for the declarators
- * @param {boolean} isFor whether parsing a `for` head
- * @param {string} kind declaration kind
- * @param {boolean=} allowMissingInitializer whether `const x;` is allowed
- * @returns {number} number of declarators written
- * @this {ParserInternals}
- */
- _parseVarInto(declarations, isFor, kind, allowMissingInitializer) {
- const ecmaVersion = this._ecmaVersion;
- const usingKind = kind === "using" || kind === "await using";
- let count = 0;
- for (;;) {
- const declStart = this.start;
- const id = usingKind ? this.parseIdent() : this.parseBindingAtom();
- this.checkLValPattern(
- id,
- kind === "var" ? BIND_VAR : BIND_LEXICAL,
- false
- );
- /** @type {Expression | null} */
- let init = null;
- if (this.eat(tokTypes.eq)) {
- init = this.parseMaybeAssign(isFor);
- } else if (
- !allowMissingInitializer &&
- kind === "const" &&
- !(
- this.type === tokTypes._in ||
- (ecmaVersion >= 6 && this.isContextual("of"))
- )
- ) {
- this.unexpected();
- } else if (
- !allowMissingInitializer &&
- usingKind &&
- ecmaVersion >= 17 &&
- this.type !== tokTypes._in &&
- !this.isContextual("of")
- ) {
- this.raise(
- this.lastTokEnd,
- `Missing initializer in ${kind} declaration`
- );
- } else if (
- !allowMissingInitializer &&
- id.type !== "Identifier" &&
- !(isFor && (this.type === tokTypes._in || this.isContextual("of")))
- ) {
- this.raise(
- this.lastTokEnd,
- "Complex binding patterns require an initialization value"
- );
- }
- declarations[count++] = /** @type {Node} */ (
- /** @type {unknown} */ (
- new VariableDeclaratorNode(declStart, this.lastTokEnd, id, init)
- )
- );
- if (!this.eat(tokTypes.comma)) break;
- }
- return count;
- }
- /**
- * Owned `parseExpressionStatement`: the statement lands on
- * `ExpressionStatementNode`'s single shape (the passed started node is
- * discarded, matching acorn's observable output). Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {Node} node started statement node from `parseStatement`
- * @param {Expression} expr the parsed expression
- * @returns {Node} expression statement
- * @this {ParserInternals}
- */
- parseExpressionStatement(node, expr) {
- if (!this._lazy) {
- return base.parseExpressionStatement.call(this, node, expr);
- }
- return this._parseExpressionStatementAt(node.start, expr);
- }
- /**
- * `parseExpressionStatement` without a started node.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @param {Expression} expr the parsed expression
- * @returns {Node} expression statement
- * @this {ParserInternals}
- */
- _parseExpressionStatementAt(start, expr) {
- this.semicolon();
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new ExpressionStatementNode(start, this.lastTokEnd, expr)
- )
- );
- }
- /**
- * Owned `parseBlock`, an exact-semantics copy of acorn 8's landing on
- * `BlockStatementNode`'s single shape. Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {boolean=} createNewLexicalScope whether the block opens a scope
- * @param {Node=} node started statement node, when called from `parseStatement`
- * @param {boolean=} exitStrict whether to restore sloppy mode at the end
- * @returns {Node} block statement
- * @this {ParserInternals}
- */
- parseBlock(createNewLexicalScope, node, exitStrict) {
- if (!this._lazy) {
- return base.parseBlock.call(
- this,
- createNewLexicalScope,
- node,
- exitStrict
- );
- }
- if (createNewLexicalScope === undefined) createNewLexicalScope = true;
- const start = this.start;
- const scratch = this._acquireScratch();
- let count = 0;
- this.expect(tokTypes.braceL);
- if (createNewLexicalScope) this.enterScope(0);
- while (this.type !== tokTypes.braceR) {
- scratch[count++] = this.parseStatement(null);
- }
- if (exitStrict) this.strict = false;
- this.next();
- if (createNewLexicalScope) this.exitScope();
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new BlockStatementNode(
- start,
- this.lastTokEnd,
- this._releaseScratch(scratch, count)
- )
- )
- );
- }
- /**
- * Owned `parseIfStatement` landing on `IfStatementNode`'s single shape.
- * Non-lazy mode delegates to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {Node} node started statement node from `parseStatement`
- * @returns {Node} if statement
- * @this {ParserInternals}
- */
- parseIfStatement(node) {
- if (!this._lazy) return base.parseIfStatement.call(this, node);
- return this._parseIfStatementAt(node.start);
- }
- /**
- * `parseIfStatement` without a started node.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @returns {Node} if statement
- * @this {ParserInternals}
- */
- _parseIfStatementAt(start) {
- this.next();
- const test = this.parseParenExpression();
- // function declarations are allowed in branches outside strict mode
- const consequent = this.parseStatement("if");
- const alternate = this.eat(tokTypes._else)
- ? this.parseStatement("if")
- : null;
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new IfStatementNode(start, this.lastTokEnd, test, consequent, alternate)
- )
- );
- }
- /**
- * Owned `parseReturnStatement` landing on `ReturnStatementNode`'s single
- * shape. Non-lazy mode delegates to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {Node} node started statement node from `parseStatement`
- * @returns {Node} return statement
- * @this {ParserInternals}
- */
- parseReturnStatement(node) {
- if (!this._lazy) {
- if (!this.allowReturn) this._tryModuleFallback();
- return base.parseReturnStatement.call(this, node);
- }
- return this._parseReturnStatementAt(node.start);
- }
- /**
- * In `auto` source type a top-level `return` is script-only syntax. When the
- * strict module parse reaches it with no module construct seen yet, downgrade
- * to a sloppy script in place: the prefix parsed under stricter rules stays a
- * valid sloppy prefix, so the second full parse is avoided.
- * @returns {boolean} true when the parse was downgraded to script
- * @this {ParserInternals}
- */
- _tryModuleFallback() {
- if (
- !this._moduleFallback ||
- this._moduleSyntaxSeen ||
- !(this.currentVarScope().flags & SCOPE_TOP)
- ) {
- return false;
- }
- this.options.allowReturnOutsideFunction = true;
- // acorn stamps Program.sourceType from this at parseTopLevel's end
- this.options.sourceType = "script";
- this.strict = false;
- this.inModule = false;
- this._moduleFallback = false;
- return true;
- }
- /**
- * `parseReturnStatement` without a started node.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @returns {Node} return statement
- * @this {ParserInternals}
- */
- _parseReturnStatementAt(start) {
- if (!this.allowReturn && !this._tryModuleFallback()) {
- this.raise(this.start, "'return' outside of function");
- }
- this.next();
- /** @type {Expression | null} */
- let argument = null;
- // `return` takes an optional argument, so eagerly look for a semicolon
- if (!(this.eat(tokTypes.semi) || this.insertSemicolon())) {
- argument = this.parseExpression();
- this.semicolon();
- }
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new ReturnStatementNode(start, this.lastTokEnd, argument)
- )
- );
- }
- /**
- * `parseBreakContinueStatement` without a started node, landing on
- * `BreakContinueNode`'s single shape.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @param {string} keyword `break` or `continue`
- * @returns {Node} break or continue statement
- * @this {ParserInternals}
- */
- _parseBreakContinueStatementAt(start, keyword) {
- const isBreak = keyword === "break";
- this.next();
- /** @type {Identifier | null} */
- let label = null;
- if (this.eat(tokTypes.semi) || this.insertSemicolon()) {
- // no label
- } else if (this.type !== tokTypes.name) {
- this.unexpected();
- } else {
- label = this.parseIdent();
- this.semicolon();
- }
- // verify there is an actual destination to break or continue to
- const labels = this.labels;
- let i = 0;
- for (; i < labels.length; ++i) {
- const lab = labels[i];
- if (label === null || lab.name === label.name) {
- if (
- lab.kind !== null &&
- lab.kind !== undefined &&
- (isBreak || lab.kind === "loop")
- ) {
- break;
- }
- if (label && isBreak) break;
- }
- }
- if (i === labels.length) this.raise(start, `Unsyntactic ${keyword}`);
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new BreakContinueNode(
- start,
- this.lastTokEnd,
- isBreak ? "BreakStatement" : "ContinueStatement",
- label
- )
- )
- );
- }
- /**
- * `parseForStatement` without a started node (fast-path gate guarantees
- * `ecmaVersion >= 9`, folding acorn's version probes).
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @returns {Node} for/for-in/for-of statement
- * @this {ParserInternals}
- */
- _parseForStatementAt(start) {
- this.next();
- const awaitAt =
- this.canAwait && this.eatContextual("await") ? this.lastTokStart : -1;
- this.labels.push(LOOP_LABEL);
- this.enterScope(0);
- this.expect(tokTypes.parenL);
- if (this.type === tokTypes.semi) {
- if (awaitAt > -1) this.unexpected(awaitAt);
- return this._parseForAt(start, null);
- }
- const isLet = this.isLet();
- if (this.type === tokTypes._var || this.type === tokTypes._const || isLet) {
- const initStart = this.start;
- const kind = isLet ? "let" : /** @type {string} */ (this.value);
- this.next();
- const scratch = this._acquireScratch();
- const count = this._parseVarInto(scratch, true, kind);
- const declarations = this._releaseScratch(scratch, count);
- const init = /** @type {Node} */ (
- /** @type {unknown} */ (
- new VariableDeclarationNode(
- initStart,
- this.lastTokEnd,
- declarations,
- kind
- )
- )
- );
- return this._parseForAfterInitAt(start, init, awaitAt);
- }
- const startsWithLet = this.isContextual("let");
- let isForOf = false;
- const usingKind = this.isUsing(true)
- ? "using"
- : this.isAwaitUsing(true)
- ? "await using"
- : null;
- if (usingKind) {
- const initStart = this.start;
- this.next();
- if (usingKind === "await using") {
- if (!this.canAwait) {
- this.raise(
- this.start,
- "Await using cannot appear outside of async function"
- );
- }
- this.next();
- }
- const scratch = this._acquireScratch();
- const count = this._parseVarInto(scratch, true, usingKind);
- const declarations = this._releaseScratch(scratch, count);
- const init = /** @type {Node} */ (
- /** @type {unknown} */ (
- new VariableDeclarationNode(
- initStart,
- this.lastTokEnd,
- declarations,
- usingKind
- )
- )
- );
- return this._parseForAfterInitAt(start, init, awaitAt);
- }
- const containsEsc = this.containsEsc;
- const refDestructuringErrors = this._acquireDestructuringErrors();
- const initPos = this.start;
- const init =
- awaitAt > -1
- ? this.parseExprSubscripts(refDestructuringErrors, "await")
- : this.parseExpression(true, refDestructuringErrors);
- if (this.type === tokTypes._in || (isForOf = this.isContextual("of"))) {
- let isAwait = false;
- if (awaitAt > -1) {
- if (this.type === tokTypes._in) this.unexpected(awaitAt);
- isAwait = true;
- } else if (
- isForOf &&
- init.start === initPos &&
- !containsEsc &&
- init.type === "Identifier" &&
- /** @type {Identifier} */ (init).name === "async"
- ) {
- this.unexpected();
- }
- if (startsWithLet && isForOf) {
- this.raise(
- init.start,
- "The left-hand side of a for-of loop may not start with 'let'."
- );
- }
- this.toAssignable(init, false, refDestructuringErrors);
- this.checkLValPattern(init);
- this._releaseDestructuringErrors();
- return this._parseForInAt(start, isAwait, /** @type {Node} */ (init));
- }
- this.checkExpressionErrors(refDestructuringErrors, true);
- this._releaseDestructuringErrors();
- if (awaitAt > -1) this.unexpected(awaitAt);
- return this._parseForAt(start, /** @type {Node} */ (init));
- }
- /**
- * `parseForAfterInit` without a started node.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @param {Node} init parsed variable declaration
- * @param {number} awaitAt offset of a consumed `await`, or -1
- * @returns {Node} for/for-in/for-of statement
- * @this {ParserInternals}
- */
- _parseForAfterInitAt(start, init, awaitAt) {
- const target =
- /** @type {Node & { declarations: Node[], kind: string }} */ (init);
- if (
- (this.type === tokTypes._in || this.isContextual("of")) &&
- target.declarations.length === 1
- ) {
- if (this.type === tokTypes._in) {
- if (
- (target.kind === "using" || target.kind === "await using") &&
- !(
- /** @type {Node & { init: Node | null }} */ (target.declarations[0])
- .init
- )
- ) {
- this.raise(
- this.start,
- "Using declaration is not allowed in for-in loops"
- );
- }
- if (awaitAt > -1) this.unexpected(awaitAt);
- return this._parseForInAt(start, false, init);
- }
- return this._parseForInAt(start, awaitAt > -1, init);
- }
- if (awaitAt > -1) this.unexpected(awaitAt);
- return this._parseForAt(start, init);
- }
- /**
- * `parseFor` without a started node, landing on `ForStatementNode`'s
- * single shape.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @param {Node | null} init init clause
- * @returns {Node} for statement
- * @this {ParserInternals}
- */
- _parseForAt(start, init) {
- this.expect(tokTypes.semi);
- const test = this.type === tokTypes.semi ? null : this.parseExpression();
- this.expect(tokTypes.semi);
- const update =
- this.type === tokTypes.parenR ? null : this.parseExpression();
- this.expect(tokTypes.parenR);
- const body = this.parseStatement("for");
- this.exitScope();
- this.labels.pop();
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new ForStatementNode(start, this.lastTokEnd, init, test, update, body)
- )
- );
- }
- /**
- * `parseForIn` without a started node, landing on `ForInStatementNode`/
- * `ForOfStatementNode`'s single shapes.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @param {boolean} isAwait whether this is `for await`
- * @param {Node} init loop target
- * @returns {Node} for-in or for-of statement
- * @this {ParserInternals}
- */
- _parseForInAt(start, isAwait, init) {
- const isForIn = this.type === tokTypes._in;
- this.next();
- if (
- init.type === "VariableDeclaration" &&
- /** @type {Node & { init: Node | null }} */ (
- /** @type {Node & { declarations: Node[] }} */ (init).declarations[0]
- ).init !== null &&
- (!isForIn ||
- this.strict ||
- /** @type {Node & { kind: string }} */ (init).kind !== "var" ||
- /** @type {Node & { id: Node }} */ (
- /** @type {Node & { declarations: Node[] }} */ (init).declarations[0]
- ).id.type !== "Identifier")
- ) {
- this.raise(
- init.start,
- `${
- isForIn ? "for-in" : "for-of"
- } loop variable declaration may not have an initializer`
- );
- }
- const right = isForIn ? this.parseExpression() : this.parseMaybeAssign();
- this.expect(tokTypes.parenR);
- const body = this.parseStatement("for");
- this.exitScope();
- this.labels.pop();
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- isForIn
- ? new ForInStatementNode(start, this.lastTokEnd, init, right, body)
- : new ForOfStatementNode(
- start,
- this.lastTokEnd,
- isAwait,
- init,
- right,
- body
- )
- )
- );
- }
- /**
- * `parseSwitchStatement` without a started node, landing on
- * `SwitchStatementNode`'s single shape with pre-shaped `SwitchCaseNode`s.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @returns {Node} switch statement
- * @this {ParserInternals}
- */
- _parseSwitchStatementAt(start) {
- this.next();
- const discriminant = this.parseParenExpression();
- /** @type {Node[]} */
- const cases = [];
- this.expect(tokTypes.braceL);
- this.labels.push(SWITCH_LABEL);
- this.enterScope(SCOPE_SWITCH);
- /** @type {SwitchCaseNode | undefined} */
- let cur;
- for (let sawDefault = false; this.type !== tokTypes.braceR;) {
- if (this.type === tokTypes._case || this.type === tokTypes._default) {
- const isCase = this.type === tokTypes._case;
- if (cur) this.finishNode(/** @type {Node} */ (cur), "SwitchCase");
- cur = new SwitchCaseNode(this.start);
- cases.push(/** @type {Node} */ (cur));
- cur.consequent = [];
- this.next();
- if (isCase) {
- cur.test = this.parseExpression();
- } else {
- if (sawDefault) {
- this.raiseRecoverable(
- this.lastTokStart,
- "Multiple default clauses"
- );
- }
- sawDefault = true;
- cur.test = null;
- }
- this.expect(tokTypes.colon);
- } else {
- if (!cur) this.unexpected();
- /** @type {Node[]} */ (
- /** @type {SwitchCaseNode} */ (cur).consequent
- ).push(this.parseStatement(null));
- }
- }
- this.exitScope();
- if (cur) this.finishNode(/** @type {Node} */ (cur), "SwitchCase");
- this.next(); // closing brace
- this.labels.pop();
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new SwitchStatementNode(start, this.lastTokEnd, discriminant, cases)
- )
- );
- }
- /**
- * `parseThrowStatement` without a started node; the illegal-newline probe
- * runs on the tokenizer's newline flag instead of acorn's gap slice+regexp.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @returns {Node} throw statement
- * @this {ParserInternals}
- */
- _parseThrowStatementAt(start) {
- this.next();
- if (this._gapHasNewline()) {
- this.raise(this.lastTokEnd, "Illegal newline after throw");
- }
- const argument = this.parseExpression();
- this.semicolon();
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new ThrowStatementNode(start, this.lastTokEnd, argument)
- )
- );
- }
- /**
- * `parseTryStatement` without a started node, landing on
- * `TryStatementNode`/`CatchClauseNode`'s single shapes.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @returns {Node} try statement
- * @this {ParserInternals}
- */
- _parseTryStatementAt(start) {
- this.next();
- const block = this.parseBlock();
- /** @type {Node | null} */
- let handler = null;
- if (this.type === tokTypes._catch) {
- const clauseStart = this.start;
- this.next();
- /** @type {Node | null} */
- let param;
- if (this.eat(tokTypes.parenL)) {
- param = this.parseCatchClauseParam();
- } else {
- if (this._ecmaVersion < 10) this.unexpected();
- param = null;
- this.enterScope(0);
- }
- const body = this.parseBlock(false);
- this.exitScope();
- handler = /** @type {Node} */ (
- /** @type {unknown} */ (
- new CatchClauseNode(clauseStart, this.lastTokEnd, param, body)
- )
- );
- }
- const finalizer = this.eat(tokTypes._finally) ? this.parseBlock() : null;
- if (!handler && !finalizer) {
- this.raise(start, "Missing catch or finally clause");
- }
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new TryStatementNode(start, this.lastTokEnd, block, handler, finalizer)
- )
- );
- }
- /**
- * `parseWhileStatement` without a started node, landing on
- * `WhileStatementNode`'s single shape.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start statement start offset
- * @returns {Node} while statement
- * @this {ParserInternals}
- */
- _parseWhileStatementAt(start) {
- this.next();
- const test = this.parseParenExpression();
- this.labels.push(LOOP_LABEL);
- const body = this.parseStatement("while");
- this.labels.pop();
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new WhileStatementNode(start, this.lastTokEnd, test, body)
- )
- );
- }
- /**
- * Owned `parseSpread` landing on `RestSpreadNode`'s single shape.
- * Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/lval.js
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors destructuring errors to fill
- * @returns {Node} spread element
- * @this {ParserInternals}
- */
- parseSpread(refDestructuringErrors) {
- if (!this._lazy) {
- return base.parseSpread.call(this, refDestructuringErrors);
- }
- const start = this.start;
- this.next();
- const argument = this.parseMaybeAssign(false, refDestructuringErrors);
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new RestSpreadNode(start, this.lastTokEnd, "SpreadElement", argument)
- )
- );
- }
- /**
- * Owned `parseMaybeConditional` landing on
- * `ConditionalExpressionNode`'s single shape. Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {boolean | string=} forInit for-init context flag
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors destructuring errors to fill
- * @returns {Expression} expression node
- * @this {ParserInternals}
- */
- parseMaybeConditional(forInit, refDestructuringErrors) {
- if (!this._lazy) {
- return base.parseMaybeConditional.call(
- this,
- forInit,
- refDestructuringErrors
- );
- }
- const startPos = this.start;
- const expr = this.parseExprOps(forInit, refDestructuringErrors);
- if (this.checkExpressionErrors(refDestructuringErrors)) return expr;
- const isArrowAtStart = this._arrowFastPath
- ? expr === this._lastArrow && expr.start === startPos
- : expr.type === "ArrowFunctionExpression" && expr.start === startPos;
- if (!isArrowAtStart && this.eat(tokTypes.question)) {
- const consequent = this.parseMaybeAssign();
- this.expect(tokTypes.colon);
- const alternate = this.parseMaybeAssign(forInit);
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- new ConditionalExpressionNode(
- startPos,
- this.lastTokEnd,
- expr,
- consequent,
- alternate
- )
- )
- );
- }
- return expr;
- }
- /**
- * acorn's `parseArrowExpression` plus `_lastArrow` tracking — arrows are
- * created and returned only here, so the expression-spine probes can test
- * node identity instead of the megamorphic `.type` load. A plugin fabricating
- * arrow nodes in an overridden `parseExprAtom` bypasses the gate (same
- * acceptance class as `_stmtFastPath`).
- * @param {Node} node started node
- * @param {Node[]} params parameter expressions
- * @param {boolean} isAsync async arrow
- * @param {boolean | string=} forInit for-init context flag
- * @returns {Expression} arrow function expression
- * @this {ParserInternals}
- */
- parseArrowExpression(node, params, isAsync, forInit) {
- if (!this._funcFastPath) {
- const result = base.parseArrowExpression.call(
- this,
- node,
- params,
- isAsync,
- forInit
- );
- this._lastArrow = result;
- return result;
- }
- // acorn's body with the started node replaced by the pre-shaped
- // FunctionNode (initFunction's writes are its constructor defaults)
- const functionNode = /** @type {Node} */ (
- /** @type {unknown} */ (new FunctionNode(node.start))
- );
- const oldYieldPos = this.yieldPos;
- const oldAwaitPos = this.awaitPos;
- const oldAwaitIdentPos = this.awaitIdentPos;
- this.enterScope(SCOPE_FUNCTION | (isAsync ? SCOPE_ASYNC : 0) | SCOPE_ARROW);
- /** @type {Node & { async: boolean, params?: Node[] }} */ (
- functionNode
- ).async = Boolean(isAsync);
- this.yieldPos = 0;
- this.awaitPos = 0;
- this.awaitIdentPos = 0;
- /** @type {Node & { params?: Node[] }} */ (functionNode).params =
- /** @type {Node[]} */ (this.toAssignableList(params, true));
- this.parseFunctionBody(functionNode, true, false, forInit);
- this.yieldPos = oldYieldPos;
- this.awaitPos = oldAwaitPos;
- this.awaitIdentPos = oldAwaitIdentPos;
- const result = /** @type {Expression} */ (
- /** @type {unknown} */ (
- this.finishNode(functionNode, "ArrowFunctionExpression")
- )
- );
- this._lastArrow = result;
- return result;
- }
- /**
- * Owned `parseFunction`, an exact-semantics copy of acorn 8's landing on
- * `FunctionNode`'s pre-declared shape (the passed started node is discarded,
- * matching acorn's observable output; `initFunction`'s writes are the
- * constructor defaults). Non-lazy mode and plugin overrides delegate.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {Node} node started node from the caller
- * @param {number} statement FUNC_* statement bit flags
- * @param {boolean=} allowExpressionBody whether an expression body is allowed
- * @param {boolean=} isAsync whether the function is async
- * @param {boolean | string=} forInit for-init context flag
- * @returns {Expression} function declaration or expression
- * @this {ParserInternals}
- */
- parseFunction(node, statement, allowExpressionBody, isAsync, forInit) {
- if (!this._funcFastPath) {
- return base.parseFunction.call(
- this,
- node,
- statement,
- allowExpressionBody,
- isAsync,
- forInit
- );
- }
- return this._parseFunctionAt(
- node.start,
- statement,
- allowExpressionBody === true,
- isAsync === true,
- forInit
- );
- }
- /**
- * `parseFunction` without a started node (fast-path gate guarantees
- * `ecmaVersion >= 9`, folding acorn's version probes).
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {number} start function start offset
- * @param {number} statement FUNC_* statement bit flags
- * @param {boolean} allowExpressionBody whether an expression body is allowed
- * @param {boolean} isAsync whether the function is async
- * @param {boolean | string=} forInit for-init context flag
- * @returns {Expression} function declaration or expression
- * @this {ParserInternals}
- */
- _parseFunctionAt(start, statement, allowExpressionBody, isAsync, forInit) {
- const node =
- /** @type {Node & { id: Identifier | null, generator: boolean, async: boolean }} */ (
- /** @type {unknown} */ (new FunctionNode(start))
- );
- if (this.type === tokTypes.star && statement & FUNC_HANGING_STATEMENT) {
- this.unexpected();
- }
- node.generator = this.eat(tokTypes.star);
- node.async = isAsync;
- if (statement & FUNC_STATEMENT) {
- node.id =
- statement & FUNC_NULLABLE_ID && this.type !== tokTypes.name
- ? null
- : this.parseIdent();
- if (node.id && !(statement & FUNC_HANGING_STATEMENT)) {
- // Annex B: sloppy-mode function declarations bind like vars
- this.checkLValSimple(
- node.id,
- this.strict || node.generator || node.async
- ? this.treatFunctionsAsVar
- ? BIND_VAR
- : BIND_LEXICAL
- : /* BIND_FUNCTION */ 3
- );
- }
- }
- const oldYieldPos = this.yieldPos;
- const oldAwaitPos = this.awaitPos;
- const oldAwaitIdentPos = this.awaitIdentPos;
- this.yieldPos = 0;
- this.awaitPos = 0;
- this.awaitIdentPos = 0;
- this.enterScope(
- SCOPE_FUNCTION |
- (node.async ? SCOPE_ASYNC : 0) |
- (node.generator ? SCOPE_GENERATOR : 0)
- );
- if (!(statement & FUNC_STATEMENT)) {
- node.id = this.type === tokTypes.name ? this.parseIdent() : null;
- }
- this.parseFunctionParams(node);
- this.parseFunctionBody(node, allowExpressionBody, false, forInit);
- this.yieldPos = oldYieldPos;
- this.awaitPos = oldAwaitPos;
- this.awaitIdentPos = oldAwaitIdentPos;
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- this.finishNode(
- node,
- statement & FUNC_STATEMENT
- ? "FunctionDeclaration"
- : "FunctionExpression"
- )
- )
- );
- }
- /**
- * Owned `parseFunctionBody`, an exact-semantics copy of acorn 8's with
- * `isSimpleParamList` inlined and computed once (acorn walks the params
- * twice) — the fast-path gate pins it to acorn's own implementation.
- * Non-lazy mode and plugin overrides delegate.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {Node} node function node being filled
- * @param {boolean} isArrowFunction whether this is an arrow function
- * @param {boolean} isMethod whether this is an object/class method
- * @param {boolean | string=} forInit for-init context flag
- * @returns {void}
- * @this {ParserInternals}
- */
- parseFunctionBody(node, isArrowFunction, isMethod, forInit) {
- if (!this._funcFastPath) {
- return base.parseFunctionBody.call(
- this,
- node,
- isArrowFunction,
- isMethod,
- forInit
- );
- }
- const target =
- /** @type {Node & { id: Identifier | null, params: Node[], body: Node, expression: boolean }} */ (
- node
- );
- const isExpression = isArrowFunction && this.type !== tokTypes.braceL;
- const oldStrict = this.strict;
- let useStrict = false;
- if (isExpression) {
- target.body = this.parseMaybeAssign(forInit);
- target.expression = true;
- this.checkParams(node, false);
- } else {
- // acorn's isSimpleParamList, inlined and shared by both its call sites
- const params = target.params;
- let simpleParams = true;
- for (let i = 0; i < params.length; i++) {
- if (params[i].type !== "Identifier") {
- simpleParams = false;
- break;
- }
- }
- if (!oldStrict || !simpleParams) {
- useStrict = this.strictDirective(this.end);
- if (useStrict && !simpleParams) {
- this.raiseRecoverable(
- node.start,
- "Illegal 'use strict' directive in function with non-simple parameter list"
- );
- }
- }
- const oldLabels = this.labels;
- this.labels = [];
- if (useStrict) this.strict = true;
- this.checkParams(
- node,
- !oldStrict &&
- !useStrict &&
- !isArrowFunction &&
- !isMethod &&
- simpleParams
- );
- if (this.strict && target.id) {
- this.checkLValSimple(target.id, BIND_OUTSIDE);
- }
- target.body = this.parseBlock(false, undefined, useStrict && !oldStrict);
- target.expression = false;
- this.adaptDirectivePrologue(
- /** @type {Node & { body: Node[] }} */ (target.body).body
- );
- this.labels = oldLabels;
- }
- this.exitScope();
- }
- /**
- * Owned `parseBindingAtom`: acorn's exact dispatch minus the per-call
- * normalized-options read. Non-lazy mode and pre-ES6 delegate.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/lval.js
- * @returns {Node} binding atom (identifier or pattern)
- * @this {ParserInternals}
- */
- parseBindingAtom() {
- if (!this._lazy || this._ecmaVersion < 6) {
- return base.parseBindingAtom.call(this);
- }
- const type = this.type;
- if (type === tokTypes.bracketL) {
- const node = /** @type {Node & { elements?: Node[] }} */ (
- this.startNode()
- );
- this.next();
- node.elements = this.parseBindingList(tokTypes.bracketR, true, true);
- return this.finishNode(node, "ArrayPattern");
- }
- if (type === tokTypes.braceL) return this.parseObj(true);
- return this.parseIdent();
- }
- /**
- * Owned `parseExprSubscripts`, an exact copy of acorn 8's with the arrow
- * probe served by `_lastArrow` identity. Delegates when the fast path is off.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors destructuring errors to fill
- * @param {boolean | string=} forInit for-init context flag
- * @returns {Expression} expression node
- * @this {ParserInternals}
- */
- parseExprSubscripts(refDestructuringErrors, forInit) {
- if (!this._arrowFastPath) {
- return base.parseExprSubscripts.call(
- this,
- refDestructuringErrors,
- forInit
- );
- }
- const startPos = this.start;
- const startLoc = this.startLoc;
- const expr = this.parseExprAtom(refDestructuringErrors, forInit);
- if (
- expr === this._lastArrow &&
- this.input.slice(this.lastTokStart, this.lastTokEnd) !== ")"
- ) {
- return expr;
- }
- const result = this.parseSubscripts(
- expr,
- startPos,
- startLoc,
- false,
- forInit
- );
- if (refDestructuringErrors && result.type === "MemberExpression") {
- if (refDestructuringErrors.parenthesizedAssign >= result.start) {
- refDestructuringErrors.parenthesizedAssign = -1;
- }
- if (refDestructuringErrors.parenthesizedBind >= result.start) {
- refDestructuringErrors.parenthesizedBind = -1;
- }
- if (refDestructuringErrors.trailingComma >= result.start) {
- refDestructuringErrors.trailingComma = -1;
- }
- }
- return result;
- }
- /**
- * Owned `parseExprList`, an exact-semantics copy of acorn 8's built in a
- * scratch array and materialized exactly sized. Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {TokenType} close closing token type
- * @param {boolean} allowTrailingComma whether a trailing comma is allowed
- * @param {boolean} allowEmpty whether holes are allowed
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors destructuring errors to fill
- * @returns {Expression[]} parsed elements (`null` for holes)
- * @this {ParserInternals}
- */
- parseExprList(close, allowTrailingComma, allowEmpty, refDestructuringErrors) {
- if (!this._lazy) {
- return base.parseExprList.call(
- this,
- close,
- allowTrailingComma,
- allowEmpty,
- refDestructuringErrors
- );
- }
- const scratch = this._acquireScratch();
- let count = 0;
- let first = true;
- while (!this.eat(close)) {
- if (!first) {
- this.expect(tokTypes.comma);
- if (allowTrailingComma && this.afterTrailingComma(close)) break;
- } else {
- first = false;
- }
- let elt;
- if (allowEmpty && this.type === tokTypes.comma) {
- elt = null;
- } else if (this.type === tokTypes.ellipsis) {
- elt = this.parseSpread(refDestructuringErrors);
- if (
- refDestructuringErrors &&
- this.type === tokTypes.comma &&
- refDestructuringErrors.trailingComma < 0
- ) {
- refDestructuringErrors.trailingComma = this.start;
- }
- } else {
- elt = this.parseMaybeAssign(false, refDestructuringErrors);
- }
- scratch[count++] = elt;
- }
- return this._releaseScratch(scratch, count);
- }
- /**
- * Owned `parseBindingList`, an exact-semantics copy of acorn 8's built in a
- * scratch array and materialized exactly sized. Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/lval.js
- * @param {TokenType} close closing token type
- * @param {boolean} allowEmpty whether holes are allowed
- * @param {boolean} allowTrailingComma whether a trailing comma is allowed
- * @param {boolean=} allowModifiers passed through to `parseAssignableListItem`
- * @returns {Node[]} parsed bindings (`null` for holes)
- * @this {ParserInternals}
- */
- parseBindingList(close, allowEmpty, allowTrailingComma, allowModifiers) {
- if (!this._lazy) {
- return base.parseBindingList.call(
- this,
- close,
- allowEmpty,
- allowTrailingComma,
- allowModifiers
- );
- }
- const scratch = this._acquireScratch();
- let count = 0;
- let first = true;
- while (!this.eat(close)) {
- if (first) first = false;
- else this.expect(tokTypes.comma);
- if (allowEmpty && this.type === tokTypes.comma) {
- scratch[count++] = null;
- } else if (allowTrailingComma && this.afterTrailingComma(close)) {
- break;
- } else if (this.type === tokTypes.ellipsis) {
- const rest = this.parseRestBinding();
- this.parseBindingListItem(rest);
- scratch[count++] = rest;
- if (this.type === tokTypes.comma) {
- this.raiseRecoverable(
- this.start,
- "Comma is not permitted after the rest element"
- );
- }
- this.expect(close);
- break;
- } else {
- scratch[count++] = this.parseAssignableListItem(allowModifiers);
- }
- }
- return this._releaseScratch(scratch, count);
- }
- /**
- * Owned `parseParenAndDistinguishExpression`, an exact-semantics copy of
- * acorn 8's with the per-paren `DestructuringErrors` record pooled and the
- * expression list built in a scratch array. Non-lazy mode delegates (the
- * copy assumes ES6+, which `_lazy` guarantees via `ecmaVersion: "latest"`).
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {boolean} canBeArrow whether this may be an arrow head
- * @param {boolean | string} forInit for-init context flag
- * @returns {Expression} expression node
- * @this {ParserInternals}
- */
- parseParenAndDistinguishExpression(canBeArrow, forInit) {
- if (!this._lazy || this._ecmaVersion < 6) {
- return base.parseParenAndDistinguishExpression.call(
- this,
- canBeArrow,
- forInit
- );
- }
- const startPos = this.start;
- const startLoc = this.startLoc;
- const allowTrailingComma = this._ecmaVersion >= 8;
- this.next();
- const innerStartPos = this.start;
- const innerStartLoc = this.startLoc;
- const scratch = this._acquireScratch();
- let count = 0;
- let first = true;
- let lastIsComma = false;
- const refDestructuringErrors = this._acquireDestructuringErrors();
- const oldYieldPos = this.yieldPos;
- const oldAwaitPos = this.awaitPos;
- let spreadStart;
- this.yieldPos = 0;
- this.awaitPos = 0;
- // Do not save awaitIdentPos to allow checking awaits nested in parameters
- while (this.type !== tokTypes.parenR) {
- if (first) first = false;
- else this.expect(tokTypes.comma);
- if (
- allowTrailingComma &&
- this.afterTrailingComma(tokTypes.parenR, true)
- ) {
- lastIsComma = true;
- break;
- } else if (this.type === tokTypes.ellipsis) {
- spreadStart = this.start;
- scratch[count++] = this.parseParenItem(this.parseRestBinding());
- if (this.type === tokTypes.comma) {
- this.raiseRecoverable(
- this.start,
- "Comma is not permitted after the rest element"
- );
- }
- break;
- } else {
- scratch[count++] = this.parseMaybeAssign(
- false,
- refDestructuringErrors,
- this.parseParenItem
- );
- }
- }
- const exprList = this._releaseScratch(scratch, count);
- const innerEndPos = this.lastTokEnd;
- this.expect(tokTypes.parenR);
- if (
- canBeArrow &&
- this.shouldParseArrow(exprList) &&
- this.eat(tokTypes.arrow)
- ) {
- this.checkPatternErrors(refDestructuringErrors, false);
- this.checkYieldAwaitInDefaultParams();
- // last read of the pooled record was above, so the arrow body parse
- // below can already reuse its pool slot
- this._releaseDestructuringErrors();
- this.yieldPos = oldYieldPos;
- this.awaitPos = oldAwaitPos;
- return this.parseParenArrowList(startPos, startLoc, exprList, forInit);
- }
- if (exprList.length === 0 || lastIsComma) {
- this.unexpected(this.lastTokStart);
- }
- if (spreadStart) this.unexpected(spreadStart);
- this.checkExpressionErrors(refDestructuringErrors, true);
- this._releaseDestructuringErrors();
- this.yieldPos = oldYieldPos || this.yieldPos;
- this.awaitPos = oldAwaitPos || this.awaitPos;
- /** @type {Expression} */
- let val;
- if (exprList.length > 1) {
- const seq = /** @type {Node & { expressions?: Expression[] }} */ (
- this.startNodeAt(innerStartPos, innerStartLoc)
- );
- seq.expressions = exprList;
- this.finishNodeAt(
- /** @type {Node} */ (seq),
- "SequenceExpression",
- innerEndPos,
- undefined
- );
- val = /** @type {Expression} */ (/** @type {unknown} */ (seq));
- } else {
- val = exprList[0];
- }
- if (this.options.preserveParens) {
- const par = /** @type {Node & { expression?: Expression }} */ (
- this.startNodeAt(startPos, startLoc)
- );
- par.expression = val;
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- this.finishNode(/** @type {Node} */ (par), "ParenthesizedExpression")
- )
- );
- }
- return val;
- }
- /**
- * Owned `afterTrailingComma`, acorn's verbatim (mode-independent), so the
- * owned list parsers do not bounce through the dist for each element.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/parseutil.js
- * @param {TokenType} tokType closing token type
- * @param {boolean=} notNext whether to leave the closing token unconsumed
- * @returns {boolean} true when the list ended on a trailing comma
- * @this {ParserInternals}
- */
- afterTrailingComma(tokType, notNext) {
- if (this.type === tokType) {
- const onTrailingComma =
- /** @type {Options & { onTrailingComma?: (pos: number, loc?: Position) => void }} */
- (this.options).onTrailingComma;
- if (onTrailingComma) {
- onTrailingComma(this.lastTokStart, this.lastTokStartLoc);
- }
- if (!notNext) this.next();
- return true;
- }
- return false;
- }
- /**
- * Owned `checkExpressionErrors`, acorn's verbatim (mode-independent): it
- * runs several times per expression from the owned spine.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/parseutil.js
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors record to inspect
- * @param {boolean=} andThrow whether to throw on error
- * @returns {boolean} whether an error position was set
- * @this {ParserInternals}
- */
- checkExpressionErrors(refDestructuringErrors, andThrow) {
- if (!refDestructuringErrors) return false;
- const shorthandAssign = refDestructuringErrors.shorthandAssign;
- const doubleProto = refDestructuringErrors.doubleProto;
- if (!andThrow) return shorthandAssign >= 0 || doubleProto >= 0;
- if (shorthandAssign >= 0) {
- this.raise(
- shorthandAssign,
- "Shorthand property assignments are valid only in destructuring patterns"
- );
- }
- if (doubleProto >= 0) {
- this.raiseRecoverable(doubleProto, "Redefinition of __proto__ property");
- }
- return false;
- }
- /**
- * Owned `parseExpression`: acorn wraps every call in `catchStackOverflow`,
- * allocating a fresh closure per expression; inline the try/catch instead
- * (same overflow-message translation, no closure). Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {boolean | string=} forInit for-init context flag
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors destructuring errors to fill
- * @returns {Expression} expression node
- * @this {ParserInternals}
- */
- parseExpression(forInit, refDestructuringErrors) {
- if (!this._lazy) {
- return base.parseExpression.call(this, forInit, refDestructuringErrors);
- }
- try {
- const startPos = this.start;
- const startLoc = this.startLoc;
- const expr = this.parseMaybeAssign(forInit, refDestructuringErrors);
- if (this.type === tokTypes.comma) {
- const node =
- /** @type {Node & { expressions?: Expression[] }} */
- (this.startNodeAt(startPos, startLoc));
- node.expressions = [expr];
- while (this.eat(tokTypes.comma)) {
- node.expressions.push(
- this.parseMaybeAssign(forInit, refDestructuringErrors)
- );
- }
- return /** @type {Expression} */ (
- /** @type {unknown} */ (this.finishNode(node, "SequenceExpression"))
- );
- }
- return expr;
- } catch (err) {
- // acorn's catchStackOverflow: translate an engine stack-overflow error
- if (
- err instanceof Error &&
- (/\bstack\b.*\b(exceeded|overflow)\b/i.test(err.message) ||
- /\btoo much recursion\b/i.test(err.message))
- ) {
- this.raise(this.start, "Not enough stack space to parse input");
- }
- throw err;
- }
- }
- /**
- * Owned `parseNew`, an exact-semantics copy of acorn 8's: `NewExpression`
- * lands on `NewExpressionNode`'s single shape (zero-argument calls share
- * one empty array like acorn's `empty`); the rare `new.target` path keeps
- * the generic node. Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @returns {Expression} new expression or meta property
- * @this {ParserInternals}
- */
- parseNew() {
- if (!this._lazy) return base.parseNew.call(this);
- if (this.containsEsc) {
- this.raiseRecoverable(this.start, "Escape sequence in keyword new");
- }
- const nodeStart = this.start;
- this.next();
- if (this._ecmaVersion >= 6 && this.type === tokTypes.dot) {
- const node =
- /** @type {Node & { meta?: Node, property?: Identifier }} */
- (this.startNodeAt(nodeStart, undefined));
- const meta =
- /** @type {Node & { name?: string }} */
- (this.startNodeAt(nodeStart, undefined));
- meta.name = "new";
- node.meta = this.finishNode(/** @type {Node} */ (meta), "Identifier");
- this.next();
- const containsEsc = this.containsEsc;
- node.property = this.parseIdent(true);
- if (node.property.name !== "target") {
- this.raiseRecoverable(
- node.property.start,
- "The only valid meta property for new is 'new.target'"
- );
- }
- if (containsEsc) {
- this.raiseRecoverable(
- nodeStart,
- "'new.target' must not contain escaped characters"
- );
- }
- if (!this.allowNewDotTarget) {
- this.raiseRecoverable(
- nodeStart,
- "'new.target' can only be used in functions and class static block"
- );
- }
- return /** @type {Expression} */ (
- /** @type {unknown} */ (this.finishNode(node, "MetaProperty"))
- );
- }
- const startPos = this.start;
- const startLoc = this.startLoc;
- const callee = this.parseSubscripts(
- this.parseExprAtom(null, false, true),
- startPos,
- startLoc,
- true,
- false
- );
- if (/** @type {string} */ (callee.type) === "Super") {
- this.raiseRecoverable(startPos, "Invalid use of 'super'");
- }
- /** @type {Expression[]} */
- const args = this.eat(tokTypes.parenL)
- ? this.parseExprList(tokTypes.parenR, true, false)
- : EMPTY_NEW_ARGS;
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- new NewExpressionNode(nodeStart, this.lastTokEnd, callee, args)
- )
- );
- }
- /**
- * Owned `parseTemplateElement` landing on `TemplateElementNode`'s single
- * shape; matches acorn's CRLF normalization and invalid-escape handling.
- * Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {{ isTagged: boolean }} opts whether the template is tagged
- * @returns {Node} template element
- * @this {ParserInternals}
- */
- parseTemplateElement(opts) {
- if (!this._lazy) return base.parseTemplateElement.call(this, opts);
- const start = this.start;
- /** @type {{ raw: string, cooked: string | null }} */
- let value;
- if (this.type === tokTypes.invalidTemplate) {
- if (!opts.isTagged) {
- this.raiseRecoverable(
- this.start,
- "Bad escape sequence in untagged template literal"
- );
- }
- value = {
- raw: /** @type {string} */ (this.value).replace(/\r\n?/g, "\n"),
- cooked: null
- };
- } else {
- const cooked = /** @type {string} */ (this.value);
- // Every escape cooks strictly shorter and CRLF shortens, while a lone CR
- // cooks to the LF the raw normalization would produce — so an
- // equal-length cooked string IS the normalized raw. That covers every
- // fast-path chunk (no backslash, no CR): raw and cooked share one string.
- value = {
- raw:
- cooked.length === this.end - this.start
- ? cooked
- : this.input.slice(this.start, this.end).replace(/\r\n?/g, "\n"),
- cooked
- };
- }
- this.next();
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new TemplateElementNode(
- start,
- this.lastTokEnd,
- value,
- this.type === tokTypes.backQuote
- )
- )
- );
- }
- /**
- * Owned `parseTemplate` landing on `TemplateLiteralNode`'s single shape.
- * Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {{ isTagged?: boolean }=} opts whether the template is tagged
- * @returns {Node} template literal
- * @this {ParserInternals}
- */
- parseTemplate(opts) {
- if (!this._lazy) {
- return base.parseTemplate.call(
- this,
- /** @type {{ isTagged: boolean }} */ (opts)
- );
- }
- const isTagged = (opts !== undefined && opts.isTagged) === true;
- const start = this.start;
- this.next();
- const exprScratch = this._acquireScratch();
- const quasiScratch = this._acquireScratch();
- let exprCount = 0;
- let quasiCount = 0;
- // one options object for all chunks (only `isTagged` is ever read)
- const eltOpts = { isTagged };
- let curElt = /** @type {Node & { tail?: boolean }} */ (
- this.parseTemplateElement(eltOpts)
- );
- quasiScratch[quasiCount++] = curElt;
- while (!curElt.tail) {
- if (this.type === tokTypes.eof) {
- this.raise(this.pos, "Unterminated template literal");
- }
- this.expect(tokTypes.dollarBraceL);
- exprScratch[exprCount++] = this.parseExpression();
- this.expect(tokTypes.braceR);
- curElt = /** @type {Node & { tail?: boolean }} */ (
- this.parseTemplateElement(eltOpts)
- );
- quasiScratch[quasiCount++] = curElt;
- }
- this.next();
- const quasis = this._releaseScratch(quasiScratch, quasiCount);
- const expressions = this._releaseScratch(exprScratch, exprCount);
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new TemplateLiteralNode(start, this.lastTokEnd, expressions, quasis)
- )
- );
- }
- /**
- * Owned `parseMaybeUnary`, an exact-semantics copy of acorn 8's: prefix
- * unary/update and postfix update nodes are built fully-formed on
- * `UnaryNode`'s shared single shape. Non-lazy mode delegates to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {DestructuringErrorsShim | null} refDestructuringErrors destructuring errors to fill
- * @param {boolean} sawUnary whether a unary operator was already consumed
- * @param {boolean} incDec whether the caller was an update operator
- * @param {boolean | string=} forInit for-init context flag
- * @returns {Expression} expression node
- * @this {ParserInternals}
- */
- parseMaybeUnary(refDestructuringErrors, sawUnary, incDec, forInit) {
- if (!this._lazy) {
- return base.parseMaybeUnary.call(
- this,
- refDestructuringErrors,
- sawUnary,
- incDec,
- forInit
- );
- }
- const startPos = this.start;
- const startLoc = this.startLoc;
- /** @type {Expression} */
- let expr;
- if (this.isContextual("await") && this.canAwait) {
- expr = this.parseAwait(forInit);
- sawUnary = true;
- } else if (/** @type {TokenTypeInternal} */ (this.type).prefix) {
- const update = this.type === tokTypes.incDec;
- const operator = /** @type {string} */ (this.value);
- this.next();
- const argument = this.parseMaybeUnary(null, true, update, forInit);
- this.checkExpressionErrors(refDestructuringErrors, true);
- if (update) {
- this.checkLValSimple(argument);
- } else if (
- this.strict &&
- operator === "delete" &&
- isLocalVariableAccess(argument)
- ) {
- this.raiseRecoverable(
- startPos,
- "Deleting local variable in strict mode"
- );
- } else if (operator === "delete" && isPrivateFieldAccess(argument)) {
- this.raiseRecoverable(startPos, "Private fields can not be deleted");
- } else {
- sawUnary = true;
- }
- expr = /** @type {Expression} */ (
- /** @type {unknown} */ (
- new UnaryNode(
- startPos,
- this.lastTokEnd,
- update ? "UpdateExpression" : "UnaryExpression",
- operator,
- true,
- argument
- )
- )
- );
- } else if (!sawUnary && this.type === tokTypes.privateId) {
- if (
- (forInit || this.privateNameStack.length === 0) &&
- this.options.checkPrivateFields
- ) {
- this.unexpected();
- }
- expr = /** @type {Expression} */ (
- /** @type {unknown} */ (this.parsePrivateIdent())
- );
- // a private name is only valid as `#x in obj`
- if (this.type !== tokTypes._in) this.unexpected();
- } else {
- expr = this.parseExprSubscripts(refDestructuringErrors, forInit);
- if (this.checkExpressionErrors(refDestructuringErrors)) return expr;
- while (
- /** @type {TokenTypeInternal} */ (this.type).postfix &&
- !this.canInsertSemicolon()
- ) {
- const operator = /** @type {string} */ (this.value);
- this.checkLValSimple(expr);
- this.next();
- expr = /** @type {Expression} */ (
- /** @type {unknown} */ (
- new UnaryNode(
- startPos,
- this.lastTokEnd,
- "UpdateExpression",
- operator,
- false,
- expr
- )
- )
- );
- }
- }
- if (!incDec && this.eat(tokTypes.starstar)) {
- if (sawUnary) {
- this.unexpected(this.lastTokStart);
- } else {
- return this.buildBinary(
- startPos,
- startLoc,
- expr,
- this.parseMaybeUnary(null, false, false, forInit),
- "**",
- false
- );
- }
- }
- return expr;
- }
- /**
- * Collapses the commonest expression shape — a name/number/string/keyword
- * literal or `this` whose next source character can only begin a token that
- * cannot continue an expression — into the atom, skipping the seven-layer
- * `parseMaybeAssign` → `parseExprAtom` descent and its destructuring-error
- * bookkeeping (which is a net no-op for a bare atom).
- * @param {boolean | string=} forInit for-init context flag
- * @returns {Expression | null} the atom, or `null` when the shape does not apply
- * @this {ParserInternals}
- */
- _parseTrivialAtom(forInit) {
- const code = this.input.charCodeAt(this.end);
- if (code >= 128 || EXPRESSION_END_CHAR[code] !== 1) return null;
- const type = this.type;
- if (type === tokTypes.name) {
- // both steer the chain themselves (`yield x`, `await x`)
- const name = /** @type {string} */ (this.value);
- if (name === "yield" || name === "await") return null;
- this.potentialArrowAt = this.start;
- this.potentialArrowInForAwait = forInit === "await";
- return /** @type {Expression} */ (
- /** @type {unknown} */ (this.parseIdent(false))
- );
- }
- if (type === tokTypes.num || type === tokTypes.string) {
- return /** @type {Expression} */ (
- /** @type {unknown} */ (this.parseLiteral(this.value))
- );
- }
- if (type === tokTypes._this) {
- const node = new ThisNode(this.start, this.end);
- this.next();
- return /** @type {Expression} */ (/** @type {unknown} */ (node));
- }
- if (
- type === tokTypes._null ||
- type === tokTypes._true ||
- type === tokTypes._false
- ) {
- const node = new LiteralNode(
- this.start,
- this.end,
- type === tokTypes._null ? null : type === tokTypes._true,
- /** @type {string} */ (type.keyword)
- );
- this.next();
- return /** @type {Expression} */ (/** @type {unknown} */ (node));
- }
- return null;
- }
- /**
- * Owned `parseMaybeAssign`, an exact-semantics copy of acorn 8's: the
- * operator is captured before `next()` and the `AssignmentExpression` is
- * built fully-formed on `AssignmentNode`'s single shape after the
- * right-hand parse. Yield and non-lazy mode delegate to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {boolean | string=} forInit for-init context flag
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors destructuring errors to fill
- * @param {((this: unknown, left: Expression, startPos: number, startLoc?: Position) => Expression)=} afterLeftParse hook applied to the parsed left side
- * @returns {Expression} expression node
- * @this {ParserInternals}
- */
- parseMaybeAssign(forInit, refDestructuringErrors, afterLeftParse) {
- if (!this._lazy) {
- return base.parseMaybeAssign.call(
- this,
- forInit,
- refDestructuringErrors,
- afterLeftParse
- );
- }
- if (this._exprFastPath && afterLeftParse === undefined) {
- const atom = this._parseTrivialAtom(forInit);
- if (atom !== null) return atom;
- }
- if (this.isContextual("yield")) {
- if (this.inGenerator) return this.parseYield(forInit);
- // the tokenizer assumed an expression follows `yield`, but this
- // isn't that kind of yield
- this.exprAllowed = false;
- }
- let ownDestructuringErrors = false;
- let oldParenAssign = -1;
- let oldTrailingComma = -1;
- let oldDoubleProto = -1;
- if (refDestructuringErrors) {
- oldParenAssign = refDestructuringErrors.parenthesizedAssign;
- oldTrailingComma = refDestructuringErrors.trailingComma;
- oldDoubleProto = refDestructuringErrors.doubleProto;
- refDestructuringErrors.parenthesizedAssign =
- refDestructuringErrors.trailingComma = -1;
- } else {
- refDestructuringErrors = this._acquireDestructuringErrors();
- ownDestructuringErrors = true;
- }
- const startPos = this.start;
- const startLoc = this.startLoc;
- if (this.type === tokTypes.parenL || this.type === tokTypes.name) {
- this.potentialArrowAt = this.start;
- this.potentialArrowInForAwait = forInit === "await";
- }
- let left = this.parseMaybeConditional(forInit, refDestructuringErrors);
- if (afterLeftParse) {
- left = afterLeftParse.call(this, left, startPos, startLoc);
- }
- if (/** @type {TokenTypeInternal} */ (this.type).isAssign) {
- const operator = /** @type {string} */ (this.value);
- if (this.type === tokTypes.eq) {
- left = /** @type {Expression} */ (
- this.toAssignable(left, false, refDestructuringErrors)
- );
- }
- if (!ownDestructuringErrors) {
- refDestructuringErrors.parenthesizedAssign =
- refDestructuringErrors.trailingComma =
- refDestructuringErrors.doubleProto =
- -1;
- }
- if (refDestructuringErrors.shorthandAssign >= left.start) {
- // shorthand default was used correctly
- refDestructuringErrors.shorthandAssign = -1;
- }
- if (this.type === tokTypes.eq) this.checkLValPattern(left);
- else this.checkLValSimple(left);
- // the own record's last read was above, so the nested right-side
- // parse below can already reuse its pool slot
- if (ownDestructuringErrors) this._releaseDestructuringErrors();
- this.next();
- const right = this.parseMaybeAssign(forInit);
- if (oldDoubleProto > -1) {
- refDestructuringErrors.doubleProto = oldDoubleProto;
- }
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- new AssignmentNode(startPos, this.lastTokEnd, operator, left, right)
- )
- );
- } else if (ownDestructuringErrors) {
- this.checkExpressionErrors(refDestructuringErrors, true);
- this._releaseDestructuringErrors();
- }
- if (oldParenAssign > -1) {
- refDestructuringErrors.parenthesizedAssign = oldParenAssign;
- }
- if (oldTrailingComma > -1) {
- refDestructuringErrors.trailingComma = oldTrailingComma;
- }
- return left;
- }
- /**
- * Owned `parseExprOps`, an exact copy of acorn 8's, so the hot expression
- * spine (`parseMaybeAssign` → here → `parseMaybeUnary`) stays monomorphic
- * on owned code. Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {boolean | string=} forInit for-init context flag
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors destructuring errors to fill
- * @returns {Expression} expression node
- * @this {ParserInternals}
- */
- parseExprOps(forInit, refDestructuringErrors) {
- if (!this._lazy) {
- return base.parseExprOps.call(this, forInit, refDestructuringErrors);
- }
- const startPos = this.start;
- const startLoc = this.startLoc;
- const expr = this.parseMaybeUnary(
- refDestructuringErrors || null,
- false,
- false,
- forInit
- );
- if (this.checkExpressionErrors(refDestructuringErrors)) return expr;
- const isArrow = this._arrowFastPath
- ? expr === this._lastArrow && expr.start === startPos
- : expr.start === startPos && expr.type === "ArrowFunctionExpression";
- return isArrow
- ? expr
- : this.parseExprOp(expr, startPos, startLoc, -1, forInit);
- }
- /**
- * Owned `parseExprOp`, an exact-semantics copy of acorn 8's with the
- * same-precedence continuation turned from tail recursion into a loop —
- * `a + b + c + d` runs one frame instead of one per operator. Non-lazy mode
- * delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {Expression} left left operand
- * @param {number} leftStartPos expression start offset
- * @param {Position | undefined} leftStartLoc expression start position
- * @param {number} minPrec minimal binding precedence to continue
- * @param {boolean | string=} forInit for-init context flag (excludes `in`)
- * @returns {Expression} expression node
- * @this {ParserInternals}
- */
- parseExprOp(left, leftStartPos, leftStartLoc, minPrec, forInit) {
- if (!this._lazy) {
- return base.parseExprOp.call(
- this,
- left,
- leftStartPos,
- leftStartLoc,
- minPrec,
- forInit
- );
- }
- for (;;) {
- const type = /** @type {TokenTypeInternal} */ (this.type);
- let prec = type.binop;
- // acorn's TokenType sets binop to null (never undefined) when absent
- if (
- prec === null ||
- prec <= minPrec ||
- (forInit && type === tokTypes._in)
- ) {
- return left;
- }
- const logical =
- type === tokTypes.logicalOR || type === tokTypes.logicalAND;
- const coalesce = type === tokTypes.coalesce;
- if (coalesce) {
- // acorn parses `??`'s right at logical precedence so the mixing
- // check below sees any unparenthesized `&&`/`||` as a sibling
- prec = /** @type {number} */ (
- /** @type {TokenTypeInternal} */ (tokTypes.logicalAND).binop
- );
- }
- const op = /** @type {string} */ (this.value);
- this.next();
- const startPos = this.start;
- const right = this.parseExprOp(
- this.parseMaybeUnary(null, false, false, forInit),
- startPos,
- this.startLoc,
- prec,
- forInit
- );
- left = this.buildBinary(
- leftStartPos,
- leftStartLoc,
- left,
- right,
- op,
- logical || coalesce
- );
- if (
- (logical && this.type === tokTypes.coalesce) ||
- (coalesce &&
- (this.type === tokTypes.logicalOR ||
- this.type === tokTypes.logicalAND))
- ) {
- this.raiseRecoverable(
- this.start,
- "Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses"
- );
- }
- }
- }
- /**
- * Owned `buildBinary` (acorn calls it only from `parseExprOp`): binary and
- * logical nodes are built fully-formed on `BinaryNode`'s single shape.
- * Non-lazy mode delegates to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {number} startPos expression start offset
- * @param {Position | undefined} startLoc expression start position
- * @param {Expression} left left operand
- * @param {Expression} right right operand
- * @param {string} op operator text
- * @param {boolean} logical whether this is a logical/coalesce expression
- * @returns {Expression} binary or logical expression node
- * @this {ParserInternals}
- */
- buildBinary(startPos, startLoc, left, right, op, logical) {
- if (!this._lazy) {
- return base.buildBinary.call(
- this,
- startPos,
- startLoc,
- left,
- right,
- op,
- logical
- );
- }
- const rightNode = /** @type {Node} */ (/** @type {unknown} */ (right));
- if (rightNode.type === "PrivateIdentifier") {
- this.raise(
- rightNode.start,
- "Private identifier can only be left side of binary expression"
- );
- }
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- new BinaryNode(
- startPos,
- this.lastTokEnd,
- logical ? "LogicalExpression" : "BinaryExpression",
- left,
- op,
- right
- )
- )
- );
- }
- /**
- * Owned `parseExprAtom` for the hot atoms: identifiers (with acorn's exact
- * async-function/async-arrow detection), number/string literals, keyword
- * literals (`true`/`false`/`null` — on the same `LiteralNode` shape as the
- * rest) and `this`. Everything else, and non-lazy mode, delegates to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors destructuring errors to fill
- * @param {boolean | string=} forInit for-init context flag
- * @param {boolean=} forNew whether parsed as a `new` callee
- * @returns {Expression} expression atom
- * @this {ParserInternals}
- */
- parseExprAtom(refDestructuringErrors, forInit, forNew) {
- if (!this._lazy) {
- return base.parseExprAtom.call(
- this,
- refDestructuringErrors,
- forInit,
- forNew
- );
- }
- // division in expression position: the tokenizer got confused, force a
- // regexp re-read (mirrors the top of acorn's parseExprAtom)
- if (this.type === tokTypes.slash) this.readRegexp();
- const type = this.type;
- if (type === tokTypes.name) {
- const canBeArrow = this.potentialArrowAt === this.start;
- const startPos = this.start;
- const startLoc = this.startLoc;
- const containsEsc = this.containsEsc;
- let id = this.parseIdent(false);
- // `async` compare first — almost every identifier fails it, skipping
- // the version probe (all operands are pure)
- if (
- id.name === "async" &&
- !containsEsc &&
- this._ecmaVersion >= 8 &&
- !this.canInsertSemicolon() &&
- this.eat(tokTypes._function)
- ) {
- this.overrideContext(tokContexts.f_expr);
- return this.parseFunction(
- this.startNodeAt(startPos, startLoc),
- 0,
- false,
- true,
- forInit
- );
- }
- if (canBeArrow && !this.canInsertSemicolon()) {
- if (this.eat(tokTypes.arrow)) {
- return this.parseArrowExpression(
- this.startNodeAt(startPos, startLoc),
- [id],
- false,
- forInit
- );
- }
- if (
- id.name === "async" &&
- !containsEsc &&
- this._ecmaVersion >= 8 &&
- this.type === tokTypes.name &&
- (!this.potentialArrowInForAwait ||
- this.value !== "of" ||
- this.containsEsc)
- ) {
- id = this.parseIdent(false);
- if (this.canInsertSemicolon() || !this.eat(tokTypes.arrow)) {
- this.unexpected();
- }
- return this.parseArrowExpression(
- this.startNodeAt(startPos, startLoc),
- [id],
- true,
- forInit
- );
- }
- }
- return id;
- }
- if (type === tokTypes.num || type === tokTypes.string) {
- return /** @type {Expression} */ (
- /** @type {unknown} */ (this.parseLiteral(this.value))
- );
- }
- if (
- type === tokTypes._null ||
- type === tokTypes._true ||
- type === tokTypes._false
- ) {
- const node = new LiteralNode(
- this.start,
- this.end,
- type === tokTypes._null ? null : type === tokTypes._true,
- /** @type {string} */ (type.keyword)
- );
- this.next();
- return /** @type {Expression} */ (/** @type {unknown} */ (node));
- }
- if (type === tokTypes._this) {
- const node = new ThisNode(this.start, this.end);
- this.next();
- return /** @type {Expression} */ (/** @type {unknown} */ (node));
- }
- if (type === tokTypes.bracketL) {
- const start = this.start;
- this.next();
- const elements = this.parseExprList(
- tokTypes.bracketR,
- true,
- true,
- refDestructuringErrors
- );
- return /** @type {Expression} */ (
- /** @type {unknown} */ (
- new ArrayExpressionNode(start, this.lastTokEnd, elements)
- )
- );
- }
- return base.parseExprAtom.call(
- this,
- refDestructuringErrors,
- forInit,
- forNew
- );
- }
- /**
- * Owned `parseObj`, an exact-semantics copy of acorn 8's landing on
- * `ObjectNode`'s single shape (the ES5 trailing-comma gate is dropped —
- * the fast path requires ES11+). Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {boolean} isPattern whether parsing a binding pattern
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors destructuring errors to fill
- * @returns {Node} object expression or pattern
- * @this {ParserInternals}
- */
- parseObj(isPattern, refDestructuringErrors) {
- if (!this._subscriptFastPath) {
- return base.parseObj.call(this, isPattern, refDestructuringErrors);
- }
- const start = this.start;
- let first = true;
- // acorn's ES6+ `checkPropClash` only ever reads/writes `.proto`, so the
- // record is pooled by nesting depth (like `_deStack`); a subclass override
- // might write arbitrary keys and gets a fresh `{}` instead. Depth resets
- // implicitly since a raise aborts the whole parse.
- const pooled = this._propHashFastPath;
- /** @type {Record<string, unknown>} */
- let propHash;
- if (pooled) {
- const stack = this._propHashStack;
- const depth = this._propHashDepth++;
- const cached = stack[depth];
- if (cached !== undefined) {
- cached.proto = false;
- propHash = cached;
- } else {
- propHash = stack[depth] = { proto: false };
- }
- } else {
- propHash = {};
- }
- const scratch = this._acquireScratch();
- let count = 0;
- this.next();
- while (!this.eat(tokTypes.braceR)) {
- if (!first) {
- this.expect(tokTypes.comma);
- if (this.afterTrailingComma(tokTypes.braceR)) break;
- } else {
- first = false;
- }
- const prop = this.parseProperty(isPattern, refDestructuringErrors);
- if (!isPattern) {
- this.checkPropClash(prop, propHash, refDestructuringErrors);
- }
- scratch[count++] = prop;
- }
- if (pooled) this._propHashDepth--;
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new ObjectNode(
- start,
- this.lastTokEnd,
- isPattern ? "ObjectPattern" : "ObjectExpression",
- this._releaseScratch(scratch, count)
- )
- )
- );
- }
- /**
- * Owned `parseProperty`, an exact-semantics copy of acorn 8's: spread/rest
- * land fully-formed on `RestSpreadNode` and properties start pre-shaped on
- * `PropertyNode`, which acorn's shared `parsePropertyName`/
- * `parsePropertyValue` then fill in place (ES9+ semantics assumed via the
- * ES11 fast-path gate). Non-lazy mode delegates.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {boolean} isPattern whether parsing a binding pattern
- * @param {DestructuringErrorsShim | null=} refDestructuringErrors destructuring errors to fill
- * @returns {Node} property, spread element or rest element
- * @this {ParserInternals}
- */
- parseProperty(isPattern, refDestructuringErrors) {
- if (!this._subscriptFastPath) {
- return base.parseProperty.call(this, isPattern, refDestructuringErrors);
- }
- const nodeStart = this.start;
- if (this.eat(tokTypes.ellipsis)) {
- if (isPattern) {
- const argument = /** @type {Node} */ (
- /** @type {unknown} */ (this.parseIdent(false))
- );
- if (this.type === tokTypes.comma) {
- this.raiseRecoverable(
- this.start,
- "Comma is not permitted after the rest element"
- );
- }
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new RestSpreadNode(
- nodeStart,
- this.lastTokEnd,
- "RestElement",
- argument
- )
- )
- );
- }
- const argument = /** @type {Node} */ (
- /** @type {unknown} */ (
- this.parseMaybeAssign(false, refDestructuringErrors)
- )
- );
- // disallow trailing comma via `this.toAssignable()`
- if (
- this.type === tokTypes.comma &&
- refDestructuringErrors &&
- refDestructuringErrors.trailingComma < 0
- ) {
- refDestructuringErrors.trailingComma = this.start;
- }
- return /** @type {Node} */ (
- /** @type {unknown} */ (
- new RestSpreadNode(
- nodeStart,
- this.lastTokEnd,
- "SpreadElement",
- argument
- )
- )
- );
- }
- const prop = /** @type {Node} */ (
- /** @type {unknown} */ (new PropertyNode(nodeStart))
- );
- let isGenerator = false;
- /** @type {number | undefined} */
- let startPos;
- /** @type {Position | undefined} */
- let startLoc;
- if (isPattern || refDestructuringErrors) {
- startPos = this.start;
- startLoc = this.startLoc;
- }
- if (!isPattern) isGenerator = this.eat(tokTypes.star);
- const containsEsc = this.containsEsc;
- this.parsePropertyName(prop);
- let isAsync = false;
- if (!isPattern && !containsEsc && !isGenerator && this.isAsyncProp(prop)) {
- isAsync = true;
- isGenerator = this.eat(tokTypes.star);
- this.parsePropertyName(prop);
- }
- this.parsePropertyValue(
- prop,
- isPattern,
- isGenerator,
- isAsync,
- startPos,
- startLoc,
- refDestructuringErrors,
- containsEsc
- );
- return this.finishNode(prop, "Property");
- }
- /**
- * Owned `parseLiteral`: builds the finished `LiteralNode` directly; the
- * `bigint` branch matches acorn's. Non-lazy mode delegates to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {unknown} value literal value
- * @returns {Node} literal node
- * @this {ParserInternals}
- */
- parseLiteral(value) {
- if (!this._lazy) return base.parseLiteral.call(this, value);
- const start = this.start;
- const end = this.end;
- const raw = this.input.slice(start, end);
- const node = new LiteralNode(start, end, value, raw);
- if (raw.charCodeAt(raw.length - 1) === 110) {
- // acorn falls back to normalizing `raw` when `BigInt` is missing;
- // every Node version webpack supports has it, so `value` is set
- /** @type {LiteralNode & { bigint?: string }} */ (node).bigint =
- /** @type {bigint} */ (value).toString();
- }
- this.next();
- return /** @type {Node} */ (/** @type {unknown} */ (node));
- }
- /**
- * Single-construction regexp literals: acorn validates the pattern and
- * then builds the value with a second `new RegExp`. This override scans
- * like acorn, keeps acorn's flag validation (for its exact messages) and
- * lets one `new RegExp` be both the V8-backed validation and the value.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/tokenize.js
- * @this {ParserInternals & { regexpState: unknown }}
- * @returns {void}
- */
- readRegexp() {
- const input = this.input;
- const start = this.pos;
- const len = input.length;
- let escaped = false;
- let inClass = false;
- let pos = start;
- for (;;) {
- if (pos >= len) this.raise(start, "Unterminated regular expression");
- const ch = input.charCodeAt(pos);
- // LF, CR, LS, PS
- if (ch === 10 || ch === 13 || ch === 0x2028 || ch === 0x2029) {
- this.raise(start, "Unterminated regular expression");
- }
- if (escaped) {
- escaped = false;
- } else {
- if (ch === 91) inClass = true;
- else if (ch === 93 && inClass) inClass = false;
- else if (ch === 47 && !inClass) break;
- escaped = ch === 92;
- }
- pos++;
- }
- const pattern = input.slice(start, pos);
- this.pos = pos + 1;
- const flagsStart = this.pos;
- const flags = this.readWord1();
- if (this.containsEsc) this.unexpected(flagsStart);
- // acorn's per-ecmaVersion flag validation, kept for its exact errors;
- // the whitelist is precomputed in the constructor
- const ecmaVersion = this._ecmaVersion;
- const validFlags = this._validRegexpFlags;
- let hasU = false;
- let hasV = false;
- for (let i = 0; i < flags.length; i++) {
- const flag = flags.charAt(i);
- if (!validFlags.includes(flag)) {
- this.raise(start, "Invalid regular expression flag");
- }
- if (flags.includes(flag, i + 1)) {
- this.raise(start, "Duplicate regular expression flag");
- }
- if (flag === "u") hasU = true;
- if (flag === "v") hasV = true;
- }
- if (ecmaVersion >= 15 && hasU && hasV) {
- this.raise(start, "Invalid regular expression flag");
- }
- let value = null;
- try {
- value = new RegExp(pattern, flags);
- } catch (err) {
- // V8's verdict on the pattern, like validateRegExpPattern below
- this.raiseRecoverable(start, /** @type {Error} */ (err).message);
- }
- return this.finishToken(tokTypes.regexp, { pattern, flags, value });
- }
- // ----- regexp validation (V8-backed, replaces acorn's JS revalidation) -----
- /**
- * Acorn constructs the literal's `RegExp` value right after this hook, so
- * V8 validates every pattern anyway; acorn's own JS copy of
- * that validation costs several percent of parse time. Raise from V8's
- * verdict instead — invalid patterns still fail the module build, only
- * exotic engine-specific message texts may differ.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/regexp.js
- * @param {{ start: number, source: string, flags: string }} state acorn regexp validation state
- * @this {ParserInternals}
- */
- validateRegExpPattern(state) {
- try {
- // eslint-disable-next-line no-new
- new RegExp(state.source, state.flags);
- } catch (err) {
- this.raiseRecoverable(state.start, /** @type {Error} */ (err).message);
- }
- }
- // ----- scope tracking (Set-based, replaces acorn's array + indexOf) -----
- /**
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/scope.js
- * @param {number} flags scope flags
- * @this {ParserInternals}
- */
- enterScope(flags) {
- this.scopeStack.push(new Scope(flags));
- }
- /**
- * Set-backed replacement for acorn's `declareName` on Set-backed scopes.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/scope.js
- * @param {string} name declared name
- * @param {number} bindingType acorn BIND_* binding type
- * @param {number} pos source offset for redeclaration errors
- * @this {ParserInternals}
- */
- declareName(name, bindingType, pos) {
- let redeclared = false;
- if (bindingType === BIND_LEXICAL) {
- const scope = this.currentScope();
- redeclared =
- (scope.lexical !== undefined && scope.lexical.has(name)) ||
- (scope.functions !== undefined && scope.functions.has(name)) ||
- (scope.var !== undefined && scope.var.has(name));
- if (scope.lexical === undefined) {
- scope.firstLexical = name;
- scope.lexical = new Set();
- }
- scope.lexical.add(name);
- if (this.inModule && scope.flags & SCOPE_TOP) {
- delete this.undefinedExports[name];
- }
- } else if (bindingType === /* BIND_SIMPLE_CATCH */ 4) {
- const scope = this.currentScope();
- if (scope.lexical === undefined) {
- scope.firstLexical = name;
- scope.lexical = new Set();
- }
- scope.lexical.add(name);
- } else if (bindingType === /* BIND_FUNCTION */ 3) {
- const scope = this.currentScope();
- redeclared = this.treatFunctionsAsVar
- ? scope.lexical !== undefined && scope.lexical.has(name)
- : (scope.lexical !== undefined && scope.lexical.has(name)) ||
- (scope.var !== undefined && scope.var.has(name));
- (scope.functions || (scope.functions = new Set())).add(name);
- } else {
- for (let i = this.scopeStack.length - 1; i >= 0; --i) {
- const scope = this.scopeStack[i];
- if (
- (scope.lexical !== undefined &&
- scope.lexical.has(name) &&
- !(
- scope.flags & SCOPE_SIMPLE_CATCH && scope.firstLexical === name
- )) ||
- // lazy-Set check first: `functions` is undefined for almost all
- // scopes, and the method call walks no state worth paying for then
- (scope.functions !== undefined &&
- !this.treatFunctionsAsVarInScope(scope) &&
- scope.functions.has(name))
- ) {
- redeclared = true;
- break;
- }
- (scope.var || (scope.var = new Set())).add(name);
- if (this.inModule && scope.flags & SCOPE_TOP) {
- delete this.undefinedExports[name];
- }
- if (scope.flags & SCOPE_VAR) break;
- }
- }
- if (redeclared) {
- this.raiseRecoverable(
- pos,
- `Identifier '${name}' has already been declared`
- );
- }
- }
- /**
- * Set-backed replacement for acorn's `checkLocalExport` on Set-backed scopes.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/scope.js
- * @param {Identifier} id exported identifier
- * @this {ParserInternals}
- */
- checkLocalExport(id) {
- const topScope = this.scopeStack[0];
- if (
- !(topScope.lexical !== undefined && topScope.lexical.has(id.name)) &&
- !(topScope.var !== undefined && topScope.var.has(id.name))
- ) {
- this.undefinedExports[id.name] = id;
- }
- }
- // ----- import attributes (`with { ... }` / legacy `assert { ... }`) -----
- /**
- * @returns {ImportAttribute[]} import attributes
- * @this {ParserInternals}
- */
- parseWithClause() {
- /** @type {ImportAttribute[] & { [LEGACY_ASSERT_ATTRIBUTES]?: boolean }} */
- const nodes = [];
- const isAssertLegacy = this.value === "assert";
- if (isAssertLegacy) {
- if (!this.eat(tokTypes.name)) {
- return nodes;
- }
- } else if (!this.eat(tokTypes._with)) {
- return nodes;
- }
- this.expect(tokTypes.braceL);
- /** @type {Record<string, boolean>} */
- const attributeKeys = {};
- let first = true;
- while (!this.eat(tokTypes.braceR)) {
- if (!first) {
- this.expect(tokTypes.comma);
- if (this.afterTrailingComma(tokTypes.braceR)) {
- break;
- }
- } else {
- first = false;
- }
- const attr = this.parseImportAttribute();
- const keyName =
- /** @type {string} */
- (attr.key.type === "Identifier" ? attr.key.name : attr.key.value);
- if (Object.prototype.hasOwnProperty.call(attributeKeys, keyName)) {
- this.raiseRecoverable(
- attr.key.start,
- `Duplicate attribute key '${keyName}'`
- );
- }
- attributeKeys[keyName] = true;
- nodes.push(attr);
- }
- if (isAssertLegacy) {
- nodes[LEGACY_ASSERT_ATTRIBUTES] = true;
- }
- return nodes;
- }
- // ----- import phases (`import defer/source`, `import.defer/source()`) -----
- /**
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {Node & { phase?: ImportPhase }} node import declaration node
- * @returns {Node} finished node
- * @this {ParserInternals}
- */
- parseImport(node) {
- this._moduleSyntaxSeen = true;
- this._importPhase = null;
- const result = base.parseImport.call(this, node);
- if (this._importPhase) node.phase = this._importPhase;
- return result;
- }
- /**
- * Owned `parseExport` only to flag module syntax for the auto-fallback guard;
- * parsing itself delegates to acorn.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @param {Node} node started export node
- * @param {unknown} exports export-name tracking object
- * @returns {Node} export declaration
- * @this {ParserInternals}
- */
- parseExport(node, exports) {
- this._moduleSyntaxSeen = true;
- return base.parseExport.call(this, node, exports);
- }
- /**
- * Owned `parseAwait` only to flag top-level await (module-only) for the
- * auto-fallback guard; await inside a function is not module syntax.
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {boolean | string=} forInit for-loop init context flag
- * @returns {Expression} await expression
- * @this {ParserInternals}
- */
- parseAwait(forInit) {
- if (this.inModule && this.currentVarScope().flags & SCOPE_TOP) {
- this._moduleSyntaxSeen = true;
- }
- return base.parseAwait.call(this, forInit);
- }
- /**
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/statement.js
- * @returns {AnyImportSpecifier[]} import specifiers
- * @this {ParserInternals}
- */
- parseImportSpecifiers() {
- if (!this._importPhasesEnabled) {
- return base.parseImportSpecifiers.call(this);
- }
- /** @type {ImportPhase | null} */
- const phase = this.isContextual("defer")
- ? "defer"
- : this.isContextual("source")
- ? "source"
- : null;
- if (!phase) return base.parseImportSpecifiers.call(this);
- const phaseId = this.parseIdent();
- if (this.isContextual("from") || this.type === tokTypes.comma) {
- // `defer`/`source` was the default import name, not a phase modifier
- const defaultSpecifier =
- /** @type {ImportDefaultSpecifier} */
- (
- this.startNodeAt(
- phaseId.start,
- phaseId.loc ? phaseId.loc.start : undefined
- )
- );
- defaultSpecifier.local = phaseId;
- this.checkLValSimple(phaseId, BIND_LEXICAL);
- /** @type {AnyImportSpecifier[]} */
- const nodes = [
- /** @type {ImportDefaultSpecifier} */
- (this.finishNode(defaultSpecifier, "ImportDefaultSpecifier"))
- ];
- if (this.eat(tokTypes.comma)) {
- if (this.type !== tokTypes.star && this.type !== tokTypes.braceL) {
- this.unexpected();
- }
- nodes.push(...base.parseImportSpecifiers.call(this));
- }
- return nodes;
- }
- this._importPhase = phase;
- if (phase === "defer") {
- if (this.type !== tokTypes.star) {
- this.raiseRecoverable(
- phaseId.start,
- "'import defer' can only be used with namespace imports ('import defer * as identifierName from ...')."
- );
- }
- } else if (this.type !== tokTypes.name) {
- this.raiseRecoverable(
- phaseId.start,
- "'import source' can only be used with direct identifier specifier imports."
- );
- }
- const specifiers = base.parseImportSpecifiers.call(this);
- if (
- phase === "source" &&
- specifiers.some((s) => s.type !== "ImportDefaultSpecifier")
- ) {
- this.raiseRecoverable(
- phaseId.start,
- "'import source' can only be used with direct identifier specifier imports ('import source identifierName from ...')."
- );
- }
- return specifiers;
- }
- /**
- * acorn source: https://github.com/acornjs/acorn/blob/8.17.0/acorn/src/expression.js
- * @param {boolean} forNew whether parsed as the operand of `new`
- * @returns {Expression} expression node
- * @this {ParserInternals}
- */
- parseExprImport(forNew) {
- const node = base.parseExprImport.call(this, forNew);
- if (
- this._importPhasesEnabled &&
- node.type === "MetaProperty" &&
- (node.property.name === "defer" || node.property.name === "source")
- ) {
- if (this.type === tokTypes.parenL) {
- if (forNew) {
- // same guard acorn applies to `new import(...)`
- this.raise(node.start, "import call cannot be the target of `new`");
- }
- const dynImport =
- /** @type {ImportExpression & { phase?: ImportPhase }} */
- (
- this.parseDynamicImport(
- this.startNodeAt(
- node.start,
- node.loc ? node.loc.start : undefined
- )
- )
- );
- dynImport.phase = node.property.name;
- return dynImport;
- }
- this.raiseRecoverable(
- node.start,
- `'import.${node.property.name}' can only be used in a dynamic import.`
- );
- }
- return node;
- }
- /**
- * @param {Node & { property?: Identifier }} node started node with `meta` set to `import`
- * @returns {Expression} MetaProperty node
- * @this {ParserInternals}
- */
- parseImportMeta(node) {
- if (!this._importPhasesEnabled) {
- // base only accepts `import.meta` here, which is module-only
- this._moduleSyntaxSeen = true;
- return base.parseImportMeta.call(this, node);
- }
- this.next();
- const containsEsc = this.containsEsc;
- const property = this.parseIdent(true);
- node.property = property;
- const { name } = property;
- // only `import.meta` is module-only; `import.defer`/`.source` are dynamic
- if (name === "meta") this._moduleSyntaxSeen = true;
- if (name !== "meta" && name !== "defer" && name !== "source") {
- this.raiseRecoverable(
- property.start,
- "The only valid meta property for import is 'import.meta'"
- );
- }
- if (containsEsc) {
- this.raiseRecoverable(
- node.start,
- `'import.${name}' must not contain escaped characters`
- );
- }
- if (
- name === "meta" &&
- this.options.sourceType !== "module" &&
- !this.options.allowImportExportEverywhere
- ) {
- this.raiseRecoverable(
- node.start,
- "Cannot use 'import.meta' outside a module"
- );
- }
- return /** @type {Expression} */ (this.finishNode(node, "MetaProperty"));
- }
- }
- /** @import { SourcePosition } from "../Dependency" */
- /**
- * Whether a raw string literal contains a legacy octal (`\47`, `\0` followed by
- * a digit) or non-octal decimal (`\8`, `\9`) escape — all SyntaxErrors in
- * strict mode. Escaped backslashes (`\\`) and `\x` / `\u` escapes are skipped.
- * @param {string} raw raw string literal text, including quotes
- * @returns {boolean} true when a strict-forbidden escape is present
- */
- const hasOctalEscape = (raw) => {
- for (let i = 0; i < raw.length; i++) {
- if (raw.charCodeAt(i) !== 92) continue;
- const next = raw.charCodeAt(i + 1);
- if (next === 92) {
- i++;
- continue;
- }
- // `\0` is a valid NUL escape unless a digit follows it.
- if (next === 48) {
- const after = raw.charCodeAt(i + 2);
- if (after >= 48 && after <= 57) return true;
- i++;
- continue;
- }
- if (next >= 49 && next <= 57) return true;
- }
- return false;
- };
- // Location decoding for lazy-mode output: nodes carry only offsets (the parser
- // skips acorn's location tracking), so line/column are derived on demand from
- // these two helpers.
- /**
- * Exact char-code scan matching acorn's `lineBreak` semantics (CRLF is one
- * break). Kept as the fallback for the terminators `buildLineStarts` cannot
- * find with a native search.
- * @param {string} source source code
- * @returns {number[]} line start offsets
- */
- const scanLineStarts = (source) => {
- const len = source.length;
- const lineStarts = [0];
- for (let i = 0; i < len; i++) {
- const ch = source.charCodeAt(i);
- if (ch === 10) {
- lineStarts.push(i + 1);
- } else if (ch === 13) {
- if (source.charCodeAt(i + 1) === 10) i++;
- lineStarts.push(i + 1);
- } else if (ch === 0x2028 || ch === 0x2029) {
- lineStarts.push(i + 1);
- }
- }
- return lineStarts;
- };
- /**
- * Offset of each line's first character. `indexOf` searches the source natively,
- * which beats a char-code loop by ~3x; CRLF needs no special case because it
- * starts the next line after its `\n` either way. A lone `\r` or a unicode
- * separator does break that equivalence, so those fall back to the char scan.
- * @param {string} source source code
- * @returns {number[]} line start offsets
- */
- const buildLineStarts = (source) => {
- if (!source.includes("\u2028") && !source.includes("\u2029")) {
- let cr = source.indexOf("\r");
- while (cr !== -1 && source.charCodeAt(cr + 1) === 10) {
- cr = source.indexOf("\r", cr + 2);
- }
- if (cr === -1) {
- const lineStarts = [0];
- let i = source.indexOf("\n");
- while (i !== -1) {
- lineStarts.push(i + 1);
- i = source.indexOf("\n", i + 1);
- }
- return lineStarts;
- }
- }
- return scanLineStarts(source);
- };
- /**
- * Binary search for the line containing the offset.
- * @param {number[]} lineStarts line start offsets
- * @param {number} offset source offset
- * @returns {SourcePosition} position (1-based line, 0-based column)
- */
- const positionAt = (lineStarts, offset) => {
- let lo = 0;
- let hi = lineStarts.length - 1;
- while (lo < hi) {
- const mid = (lo + hi + 1) >>> 1;
- if (lineStarts[mid] <= offset) lo = mid;
- else hi = mid - 1;
- }
- return { line: lo + 1, column: offset - lineStarts[lo] };
- };
- const KEYWORDS_BEFORE_REGEXP = new Set([
- "await",
- "case",
- "delete",
- "do",
- "else",
- "in",
- "instanceof",
- "new",
- "of",
- "return",
- "throw",
- "typeof",
- "void",
- "yield"
- ]);
- /**
- * Whether a `/` at this point starts a regexp rather than division, decided by
- * what precedes it: after a value (name, number, `)`, `]`) it divides, after an
- * operator or a keyword it opens a regexp. `)` and `]` are always read as
- * division — telling `if (a) /re/` from `(a) / b` needs paren-context tracking
- * this single pass does not carry.
- * @param {string} source source code
- * @param {number} slash offset of the `/`
- * @returns {boolean} true when a regexp literal starts here
- */
- const startsRegExp = (source, slash) => {
- let i = slash - 1;
- while (i >= 0) {
- const code = source.charCodeAt(i);
- if (code === 32 || code === 9 || code === 10 || code === 13) i--;
- else break;
- }
- if (i < 0) return true;
- const code = source.charCodeAt(i);
- if (code === 41 || code === 93) return false;
- if (!isIdentifierChar(code)) return true;
- // a keyword before `/` is an operator position (`return /re/`), a name is not
- let start = i;
- while (start >= 0 && isIdentifierChar(source.charCodeAt(start))) start--;
- return KEYWORDS_BEFORE_REGEXP.has(source.slice(start + 1, i + 1));
- };
- /**
- * Skips whitespace and comments, returning the next code offset.
- * @param {string} source source code
- * @param {number} i offset to start at
- * @returns {number} offset of the next significant character
- */
- const skipTrivia = (source, i) => {
- while (i < source.length) {
- const code = source.charCodeAt(i);
- if (code === 32 || code === 9 || code === 10 || code === 13) {
- i++;
- } else if (code === 47 && source.charCodeAt(i + 1) === 47) {
- const end = source.indexOf("\n", i + 2);
- i = end === -1 ? source.length : end + 1;
- } else if (code === 47 && source.charCodeAt(i + 1) === 42) {
- const end = source.indexOf("*/", i + 2);
- i = end === -1 ? source.length : end + 2;
- } else {
- break;
- }
- }
- return i;
- };
- /** @type {Map<number, string>} */
- const SINGLE_CHARACTER_ESCAPES = new Map([
- [110, "\n"],
- [114, "\r"],
- [116, "\t"],
- [98, "\b"],
- [118, "\u000B"],
- [102, "\f"]
- ]);
- /**
- * Reads the hex digits of a `\x`/`\u` escape.
- * @param {string} source source code
- * @param {number} i offset of the first digit
- * @param {number} length how many digits to read
- * @returns {number} the code point, or `-1` when a digit is missing
- */
- const readHexEscapeValue = (source, i, length) => {
- let value = 0;
- for (let j = i; j < i + length; j++) {
- const digit = Number.parseInt(source[j], 16);
- if (Number.isNaN(digit)) return -1;
- value = value * 16 + digit;
- }
- return value;
- };
- /**
- * Cooks one escape sequence, sloppy-mode script semantics (legacy octal included).
- * @param {string} source source code
- * @param {number} i offset just past the backslash
- * @returns {{ value: string, end: number } | undefined} the cooked text, or `undefined` when malformed
- */
- const readEscape = (source, i) => {
- if (i >= source.length) return;
- const code = source.charCodeAt(i);
- const single = SINGLE_CHARACTER_ESCAPES.get(code);
- if (single !== undefined) return { value: single, end: i + 1 };
- // a line continuation cooks to nothing; `\r\n` counts as one terminator
- if (code === 13) {
- return { value: "", end: source.charCodeAt(i + 1) === 10 ? i + 2 : i + 1 };
- }
- if (code === 10 || code === 0x2028 || code === 0x2029) {
- return { value: "", end: i + 1 };
- }
- if (code === 120) {
- const value = readHexEscapeValue(source, i + 1, 2);
- if (value === -1) return;
- return { value: String.fromCodePoint(value), end: i + 3 };
- }
- if (code === 117) {
- if (source.charCodeAt(i + 1) === 123) {
- const end = source.indexOf("}", i + 2);
- if (end === -1 || end === i + 2) return;
- const value = readHexEscapeValue(source, i + 2, end - i - 2);
- if (value === -1 || value > 0x10ffff) return;
- return { value: String.fromCodePoint(value), end: end + 1 };
- }
- const value = readHexEscapeValue(source, i + 1, 4);
- if (value === -1) return;
- return { value: String.fromCodePoint(value), end: i + 5 };
- }
- if (code >= 48 && code <= 55) {
- let digits = /** @type {RegExpMatchArray} */ (
- source.slice(i, i + 3).match(/^[0-7]+/)
- )[0];
- if (Number.parseInt(digits, 8) > 255) digits = digits.slice(0, -1);
- return {
- value: String.fromCharCode(Number.parseInt(digits, 8)),
- end: i + digits.length
- };
- }
- return { value: source[i], end: i + 1 };
- };
- /**
- * Reads a `'`/`"` string starting at `i`, cooking its escapes the way the ast
- * this replaces did — a `Literal` node carries the cooked value, so a specifier
- * written `"\x2e/a"` is the same dependency as `"./a"`. Templates are not
- * specifiers here: the ast only matched `Literal` nodes.
- * @param {string} source source code
- * @param {number} i offset of the opening quote
- * @returns {{ value: string, end: number } | undefined} the literal
- */
- const readStringLiteral = (source, i) => {
- const quote = source.charCodeAt(i);
- if (quote !== 34 && quote !== 39) return;
- for (let j = i + 1; j < source.length; j++) {
- const code = source.charCodeAt(j);
- if (code === 10 || code === 13) return;
- if (code === 92) return readEscapedStringLiteral(source, i, j);
- if (code === quote) return { value: source.slice(i + 1, j), end: j + 1 };
- }
- };
- /**
- * Continues `readStringLiteral` from its first escape, so the unescaped case
- * stays a single slice.
- * @param {string} source source code
- * @param {number} i offset of the opening quote
- * @param {number} firstEscape offset of the first backslash
- * @returns {{ value: string, end: number } | undefined} the literal
- */
- const readEscapedStringLiteral = (source, i, firstEscape) => {
- const quote = source.charCodeAt(i);
- let value = "";
- let segment = i + 1;
- for (let j = firstEscape; j < source.length; j++) {
- const code = source.charCodeAt(j);
- if (code === 10 || code === 13) return;
- if (code === quote) {
- return { value: value + source.slice(segment, j), end: j + 1 };
- }
- if (code !== 92) continue;
- const escape = readEscape(source, j + 1);
- if (escape === undefined) return;
- value += source.slice(segment, j) + escape.value;
- segment = escape.end;
- j = escape.end - 1;
- }
- };
- /**
- * Scans a template body from `i` (just past a backtick or a `}` resuming one).
- * @param {string} source source code
- * @param {number} i offset inside the template
- * @returns {{ end: number, substitution: boolean }} where the body stops
- */
- const skipTemplateBody = (source, i) => {
- for (; i < source.length; i++) {
- const code = source.charCodeAt(i);
- if (code === 92) {
- i++;
- } else if (code === 96) {
- return { end: i, substitution: false };
- } else if (code === 36 && source.charCodeAt(i + 1) === 123) {
- return { end: i + 1, substitution: true };
- }
- }
- return { end: source.length, substitution: false };
- };
- /**
- * Collects the specifiers of static `require("…")` calls. A scan rather than a
- * parse: it only has to skip what could hide or fake a call — comments, strings,
- * templates and regexps — so it costs one pass and builds no ast. Template
- * substitutions are scanned as the code they are, which is why the brace depth
- * of each open `${` is tracked.
- * @param {string} source source code
- * @returns {Set<string>} required specifiers
- */
- const collectCjsRequireSpecifiers = (source) => {
- /** @type {Set<string>} */
- const specifiers = new Set();
- /** @type {number[]} */
- const templateBraceDepths = [];
- let braceDepth = 0;
- for (let i = 0; i < source.length; i++) {
- const code = source.charCodeAt(i);
- if (code === 96) {
- const body = skipTemplateBody(source, i + 1);
- i = body.end;
- if (body.substitution) {
- templateBraceDepths.push(braceDepth);
- braceDepth++;
- }
- continue;
- }
- if (code === 123) {
- braceDepth++;
- continue;
- }
- if (code === 125) {
- braceDepth--;
- if (
- templateBraceDepths.length > 0 &&
- templateBraceDepths[templateBraceDepths.length - 1] === braceDepth
- ) {
- templateBraceDepths.pop();
- const body = skipTemplateBody(source, i + 1);
- i = body.end;
- if (body.substitution) {
- templateBraceDepths.push(braceDepth);
- braceDepth++;
- }
- }
- continue;
- }
- if (code === 34 || code === 39) {
- for (i++; i < source.length; i++) {
- const inner = source.charCodeAt(i);
- if (inner === 92) i++;
- else if (inner === code) break;
- }
- continue;
- }
- if (code === 47) {
- const next = source.charCodeAt(i + 1);
- if (next === 47) {
- const end = source.indexOf("\n", i + 2);
- i = end === -1 ? source.length : end;
- continue;
- }
- if (next === 42) {
- const end = source.indexOf("*/", i + 2);
- i = end === -1 ? source.length : end + 1;
- continue;
- }
- if (!startsRegExp(source, i)) continue;
- let inClass = false;
- for (i++; i < source.length; i++) {
- const inner = source.charCodeAt(i);
- if (inner === 92) i++;
- else if (inner === 91) inClass = true;
- else if (inner === 93) inClass = false;
- else if (inner === 10) break;
- else if (inner === 47 && !inClass) break;
- }
- continue;
- }
- // `r` of `require`, as a whole word that is not a member access
- if (code !== 114 || !source.startsWith("require", i)) continue;
- if (isIdentifierChar(source.charCodeAt(i + 7))) continue;
- if (i > 0) {
- const before = source.charCodeAt(i - 1);
- if (isIdentifierChar(before) || before === 46) continue;
- }
- let j = skipTrivia(source, i + 7);
- if (source.charCodeAt(j) !== 40) continue;
- j = skipTrivia(source, j + 1);
- // a cast wraps the specifier in its own parens: `require(/** … */ ("x"))`
- let parens = 0;
- while (source.charCodeAt(j) === 40) {
- parens++;
- j = skipTrivia(source, j + 1);
- }
- const literal = readStringLiteral(source, j);
- if (literal === undefined) {
- i += 6;
- continue;
- }
- j = skipTrivia(source, literal.end);
- while (parens > 0 && source.charCodeAt(j) === 41) {
- parens--;
- j = skipTrivia(source, j + 1);
- }
- if (parens !== 0 || source.charCodeAt(j) !== 41) {
- i += 6;
- continue;
- }
- specifiers.add(literal.value);
- i = j;
- }
- return specifiers;
- };
- module.exports.LEGACY_ASSERT_ATTRIBUTES = LEGACY_ASSERT_ATTRIBUTES;
- module.exports.WebpackParser = WebpackParser;
- module.exports.buildLineStarts = buildLineStarts;
- module.exports.collectCjsRequireSpecifiers = collectCjsRequireSpecifiers;
- module.exports.hasOctalEscape = hasOctalEscape;
- module.exports.isIdentifierChar = isIdentifierChar;
- module.exports.positionAt = positionAt;
|