12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449384503845138452384533845438455384563845738458384593846038461384623846338464384653846638467384683846938470384713847238473384743847538476384773847838479384803848138482384833848438485384863848738488384893849038491384923849338494384953849638497384983849938500385013850238503385043850538506385073850838509385103851138512385133851438515385163851738518385193852038521385223852338524385253852638527385283852938530385313853238533385343853538536385373853838539385403854138542385433854438545385463854738548385493855038551385523855338554385553855638557385583855938560385613856238563385643856538566385673856838569385703857138572385733857438575385763857738578385793858038581385823858338584385853858638587385883858938590385913859238593385943859538596385973859838599386003860138602386033860438605386063860738608386093861038611386123861338614386153861638617386183861938620386213862238623386243862538626386273862838629386303863138632386333863438635386363863738638386393864038641386423864338644386453864638647386483864938650386513865238653386543865538656386573865838659386603866138662386633866438665386663866738668386693867038671386723867338674386753867638677386783867938680386813868238683386843868538686386873868838689386903869138692386933869438695386963869738698386993870038701387023870338704387053870638707387083870938710387113871238713387143871538716387173871838719387203872138722387233872438725387263872738728387293873038731387323873338734387353873638737387383873938740387413874238743387443874538746387473874838749387503875138752387533875438755387563875738758387593876038761387623876338764387653876638767387683876938770387713877238773387743877538776387773877838779387803878138782387833878438785387863878738788387893879038791387923879338794387953879638797387983879938800388013880238803388043880538806388073880838809388103881138812388133881438815388163881738818388193882038821388223882338824388253882638827388283882938830388313883238833388343883538836388373883838839388403884138842388433884438845388463884738848388493885038851388523885338854388553885638857388583885938860388613886238863388643886538866388673886838869388703887138872388733887438875388763887738878388793888038881388823888338884388853888638887388883888938890388913889238893388943889538896388973889838899389003890138902389033890438905389063890738908389093891038911389123891338914389153891638917389183891938920389213892238923389243892538926389273892838929389303893138932389333893438935389363893738938389393894038941389423894338944389453894638947389483894938950389513895238953389543895538956389573895838959389603896138962389633896438965389663896738968389693897038971389723897338974389753897638977389783897938980389813898238983389843898538986389873898838989389903899138992389933899438995389963899738998389993900039001390023900339004390053900639007390083900939010390113901239013390143901539016390173901839019390203902139022390233902439025390263902739028390293903039031390323903339034390353903639037390383903939040390413904239043390443904539046390473904839049390503905139052390533905439055390563905739058390593906039061390623906339064390653906639067390683906939070390713907239073390743907539076390773907839079390803908139082390833908439085390863908739088390893909039091390923909339094390953909639097390983909939100391013910239103391043910539106391073910839109391103911139112391133911439115391163911739118391193912039121391223912339124391253912639127391283912939130391313913239133391343913539136391373913839139391403914139142391433914439145391463914739148391493915039151391523915339154391553915639157391583915939160391613916239163391643916539166391673916839169391703917139172391733917439175391763917739178391793918039181391823918339184391853918639187391883918939190391913919239193391943919539196391973919839199392003920139202392033920439205392063920739208392093921039211392123921339214392153921639217392183921939220392213922239223392243922539226392273922839229392303923139232392333923439235392363923739238392393924039241392423924339244392453924639247392483924939250392513925239253392543925539256392573925839259392603926139262392633926439265392663926739268392693927039271392723927339274392753927639277392783927939280392813928239283392843928539286392873928839289392903929139292392933929439295392963929739298392993930039301393023930339304393053930639307393083930939310393113931239313393143931539316393173931839319393203932139322393233932439325393263932739328393293933039331393323933339334393353933639337393383933939340393413934239343393443934539346393473934839349393503935139352393533935439355393563935739358393593936039361393623936339364393653936639367393683936939370393713937239373393743937539376393773937839379393803938139382393833938439385393863938739388393893939039391393923939339394393953939639397393983939939400394013940239403394043940539406394073940839409394103941139412394133941439415394163941739418394193942039421394223942339424394253942639427394283942939430394313943239433394343943539436394373943839439394403944139442394433944439445394463944739448394493945039451394523945339454394553945639457394583945939460394613946239463394643946539466394673946839469394703947139472394733947439475394763947739478394793948039481394823948339484394853948639487394883948939490394913949239493394943949539496394973949839499395003950139502395033950439505395063950739508395093951039511395123951339514395153951639517395183951939520395213952239523395243952539526395273952839529395303953139532395333953439535395363953739538395393954039541395423954339544395453954639547395483954939550395513955239553395543955539556395573955839559395603956139562395633956439565395663956739568395693957039571395723957339574395753957639577395783957939580395813958239583395843958539586395873958839589395903959139592395933959439595395963959739598395993960039601396023960339604396053960639607396083960939610396113961239613396143961539616396173961839619396203962139622396233962439625396263962739628396293963039631396323963339634396353963639637396383963939640396413964239643396443964539646396473964839649396503965139652396533965439655396563965739658396593966039661396623966339664396653966639667396683966939670396713967239673396743967539676396773967839679396803968139682396833968439685396863968739688396893969039691396923969339694396953969639697396983969939700397013970239703397043970539706397073970839709397103971139712397133971439715397163971739718397193972039721397223972339724397253972639727397283972939730397313973239733397343973539736397373973839739397403974139742397433974439745397463974739748397493975039751397523975339754397553975639757397583975939760397613976239763397643976539766397673976839769397703977139772397733977439775397763977739778397793978039781397823978339784397853978639787397883978939790397913979239793397943979539796397973979839799398003980139802398033980439805398063980739808398093981039811398123981339814398153981639817398183981939820398213982239823398243982539826398273982839829398303983139832398333983439835398363983739838398393984039841398423984339844398453984639847398483984939850398513985239853398543985539856398573985839859398603986139862398633986439865398663986739868398693987039871398723987339874398753987639877398783987939880398813988239883398843988539886398873988839889398903989139892398933989439895398963989739898398993990039901399023990339904399053990639907399083990939910399113991239913399143991539916399173991839919399203992139922399233992439925399263992739928399293993039931399323993339934399353993639937399383993939940399413994239943399443994539946399473994839949399503995139952399533995439955399563995739958399593996039961399623996339964399653996639967399683996939970399713997239973399743997539976399773997839979399803998139982399833998439985399863998739988399893999039991399923999339994399953999639997399983999940000400014000240003400044000540006400074000840009400104001140012400134001440015400164001740018400194002040021400224002340024400254002640027400284002940030400314003240033400344003540036400374003840039400404004140042400434004440045400464004740048400494005040051400524005340054400554005640057400584005940060400614006240063400644006540066400674006840069400704007140072400734007440075400764007740078400794008040081400824008340084400854008640087400884008940090400914009240093400944009540096400974009840099401004010140102401034010440105401064010740108401094011040111401124011340114401154011640117401184011940120401214012240123401244012540126401274012840129401304013140132401334013440135401364013740138401394014040141401424014340144401454014640147401484014940150401514015240153401544015540156401574015840159401604016140162401634016440165401664016740168401694017040171401724017340174401754017640177401784017940180401814018240183401844018540186401874018840189401904019140192401934019440195401964019740198401994020040201402024020340204402054020640207402084020940210402114021240213402144021540216402174021840219402204022140222402234022440225402264022740228402294023040231402324023340234402354023640237402384023940240402414024240243402444024540246402474024840249402504025140252402534025440255402564025740258402594026040261402624026340264402654026640267402684026940270402714027240273402744027540276402774027840279402804028140282402834028440285402864028740288402894029040291402924029340294402954029640297402984029940300403014030240303403044030540306403074030840309403104031140312403134031440315403164031740318403194032040321403224032340324403254032640327403284032940330403314033240333403344033540336403374033840339403404034140342403434034440345403464034740348403494035040351403524035340354403554035640357403584035940360403614036240363403644036540366403674036840369403704037140372403734037440375403764037740378403794038040381403824038340384403854038640387403884038940390403914039240393403944039540396403974039840399404004040140402404034040440405404064040740408404094041040411404124041340414404154041640417404184041940420404214042240423404244042540426404274042840429404304043140432404334043440435404364043740438404394044040441404424044340444404454044640447404484044940450404514045240453404544045540456404574045840459404604046140462404634046440465404664046740468404694047040471404724047340474404754047640477404784047940480404814048240483404844048540486404874048840489404904049140492404934049440495404964049740498404994050040501405024050340504405054050640507405084050940510405114051240513405144051540516405174051840519405204052140522405234052440525405264052740528405294053040531405324053340534405354053640537405384053940540405414054240543405444054540546405474054840549405504055140552405534055440555405564055740558405594056040561405624056340564405654056640567405684056940570405714057240573405744057540576405774057840579405804058140582405834058440585405864058740588405894059040591405924059340594405954059640597405984059940600406014060240603406044060540606406074060840609406104061140612406134061440615406164061740618406194062040621406224062340624406254062640627406284062940630406314063240633406344063540636406374063840639406404064140642406434064440645406464064740648406494065040651406524065340654406554065640657406584065940660406614066240663406644066540666406674066840669406704067140672406734067440675406764067740678406794068040681406824068340684406854068640687406884068940690406914069240693406944069540696406974069840699407004070140702407034070440705407064070740708407094071040711407124071340714407154071640717407184071940720407214072240723407244072540726407274072840729407304073140732407334073440735407364073740738407394074040741407424074340744407454074640747407484074940750407514075240753407544075540756407574075840759407604076140762407634076440765407664076740768407694077040771407724077340774407754077640777407784077940780407814078240783407844078540786407874078840789407904079140792407934079440795407964079740798407994080040801408024080340804408054080640807408084080940810408114081240813408144081540816408174081840819408204082140822408234082440825408264082740828408294083040831408324083340834408354083640837408384083940840408414084240843408444084540846408474084840849408504085140852408534085440855408564085740858408594086040861408624086340864408654086640867408684086940870408714087240873408744087540876408774087840879408804088140882408834088440885408864088740888408894089040891408924089340894408954089640897408984089940900409014090240903409044090540906409074090840909409104091140912409134091440915409164091740918409194092040921409224092340924409254092640927409284092940930409314093240933409344093540936409374093840939409404094140942409434094440945409464094740948409494095040951409524095340954409554095640957409584095940960409614096240963409644096540966409674096840969409704097140972409734097440975409764097740978409794098040981409824098340984409854098640987409884098940990409914099240993409944099540996409974099840999410004100141002410034100441005410064100741008410094101041011410124101341014410154101641017410184101941020410214102241023410244102541026410274102841029410304103141032410334103441035410364103741038410394104041041410424104341044410454104641047410484104941050410514105241053410544105541056410574105841059410604106141062410634106441065410664106741068410694107041071410724107341074410754107641077410784107941080410814108241083410844108541086410874108841089410904109141092410934109441095410964109741098410994110041101411024110341104411054110641107411084110941110411114111241113411144111541116411174111841119411204112141122411234112441125411264112741128411294113041131411324113341134411354113641137411384113941140411414114241143411444114541146411474114841149411504115141152411534115441155411564115741158411594116041161411624116341164411654116641167411684116941170411714117241173411744117541176411774117841179411804118141182411834118441185411864118741188411894119041191411924119341194411954119641197411984119941200412014120241203412044120541206412074120841209412104121141212412134121441215412164121741218412194122041221412224122341224412254122641227412284122941230412314123241233412344123541236412374123841239412404124141242412434124441245412464124741248412494125041251412524125341254412554125641257412584125941260412614126241263412644126541266412674126841269412704127141272412734127441275412764127741278412794128041281412824128341284412854128641287412884128941290412914129241293412944129541296412974129841299413004130141302413034130441305413064130741308413094131041311413124131341314413154131641317413184131941320413214132241323413244132541326413274132841329413304133141332413334133441335413364133741338413394134041341413424134341344413454134641347413484134941350413514135241353413544135541356413574135841359413604136141362413634136441365413664136741368413694137041371413724137341374413754137641377413784137941380413814138241383413844138541386413874138841389413904139141392413934139441395413964139741398413994140041401414024140341404414054140641407414084140941410414114141241413414144141541416414174141841419414204142141422414234142441425414264142741428414294143041431414324143341434414354143641437414384143941440414414144241443414444144541446414474144841449414504145141452414534145441455414564145741458414594146041461414624146341464414654146641467414684146941470414714147241473414744147541476414774147841479414804148141482414834148441485414864148741488414894149041491414924149341494414954149641497414984149941500415014150241503415044150541506415074150841509415104151141512415134151441515415164151741518415194152041521415224152341524415254152641527415284152941530415314153241533415344153541536415374153841539415404154141542415434154441545415464154741548415494155041551415524155341554415554155641557415584155941560415614156241563415644156541566415674156841569415704157141572415734157441575415764157741578415794158041581415824158341584415854158641587415884158941590415914159241593415944159541596415974159841599416004160141602416034160441605416064160741608416094161041611416124161341614416154161641617416184161941620416214162241623416244162541626416274162841629416304163141632416334163441635416364163741638416394164041641416424164341644416454164641647416484164941650416514165241653416544165541656416574165841659416604166141662416634166441665416664166741668416694167041671416724167341674416754167641677416784167941680416814168241683416844168541686416874168841689416904169141692416934169441695416964169741698416994170041701417024170341704417054170641707417084170941710417114171241713417144171541716417174171841719417204172141722417234172441725417264172741728417294173041731417324173341734417354173641737417384173941740417414174241743417444174541746417474174841749417504175141752417534175441755417564175741758417594176041761417624176341764417654176641767417684176941770417714177241773417744177541776417774177841779417804178141782417834178441785417864178741788417894179041791417924179341794417954179641797417984179941800418014180241803418044180541806418074180841809418104181141812418134181441815418164181741818418194182041821418224182341824418254182641827418284182941830418314183241833418344183541836418374183841839418404184141842418434184441845418464184741848418494185041851418524185341854418554185641857418584185941860418614186241863418644186541866418674186841869418704187141872418734187441875418764187741878418794188041881418824188341884418854188641887418884188941890418914189241893418944189541896418974189841899419004190141902419034190441905419064190741908419094191041911419124191341914419154191641917419184191941920419214192241923419244192541926419274192841929419304193141932419334193441935419364193741938419394194041941419424194341944419454194641947419484194941950419514195241953419544195541956419574195841959419604196141962419634196441965419664196741968419694197041971419724197341974419754197641977419784197941980419814198241983419844198541986419874198841989419904199141992419934199441995419964199741998419994200042001420024200342004420054200642007420084200942010420114201242013420144201542016420174201842019420204202142022420234202442025420264202742028420294203042031420324203342034420354203642037420384203942040420414204242043420444204542046420474204842049420504205142052420534205442055420564205742058420594206042061420624206342064420654206642067420684206942070420714207242073420744207542076420774207842079420804208142082420834208442085420864208742088420894209042091420924209342094420954209642097420984209942100421014210242103421044210542106421074210842109421104211142112421134211442115421164211742118421194212042121421224212342124421254212642127421284212942130421314213242133421344213542136421374213842139421404214142142421434214442145421464214742148421494215042151421524215342154421554215642157421584215942160421614216242163421644216542166421674216842169421704217142172421734217442175421764217742178421794218042181421824218342184421854218642187421884218942190421914219242193421944219542196421974219842199422004220142202422034220442205422064220742208422094221042211422124221342214422154221642217422184221942220422214222242223422244222542226422274222842229422304223142232422334223442235422364223742238422394224042241422424224342244422454224642247422484224942250422514225242253422544225542256422574225842259422604226142262422634226442265422664226742268422694227042271422724227342274422754227642277422784227942280422814228242283422844228542286422874228842289422904229142292422934229442295422964229742298422994230042301423024230342304423054230642307423084230942310423114231242313423144231542316423174231842319423204232142322423234232442325423264232742328423294233042331423324233342334423354233642337423384233942340423414234242343423444234542346423474234842349423504235142352423534235442355423564235742358423594236042361423624236342364423654236642367423684236942370423714237242373423744237542376423774237842379423804238142382423834238442385423864238742388423894239042391423924239342394423954239642397423984239942400424014240242403424044240542406424074240842409424104241142412424134241442415424164241742418424194242042421424224242342424424254242642427424284242942430424314243242433424344243542436424374243842439424404244142442424434244442445424464244742448424494245042451424524245342454424554245642457424584245942460424614246242463424644246542466424674246842469424704247142472424734247442475424764247742478424794248042481424824248342484424854248642487424884248942490424914249242493424944249542496424974249842499425004250142502425034250442505425064250742508425094251042511425124251342514425154251642517425184251942520425214252242523425244252542526425274252842529425304253142532425334253442535425364253742538425394254042541425424254342544425454254642547425484254942550425514255242553425544255542556425574255842559425604256142562425634256442565425664256742568425694257042571425724257342574425754257642577425784257942580425814258242583425844258542586425874258842589425904259142592425934259442595425964259742598425994260042601426024260342604426054260642607426084260942610426114261242613426144261542616426174261842619426204262142622426234262442625426264262742628426294263042631426324263342634426354263642637426384263942640426414264242643426444264542646426474264842649426504265142652426534265442655426564265742658426594266042661426624266342664426654266642667426684266942670426714267242673426744267542676426774267842679426804268142682426834268442685426864268742688426894269042691426924269342694426954269642697426984269942700427014270242703427044270542706427074270842709427104271142712427134271442715427164271742718427194272042721427224272342724427254272642727427284272942730427314273242733427344273542736427374273842739427404274142742427434274442745427464274742748427494275042751427524275342754427554275642757427584275942760427614276242763427644276542766427674276842769427704277142772427734277442775427764277742778427794278042781427824278342784427854278642787427884278942790427914279242793427944279542796427974279842799428004280142802428034280442805428064280742808428094281042811428124281342814428154281642817428184281942820428214282242823428244282542826428274282842829428304283142832428334283442835428364283742838428394284042841428424284342844428454284642847428484284942850428514285242853428544285542856428574285842859428604286142862428634286442865428664286742868428694287042871428724287342874428754287642877428784287942880428814288242883428844288542886428874288842889428904289142892428934289442895428964289742898428994290042901429024290342904429054290642907429084290942910429114291242913429144291542916429174291842919429204292142922429234292442925429264292742928429294293042931429324293342934429354293642937429384293942940429414294242943429444294542946429474294842949429504295142952429534295442955429564295742958429594296042961429624296342964429654296642967429684296942970429714297242973429744297542976429774297842979429804298142982429834298442985429864298742988429894299042991429924299342994429954299642997429984299943000430014300243003430044300543006430074300843009430104301143012430134301443015430164301743018430194302043021430224302343024430254302643027430284302943030430314303243033430344303543036430374303843039430404304143042430434304443045430464304743048430494305043051430524305343054430554305643057430584305943060430614306243063430644306543066430674306843069430704307143072430734307443075430764307743078430794308043081430824308343084430854308643087430884308943090430914309243093430944309543096430974309843099431004310143102431034310443105431064310743108431094311043111431124311343114431154311643117431184311943120431214312243123431244312543126431274312843129431304313143132431334313443135431364313743138431394314043141431424314343144431454314643147431484314943150431514315243153431544315543156431574315843159431604316143162431634316443165431664316743168431694317043171431724317343174431754317643177431784317943180431814318243183431844318543186431874318843189431904319143192431934319443195431964319743198431994320043201432024320343204432054320643207432084320943210432114321243213432144321543216432174321843219432204322143222432234322443225432264322743228432294323043231432324323343234432354323643237432384323943240432414324243243432444324543246432474324843249432504325143252432534325443255432564325743258432594326043261432624326343264432654326643267432684326943270432714327243273432744327543276432774327843279432804328143282432834328443285432864328743288432894329043291432924329343294432954329643297432984329943300433014330243303433044330543306433074330843309433104331143312433134331443315433164331743318433194332043321433224332343324433254332643327433284332943330433314333243333433344333543336433374333843339433404334143342433434334443345433464334743348433494335043351433524335343354433554335643357433584335943360433614336243363433644336543366433674336843369433704337143372433734337443375433764337743378433794338043381433824338343384433854338643387433884338943390433914339243393433944339543396433974339843399434004340143402434034340443405434064340743408434094341043411434124341343414434154341643417434184341943420434214342243423434244342543426434274342843429434304343143432434334343443435434364343743438434394344043441434424344343444434454344643447434484344943450434514345243453434544345543456434574345843459434604346143462434634346443465434664346743468434694347043471434724347343474434754347643477434784347943480434814348243483434844348543486434874348843489434904349143492434934349443495434964349743498434994350043501435024350343504435054350643507435084350943510435114351243513435144351543516435174351843519435204352143522435234352443525435264352743528435294353043531435324353343534435354353643537435384353943540435414354243543435444354543546435474354843549435504355143552435534355443555435564355743558435594356043561435624356343564435654356643567435684356943570435714357243573435744357543576435774357843579435804358143582435834358443585435864358743588435894359043591435924359343594435954359643597435984359943600436014360243603436044360543606436074360843609436104361143612436134361443615436164361743618436194362043621436224362343624436254362643627436284362943630436314363243633436344363543636436374363843639436404364143642436434364443645436464364743648436494365043651436524365343654436554365643657436584365943660436614366243663436644366543666436674366843669436704367143672436734367443675436764367743678436794368043681436824368343684436854368643687436884368943690436914369243693436944369543696436974369843699437004370143702437034370443705437064370743708437094371043711437124371343714437154371643717437184371943720437214372243723437244372543726437274372843729437304373143732437334373443735437364373743738437394374043741437424374343744437454374643747437484374943750437514375243753437544375543756437574375843759437604376143762437634376443765437664376743768437694377043771437724377343774437754377643777437784377943780437814378243783437844378543786437874378843789437904379143792437934379443795437964379743798437994380043801438024380343804438054380643807438084380943810438114381243813438144381543816438174381843819438204382143822438234382443825438264382743828438294383043831438324383343834438354383643837438384383943840438414384243843438444384543846438474384843849438504385143852438534385443855438564385743858438594386043861438624386343864438654386643867438684386943870438714387243873438744387543876438774387843879438804388143882438834388443885438864388743888438894389043891438924389343894438954389643897438984389943900439014390243903439044390543906439074390843909439104391143912439134391443915439164391743918439194392043921439224392343924439254392643927439284392943930439314393243933439344393543936439374393843939439404394143942439434394443945439464394743948439494395043951439524395343954439554395643957439584395943960439614396243963439644396543966439674396843969439704397143972439734397443975439764397743978439794398043981439824398343984439854398643987439884398943990439914399243993439944399543996439974399843999440004400144002440034400444005440064400744008440094401044011440124401344014440154401644017440184401944020440214402244023440244402544026440274402844029440304403144032440334403444035440364403744038440394404044041440424404344044440454404644047440484404944050440514405244053440544405544056440574405844059440604406144062440634406444065440664406744068440694407044071440724407344074440754407644077440784407944080440814408244083440844408544086440874408844089440904409144092440934409444095440964409744098440994410044101441024410344104441054410644107441084410944110441114411244113441144411544116441174411844119441204412144122441234412444125441264412744128441294413044131441324413344134441354413644137441384413944140441414414244143441444414544146441474414844149441504415144152441534415444155441564415744158441594416044161441624416344164441654416644167441684416944170441714417244173441744417544176441774417844179441804418144182441834418444185441864418744188441894419044191441924419344194441954419644197441984419944200442014420244203442044420544206442074420844209442104421144212442134421444215442164421744218442194422044221442224422344224442254422644227442284422944230442314423244233442344423544236442374423844239442404424144242442434424444245442464424744248442494425044251442524425344254442554425644257442584425944260442614426244263442644426544266442674426844269442704427144272442734427444275442764427744278442794428044281442824428344284442854428644287442884428944290442914429244293442944429544296442974429844299443004430144302443034430444305443064430744308443094431044311443124431344314443154431644317443184431944320443214432244323443244432544326443274432844329443304433144332443334433444335443364433744338443394434044341443424434344344443454434644347443484434944350443514435244353443544435544356443574435844359443604436144362443634436444365443664436744368443694437044371443724437344374443754437644377443784437944380443814438244383443844438544386443874438844389443904439144392443934439444395443964439744398443994440044401444024440344404444054440644407444084440944410444114441244413444144441544416444174441844419444204442144422444234442444425444264442744428444294443044431444324443344434444354443644437444384443944440444414444244443444444444544446444474444844449444504445144452444534445444455444564445744458444594446044461444624446344464444654446644467444684446944470444714447244473444744447544476444774447844479444804448144482444834448444485444864448744488444894449044491444924449344494444954449644497444984449944500445014450244503445044450544506445074450844509445104451144512445134451444515445164451744518445194452044521445224452344524445254452644527445284452944530445314453244533445344453544536445374453844539445404454144542445434454444545445464454744548445494455044551445524455344554445554455644557445584455944560445614456244563445644456544566445674456844569445704457144572445734457444575445764457744578445794458044581445824458344584445854458644587445884458944590445914459244593445944459544596445974459844599446004460144602446034460444605446064460744608446094461044611446124461344614446154461644617446184461944620446214462244623446244462544626446274462844629446304463144632446334463444635446364463744638446394464044641446424464344644446454464644647446484464944650446514465244653446544465544656446574465844659446604466144662446634466444665446664466744668446694467044671446724467344674446754467644677446784467944680446814468244683446844468544686446874468844689446904469144692446934469444695446964469744698446994470044701447024470344704447054470644707447084470944710447114471244713447144471544716447174471844719447204472144722447234472444725447264472744728447294473044731447324473344734447354473644737447384473944740447414474244743447444474544746447474474844749447504475144752447534475444755447564475744758447594476044761447624476344764447654476644767447684476944770447714477244773447744477544776447774477844779447804478144782447834478444785447864478744788447894479044791447924479344794447954479644797447984479944800448014480244803448044480544806448074480844809448104481144812448134481444815448164481744818448194482044821448224482344824448254482644827448284482944830448314483244833448344483544836448374483844839448404484144842448434484444845448464484744848448494485044851448524485344854448554485644857448584485944860448614486244863448644486544866448674486844869448704487144872448734487444875448764487744878448794488044881448824488344884448854488644887448884488944890448914489244893448944489544896448974489844899449004490144902449034490444905449064490744908449094491044911449124491344914449154491644917449184491944920449214492244923449244492544926449274492844929449304493144932449334493444935449364493744938449394494044941449424494344944449454494644947449484494944950449514495244953449544495544956449574495844959449604496144962449634496444965449664496744968449694497044971449724497344974449754497644977449784497944980449814498244983449844498544986449874498844989449904499144992449934499444995449964499744998449994500045001450024500345004450054500645007450084500945010450114501245013450144501545016450174501845019450204502145022450234502445025450264502745028450294503045031450324503345034450354503645037450384503945040450414504245043450444504545046450474504845049450504505145052450534505445055450564505745058450594506045061450624506345064450654506645067450684506945070450714507245073450744507545076450774507845079450804508145082450834508445085450864508745088450894509045091450924509345094450954509645097450984509945100451014510245103451044510545106451074510845109451104511145112451134511445115451164511745118451194512045121451224512345124451254512645127451284512945130451314513245133451344513545136451374513845139451404514145142451434514445145451464514745148451494515045151451524515345154451554515645157451584515945160451614516245163451644516545166451674516845169451704517145172451734517445175451764517745178451794518045181451824518345184451854518645187451884518945190451914519245193451944519545196451974519845199452004520145202452034520445205452064520745208452094521045211452124521345214452154521645217452184521945220452214522245223452244522545226452274522845229452304523145232452334523445235452364523745238452394524045241452424524345244452454524645247452484524945250452514525245253452544525545256452574525845259452604526145262452634526445265452664526745268452694527045271452724527345274452754527645277452784527945280452814528245283452844528545286452874528845289452904529145292452934529445295452964529745298452994530045301453024530345304453054530645307453084530945310453114531245313453144531545316453174531845319453204532145322453234532445325453264532745328453294533045331453324533345334453354533645337453384533945340453414534245343453444534545346453474534845349453504535145352453534535445355453564535745358453594536045361453624536345364453654536645367453684536945370453714537245373453744537545376453774537845379453804538145382453834538445385453864538745388453894539045391453924539345394453954539645397453984539945400454014540245403454044540545406454074540845409454104541145412454134541445415454164541745418454194542045421454224542345424454254542645427454284542945430454314543245433454344543545436454374543845439454404544145442454434544445445454464544745448454494545045451454524545345454454554545645457454584545945460454614546245463454644546545466454674546845469454704547145472454734547445475454764547745478454794548045481454824548345484454854548645487454884548945490454914549245493454944549545496454974549845499455004550145502455034550445505455064550745508455094551045511455124551345514455154551645517455184551945520455214552245523455244552545526455274552845529455304553145532455334553445535455364553745538455394554045541455424554345544455454554645547455484554945550455514555245553455544555545556455574555845559455604556145562455634556445565455664556745568455694557045571455724557345574455754557645577455784557945580455814558245583455844558545586455874558845589455904559145592455934559445595455964559745598455994560045601456024560345604456054560645607456084560945610456114561245613456144561545616456174561845619456204562145622456234562445625456264562745628456294563045631456324563345634456354563645637456384563945640456414564245643456444564545646456474564845649456504565145652456534565445655456564565745658456594566045661456624566345664456654566645667456684566945670456714567245673456744567545676456774567845679456804568145682456834568445685456864568745688456894569045691456924569345694456954569645697456984569945700457014570245703457044570545706457074570845709457104571145712457134571445715457164571745718457194572045721457224572345724457254572645727457284572945730457314573245733457344573545736457374573845739457404574145742457434574445745457464574745748457494575045751457524575345754457554575645757457584575945760457614576245763457644576545766457674576845769457704577145772457734577445775457764577745778457794578045781457824578345784457854578645787457884578945790457914579245793457944579545796457974579845799458004580145802458034580445805458064580745808458094581045811458124581345814458154581645817458184581945820458214582245823458244582545826458274582845829458304583145832458334583445835458364583745838458394584045841458424584345844458454584645847458484584945850458514585245853458544585545856458574585845859458604586145862458634586445865458664586745868458694587045871458724587345874458754587645877458784587945880458814588245883458844588545886458874588845889458904589145892458934589445895458964589745898458994590045901459024590345904459054590645907459084590945910459114591245913459144591545916459174591845919459204592145922459234592445925459264592745928459294593045931459324593345934459354593645937459384593945940459414594245943459444594545946459474594845949459504595145952459534595445955459564595745958459594596045961459624596345964459654596645967459684596945970459714597245973459744597545976459774597845979459804598145982459834598445985459864598745988459894599045991459924599345994459954599645997459984599946000460014600246003460044600546006460074600846009460104601146012460134601446015460164601746018460194602046021460224602346024460254602646027460284602946030460314603246033460344603546036460374603846039460404604146042460434604446045460464604746048460494605046051460524605346054460554605646057460584605946060460614606246063460644606546066460674606846069460704607146072460734607446075460764607746078460794608046081460824608346084460854608646087460884608946090460914609246093460944609546096460974609846099461004610146102461034610446105461064610746108461094611046111461124611346114461154611646117461184611946120461214612246123461244612546126461274612846129461304613146132461334613446135461364613746138461394614046141461424614346144461454614646147461484614946150461514615246153461544615546156461574615846159461604616146162461634616446165461664616746168461694617046171461724617346174461754617646177461784617946180461814618246183461844618546186461874618846189461904619146192461934619446195461964619746198461994620046201462024620346204462054620646207462084620946210462114621246213462144621546216462174621846219462204622146222462234622446225462264622746228462294623046231462324623346234462354623646237462384623946240462414624246243462444624546246462474624846249462504625146252462534625446255462564625746258462594626046261462624626346264462654626646267462684626946270462714627246273462744627546276462774627846279462804628146282462834628446285462864628746288462894629046291462924629346294462954629646297462984629946300463014630246303463044630546306463074630846309463104631146312463134631446315463164631746318463194632046321463224632346324463254632646327463284632946330463314633246333463344633546336463374633846339463404634146342463434634446345463464634746348463494635046351463524635346354463554635646357463584635946360463614636246363463644636546366463674636846369463704637146372463734637446375463764637746378463794638046381463824638346384463854638646387463884638946390463914639246393463944639546396463974639846399464004640146402464034640446405464064640746408464094641046411464124641346414464154641646417464184641946420464214642246423464244642546426464274642846429464304643146432464334643446435464364643746438464394644046441464424644346444464454644646447464484644946450464514645246453464544645546456464574645846459464604646146462464634646446465464664646746468464694647046471464724647346474464754647646477464784647946480464814648246483464844648546486464874648846489464904649146492464934649446495464964649746498464994650046501465024650346504465054650646507465084650946510465114651246513465144651546516465174651846519465204652146522465234652446525465264652746528465294653046531465324653346534465354653646537465384653946540465414654246543465444654546546465474654846549465504655146552465534655446555465564655746558465594656046561465624656346564465654656646567465684656946570465714657246573465744657546576465774657846579465804658146582465834658446585465864658746588465894659046591465924659346594465954659646597465984659946600466014660246603466044660546606466074660846609466104661146612466134661446615466164661746618466194662046621466224662346624466254662646627466284662946630466314663246633466344663546636466374663846639466404664146642466434664446645466464664746648466494665046651466524665346654466554665646657466584665946660466614666246663466644666546666466674666846669466704667146672466734667446675466764667746678466794668046681466824668346684466854668646687466884668946690466914669246693466944669546696466974669846699467004670146702467034670446705467064670746708467094671046711467124671346714467154671646717467184671946720467214672246723467244672546726467274672846729467304673146732467334673446735467364673746738467394674046741467424674346744467454674646747467484674946750467514675246753467544675546756467574675846759467604676146762467634676446765467664676746768467694677046771467724677346774467754677646777467784677946780467814678246783467844678546786467874678846789467904679146792467934679446795467964679746798467994680046801468024680346804468054680646807468084680946810468114681246813468144681546816468174681846819468204682146822468234682446825468264682746828468294683046831468324683346834468354683646837468384683946840468414684246843468444684546846468474684846849468504685146852468534685446855468564685746858468594686046861468624686346864468654686646867468684686946870468714687246873468744687546876468774687846879468804688146882468834688446885468864688746888468894689046891468924689346894468954689646897468984689946900469014690246903469044690546906469074690846909469104691146912469134691446915469164691746918469194692046921469224692346924469254692646927469284692946930469314693246933469344693546936469374693846939469404694146942469434694446945469464694746948469494695046951469524695346954469554695646957469584695946960469614696246963469644696546966469674696846969469704697146972469734697446975469764697746978469794698046981469824698346984469854698646987469884698946990469914699246993469944699546996469974699846999470004700147002470034700447005470064700747008470094701047011470124701347014470154701647017470184701947020470214702247023470244702547026470274702847029470304703147032470334703447035470364703747038470394704047041470424704347044470454704647047470484704947050470514705247053470544705547056470574705847059470604706147062470634706447065470664706747068470694707047071470724707347074470754707647077470784707947080470814708247083470844708547086470874708847089470904709147092470934709447095470964709747098470994710047101471024710347104471054710647107471084710947110471114711247113471144711547116471174711847119471204712147122471234712447125471264712747128471294713047131471324713347134471354713647137471384713947140471414714247143471444714547146471474714847149471504715147152471534715447155471564715747158471594716047161471624716347164471654716647167471684716947170471714717247173471744717547176471774717847179471804718147182471834718447185471864718747188471894719047191471924719347194471954719647197471984719947200472014720247203472044720547206472074720847209472104721147212472134721447215472164721747218472194722047221472224722347224472254722647227472284722947230472314723247233472344723547236472374723847239472404724147242472434724447245472464724747248472494725047251472524725347254472554725647257472584725947260472614726247263472644726547266472674726847269472704727147272472734727447275472764727747278472794728047281472824728347284472854728647287472884728947290472914729247293472944729547296472974729847299473004730147302473034730447305473064730747308473094731047311473124731347314473154731647317473184731947320473214732247323473244732547326473274732847329473304733147332473334733447335473364733747338473394734047341473424734347344473454734647347473484734947350473514735247353473544735547356473574735847359473604736147362473634736447365473664736747368473694737047371473724737347374473754737647377473784737947380473814738247383473844738547386473874738847389473904739147392473934739447395473964739747398473994740047401474024740347404474054740647407474084740947410474114741247413474144741547416474174741847419474204742147422474234742447425474264742747428474294743047431474324743347434474354743647437474384743947440474414744247443474444744547446474474744847449474504745147452474534745447455474564745747458474594746047461474624746347464474654746647467474684746947470474714747247473474744747547476474774747847479474804748147482474834748447485474864748747488474894749047491474924749347494474954749647497474984749947500475014750247503475044750547506475074750847509475104751147512475134751447515475164751747518475194752047521475224752347524475254752647527475284752947530475314753247533475344753547536475374753847539475404754147542475434754447545475464754747548475494755047551475524755347554475554755647557475584755947560475614756247563475644756547566475674756847569475704757147572475734757447575475764757747578475794758047581475824758347584475854758647587475884758947590475914759247593475944759547596475974759847599476004760147602476034760447605476064760747608476094761047611476124761347614476154761647617476184761947620476214762247623476244762547626476274762847629476304763147632476334763447635476364763747638476394764047641476424764347644476454764647647476484764947650476514765247653476544765547656476574765847659476604766147662476634766447665476664766747668476694767047671476724767347674476754767647677476784767947680476814768247683476844768547686476874768847689476904769147692476934769447695476964769747698476994770047701477024770347704477054770647707477084770947710477114771247713477144771547716477174771847719477204772147722477234772447725477264772747728477294773047731477324773347734477354773647737477384773947740477414774247743477444774547746477474774847749477504775147752477534775447755477564775747758477594776047761477624776347764477654776647767477684776947770477714777247773477744777547776477774777847779477804778147782477834778447785477864778747788477894779047791477924779347794477954779647797477984779947800478014780247803478044780547806478074780847809478104781147812478134781447815478164781747818478194782047821478224782347824478254782647827478284782947830478314783247833478344783547836478374783847839478404784147842478434784447845478464784747848478494785047851478524785347854478554785647857478584785947860478614786247863478644786547866478674786847869478704787147872478734787447875478764787747878478794788047881478824788347884478854788647887478884788947890478914789247893478944789547896478974789847899479004790147902479034790447905479064790747908479094791047911479124791347914479154791647917479184791947920479214792247923479244792547926479274792847929479304793147932479334793447935479364793747938479394794047941479424794347944479454794647947479484794947950479514795247953479544795547956479574795847959479604796147962479634796447965479664796747968479694797047971479724797347974479754797647977479784797947980479814798247983479844798547986479874798847989479904799147992479934799447995479964799747998479994800048001480024800348004480054800648007480084800948010480114801248013480144801548016480174801848019480204802148022480234802448025480264802748028480294803048031480324803348034480354803648037480384803948040480414804248043480444804548046480474804848049480504805148052480534805448055480564805748058480594806048061480624806348064480654806648067480684806948070480714807248073480744807548076480774807848079480804808148082480834808448085480864808748088480894809048091480924809348094480954809648097480984809948100481014810248103481044810548106481074810848109481104811148112481134811448115481164811748118481194812048121481224812348124481254812648127481284812948130481314813248133481344813548136481374813848139481404814148142481434814448145481464814748148481494815048151481524815348154481554815648157481584815948160481614816248163481644816548166481674816848169481704817148172481734817448175481764817748178481794818048181481824818348184481854818648187481884818948190481914819248193481944819548196481974819848199482004820148202482034820448205482064820748208482094821048211482124821348214482154821648217482184821948220482214822248223482244822548226482274822848229482304823148232482334823448235482364823748238482394824048241482424824348244482454824648247482484824948250482514825248253482544825548256482574825848259482604826148262482634826448265482664826748268482694827048271482724827348274482754827648277482784827948280482814828248283482844828548286482874828848289482904829148292482934829448295482964829748298482994830048301483024830348304483054830648307483084830948310483114831248313483144831548316483174831848319483204832148322483234832448325483264832748328483294833048331483324833348334483354833648337483384833948340483414834248343483444834548346483474834848349483504835148352483534835448355483564835748358483594836048361483624836348364483654836648367483684836948370483714837248373483744837548376483774837848379483804838148382483834838448385483864838748388483894839048391483924839348394483954839648397483984839948400484014840248403484044840548406484074840848409484104841148412484134841448415484164841748418484194842048421484224842348424484254842648427484284842948430484314843248433484344843548436484374843848439484404844148442484434844448445484464844748448484494845048451484524845348454484554845648457484584845948460484614846248463484644846548466484674846848469 |
- 'use strict';
- /**
- * @license Angular v19.2.13
- * (c) 2010-2025 Google LLC. https://angular.io/
- * License: MIT
- */
- 'use strict';
- var ts = require('typescript');
- require('os');
- var fs$1 = require('fs');
- var module$1 = require('module');
- var p = require('path');
- var url = require('url');
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
- function _interopNamespaceDefault(e) {
- var n = Object.create(null);
- if (e) {
- Object.keys(e).forEach(function (k) {
- if (k !== 'default') {
- var d = Object.getOwnPropertyDescriptor(e, k);
- Object.defineProperty(n, k, d.get ? d : {
- enumerable: true,
- get: function () { return e[k]; }
- });
- }
- });
- }
- n.default = e;
- return Object.freeze(n);
- }
- var p__namespace = /*#__PURE__*/_interopNamespaceDefault(p);
- const _SELECTOR_REGEXP = new RegExp('(\\:not\\()|' + // 1: ":not("
- '(([\\.\\#]?)[-\\w]+)|' + // 2: "tag"; 3: "."/"#";
- // "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
- // 4: attribute; 5: attribute_string; 6: attribute_value
- '(?:\\[([-.\\w*\\\\$]+)(?:=(["\']?)([^\\]"\']*)\\5)?\\])|' + // "[name]", "[name=value]",
- // "[name="value"]",
- // "[name='value']"
- '(\\))|' + // 7: ")"
- '(\\s*,\\s*)', // 8: ","
- 'g');
- /**
- * A css selector contains an element name,
- * css classes and attribute/value pairs with the purpose
- * of selecting subsets out of them.
- */
- class CssSelector {
- element = null;
- classNames = [];
- /**
- * The selectors are encoded in pairs where:
- * - even locations are attribute names
- * - odd locations are attribute values.
- *
- * Example:
- * Selector: `[key1=value1][key2]` would parse to:
- * ```
- * ['key1', 'value1', 'key2', '']
- * ```
- */
- attrs = [];
- notSelectors = [];
- static parse(selector) {
- const results = [];
- const _addResult = (res, cssSel) => {
- if (cssSel.notSelectors.length > 0 &&
- !cssSel.element &&
- cssSel.classNames.length == 0 &&
- cssSel.attrs.length == 0) {
- cssSel.element = '*';
- }
- res.push(cssSel);
- };
- let cssSelector = new CssSelector();
- let match;
- let current = cssSelector;
- let inNot = false;
- _SELECTOR_REGEXP.lastIndex = 0;
- while ((match = _SELECTOR_REGEXP.exec(selector))) {
- if (match[1 /* SelectorRegexp.NOT */]) {
- if (inNot) {
- throw new Error('Nesting :not in a selector is not allowed');
- }
- inNot = true;
- current = new CssSelector();
- cssSelector.notSelectors.push(current);
- }
- const tag = match[2 /* SelectorRegexp.TAG */];
- if (tag) {
- const prefix = match[3 /* SelectorRegexp.PREFIX */];
- if (prefix === '#') {
- // #hash
- current.addAttribute('id', tag.slice(1));
- }
- else if (prefix === '.') {
- // Class
- current.addClassName(tag.slice(1));
- }
- else {
- // Element
- current.setElement(tag);
- }
- }
- const attribute = match[4 /* SelectorRegexp.ATTRIBUTE */];
- if (attribute) {
- current.addAttribute(current.unescapeAttribute(attribute), match[6 /* SelectorRegexp.ATTRIBUTE_VALUE */]);
- }
- if (match[7 /* SelectorRegexp.NOT_END */]) {
- inNot = false;
- current = cssSelector;
- }
- if (match[8 /* SelectorRegexp.SEPARATOR */]) {
- if (inNot) {
- throw new Error('Multiple selectors in :not are not supported');
- }
- _addResult(results, cssSelector);
- cssSelector = current = new CssSelector();
- }
- }
- _addResult(results, cssSelector);
- return results;
- }
- /**
- * Unescape `\$` sequences from the CSS attribute selector.
- *
- * This is needed because `$` can have a special meaning in CSS selectors,
- * but we might want to match an attribute that contains `$`.
- * [MDN web link for more
- * info](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors).
- * @param attr the attribute to unescape.
- * @returns the unescaped string.
- */
- unescapeAttribute(attr) {
- let result = '';
- let escaping = false;
- for (let i = 0; i < attr.length; i++) {
- const char = attr.charAt(i);
- if (char === '\\') {
- escaping = true;
- continue;
- }
- if (char === '$' && !escaping) {
- throw new Error(`Error in attribute selector "${attr}". ` +
- `Unescaped "$" is not supported. Please escape with "\\$".`);
- }
- escaping = false;
- result += char;
- }
- return result;
- }
- /**
- * Escape `$` sequences from the CSS attribute selector.
- *
- * This is needed because `$` can have a special meaning in CSS selectors,
- * with this method we are escaping `$` with `\$'.
- * [MDN web link for more
- * info](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors).
- * @param attr the attribute to escape.
- * @returns the escaped string.
- */
- escapeAttribute(attr) {
- return attr.replace(/\\/g, '\\\\').replace(/\$/g, '\\$');
- }
- isElementSelector() {
- return (this.hasElementSelector() &&
- this.classNames.length == 0 &&
- this.attrs.length == 0 &&
- this.notSelectors.length === 0);
- }
- hasElementSelector() {
- return !!this.element;
- }
- setElement(element = null) {
- this.element = element;
- }
- getAttrs() {
- const result = [];
- if (this.classNames.length > 0) {
- result.push('class', this.classNames.join(' '));
- }
- return result.concat(this.attrs);
- }
- addAttribute(name, value = '') {
- this.attrs.push(name, (value && value.toLowerCase()) || '');
- }
- addClassName(name) {
- this.classNames.push(name.toLowerCase());
- }
- toString() {
- let res = this.element || '';
- if (this.classNames) {
- this.classNames.forEach((klass) => (res += `.${klass}`));
- }
- if (this.attrs) {
- for (let i = 0; i < this.attrs.length; i += 2) {
- const name = this.escapeAttribute(this.attrs[i]);
- const value = this.attrs[i + 1];
- res += `[${name}${value ? '=' + value : ''}]`;
- }
- }
- this.notSelectors.forEach((notSelector) => (res += `:not(${notSelector})`));
- return res;
- }
- }
- /**
- * Reads a list of CssSelectors and allows to calculate which ones
- * are contained in a given CssSelector.
- */
- class SelectorMatcher {
- static createNotMatcher(notSelectors) {
- const notMatcher = new SelectorMatcher();
- notMatcher.addSelectables(notSelectors, null);
- return notMatcher;
- }
- _elementMap = new Map();
- _elementPartialMap = new Map();
- _classMap = new Map();
- _classPartialMap = new Map();
- _attrValueMap = new Map();
- _attrValuePartialMap = new Map();
- _listContexts = [];
- addSelectables(cssSelectors, callbackCtxt) {
- let listContext = null;
- if (cssSelectors.length > 1) {
- listContext = new SelectorListContext(cssSelectors);
- this._listContexts.push(listContext);
- }
- for (let i = 0; i < cssSelectors.length; i++) {
- this._addSelectable(cssSelectors[i], callbackCtxt, listContext);
- }
- }
- /**
- * Add an object that can be found later on by calling `match`.
- * @param cssSelector A css selector
- * @param callbackCtxt An opaque object that will be given to the callback of the `match` function
- */
- _addSelectable(cssSelector, callbackCtxt, listContext) {
- let matcher = this;
- const element = cssSelector.element;
- const classNames = cssSelector.classNames;
- const attrs = cssSelector.attrs;
- const selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
- if (element) {
- const isTerminal = attrs.length === 0 && classNames.length === 0;
- if (isTerminal) {
- this._addTerminal(matcher._elementMap, element, selectable);
- }
- else {
- matcher = this._addPartial(matcher._elementPartialMap, element);
- }
- }
- if (classNames) {
- for (let i = 0; i < classNames.length; i++) {
- const isTerminal = attrs.length === 0 && i === classNames.length - 1;
- const className = classNames[i];
- if (isTerminal) {
- this._addTerminal(matcher._classMap, className, selectable);
- }
- else {
- matcher = this._addPartial(matcher._classPartialMap, className);
- }
- }
- }
- if (attrs) {
- for (let i = 0; i < attrs.length; i += 2) {
- const isTerminal = i === attrs.length - 2;
- const name = attrs[i];
- const value = attrs[i + 1];
- if (isTerminal) {
- const terminalMap = matcher._attrValueMap;
- let terminalValuesMap = terminalMap.get(name);
- if (!terminalValuesMap) {
- terminalValuesMap = new Map();
- terminalMap.set(name, terminalValuesMap);
- }
- this._addTerminal(terminalValuesMap, value, selectable);
- }
- else {
- const partialMap = matcher._attrValuePartialMap;
- let partialValuesMap = partialMap.get(name);
- if (!partialValuesMap) {
- partialValuesMap = new Map();
- partialMap.set(name, partialValuesMap);
- }
- matcher = this._addPartial(partialValuesMap, value);
- }
- }
- }
- }
- _addTerminal(map, name, selectable) {
- let terminalList = map.get(name);
- if (!terminalList) {
- terminalList = [];
- map.set(name, terminalList);
- }
- terminalList.push(selectable);
- }
- _addPartial(map, name) {
- let matcher = map.get(name);
- if (!matcher) {
- matcher = new SelectorMatcher();
- map.set(name, matcher);
- }
- return matcher;
- }
- /**
- * Find the objects that have been added via `addSelectable`
- * whose css selector is contained in the given css selector.
- * @param cssSelector A css selector
- * @param matchedCallback This callback will be called with the object handed into `addSelectable`
- * @return boolean true if a match was found
- */
- match(cssSelector, matchedCallback) {
- let result = false;
- const element = cssSelector.element;
- const classNames = cssSelector.classNames;
- const attrs = cssSelector.attrs;
- for (let i = 0; i < this._listContexts.length; i++) {
- this._listContexts[i].alreadyMatched = false;
- }
- result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
- result =
- this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result;
- if (classNames) {
- for (let i = 0; i < classNames.length; i++) {
- const className = classNames[i];
- result =
- this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result;
- result =
- this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback) ||
- result;
- }
- }
- if (attrs) {
- for (let i = 0; i < attrs.length; i += 2) {
- const name = attrs[i];
- const value = attrs[i + 1];
- const terminalValuesMap = this._attrValueMap.get(name);
- if (value) {
- result =
- this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result;
- }
- result =
- this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result;
- const partialValuesMap = this._attrValuePartialMap.get(name);
- if (value) {
- result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result;
- }
- result =
- this._matchPartial(partialValuesMap, value, cssSelector, matchedCallback) || result;
- }
- }
- return result;
- }
- /** @internal */
- _matchTerminal(map, name, cssSelector, matchedCallback) {
- if (!map || typeof name !== 'string') {
- return false;
- }
- let selectables = map.get(name) || [];
- const starSelectables = map.get('*');
- if (starSelectables) {
- selectables = selectables.concat(starSelectables);
- }
- if (selectables.length === 0) {
- return false;
- }
- let selectable;
- let result = false;
- for (let i = 0; i < selectables.length; i++) {
- selectable = selectables[i];
- result = selectable.finalize(cssSelector, matchedCallback) || result;
- }
- return result;
- }
- /** @internal */
- _matchPartial(map, name, cssSelector, matchedCallback) {
- if (!map || typeof name !== 'string') {
- return false;
- }
- const nestedSelector = map.get(name);
- if (!nestedSelector) {
- return false;
- }
- // TODO(perf): get rid of recursion and measure again
- // TODO(perf): don't pass the whole selector into the recursion,
- // but only the not processed parts
- return nestedSelector.match(cssSelector, matchedCallback);
- }
- }
- class SelectorListContext {
- selectors;
- alreadyMatched = false;
- constructor(selectors) {
- this.selectors = selectors;
- }
- }
- // Store context to pass back selector and context when a selector is matched
- class SelectorContext {
- selector;
- cbContext;
- listContext;
- notSelectors;
- constructor(selector, cbContext, listContext) {
- this.selector = selector;
- this.cbContext = cbContext;
- this.listContext = listContext;
- this.notSelectors = selector.notSelectors;
- }
- finalize(cssSelector, callback) {
- let result = true;
- if (this.notSelectors.length > 0 && (!this.listContext || !this.listContext.alreadyMatched)) {
- const notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
- result = !notMatcher.match(cssSelector, null);
- }
- if (result && callback && (!this.listContext || !this.listContext.alreadyMatched)) {
- if (this.listContext) {
- this.listContext.alreadyMatched = true;
- }
- callback(this.selector, this.cbContext);
- }
- return result;
- }
- }
- // Attention:
- // This file duplicates types and values from @angular/core
- // so that we are able to make @angular/compiler independent of @angular/core.
- // This is important to prevent a build cycle, as @angular/core needs to
- // be compiled with the compiler.
- // Stores the default value of `emitDistinctChangesOnly` when the `emitDistinctChangesOnly` is not
- // explicitly set.
- const emitDistinctChangesOnlyDefaultValue = true;
- exports.ViewEncapsulation = void 0;
- (function (ViewEncapsulation) {
- ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated";
- // Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
- ViewEncapsulation[ViewEncapsulation["None"] = 2] = "None";
- ViewEncapsulation[ViewEncapsulation["ShadowDom"] = 3] = "ShadowDom";
- })(exports.ViewEncapsulation || (exports.ViewEncapsulation = {}));
- exports.ChangeDetectionStrategy = void 0;
- (function (ChangeDetectionStrategy) {
- ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush";
- ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default";
- })(exports.ChangeDetectionStrategy || (exports.ChangeDetectionStrategy = {}));
- /** Flags describing an input for a directive. */
- var InputFlags;
- (function (InputFlags) {
- InputFlags[InputFlags["None"] = 0] = "None";
- InputFlags[InputFlags["SignalBased"] = 1] = "SignalBased";
- InputFlags[InputFlags["HasDecoratorInputTransform"] = 2] = "HasDecoratorInputTransform";
- })(InputFlags || (InputFlags = {}));
- const CUSTOM_ELEMENTS_SCHEMA = {
- name: 'custom-elements',
- };
- const NO_ERRORS_SCHEMA = {
- name: 'no-errors-schema',
- };
- var SecurityContext;
- (function (SecurityContext) {
- SecurityContext[SecurityContext["NONE"] = 0] = "NONE";
- SecurityContext[SecurityContext["HTML"] = 1] = "HTML";
- SecurityContext[SecurityContext["STYLE"] = 2] = "STYLE";
- SecurityContext[SecurityContext["SCRIPT"] = 3] = "SCRIPT";
- SecurityContext[SecurityContext["URL"] = 4] = "URL";
- SecurityContext[SecurityContext["RESOURCE_URL"] = 5] = "RESOURCE_URL";
- })(SecurityContext || (SecurityContext = {}));
- var MissingTranslationStrategy;
- (function (MissingTranslationStrategy) {
- MissingTranslationStrategy[MissingTranslationStrategy["Error"] = 0] = "Error";
- MissingTranslationStrategy[MissingTranslationStrategy["Warning"] = 1] = "Warning";
- MissingTranslationStrategy[MissingTranslationStrategy["Ignore"] = 2] = "Ignore";
- })(MissingTranslationStrategy || (MissingTranslationStrategy = {}));
- function parserSelectorToSimpleSelector(selector) {
- const classes = selector.classNames && selector.classNames.length
- ? [8 /* SelectorFlags.CLASS */, ...selector.classNames]
- : [];
- const elementName = selector.element && selector.element !== '*' ? selector.element : '';
- return [elementName, ...selector.attrs, ...classes];
- }
- function parserSelectorToNegativeSelector(selector) {
- const classes = selector.classNames && selector.classNames.length
- ? [8 /* SelectorFlags.CLASS */, ...selector.classNames]
- : [];
- if (selector.element) {
- return [
- 1 /* SelectorFlags.NOT */ | 4 /* SelectorFlags.ELEMENT */,
- selector.element,
- ...selector.attrs,
- ...classes,
- ];
- }
- else if (selector.attrs.length) {
- return [1 /* SelectorFlags.NOT */ | 2 /* SelectorFlags.ATTRIBUTE */, ...selector.attrs, ...classes];
- }
- else {
- return selector.classNames && selector.classNames.length
- ? [1 /* SelectorFlags.NOT */ | 8 /* SelectorFlags.CLASS */, ...selector.classNames]
- : [];
- }
- }
- function parserSelectorToR3Selector(selector) {
- const positive = parserSelectorToSimpleSelector(selector);
- const negative = selector.notSelectors && selector.notSelectors.length
- ? selector.notSelectors.map((notSelector) => parserSelectorToNegativeSelector(notSelector))
- : [];
- return positive.concat(...negative);
- }
- function parseSelectorToR3Selector(selector) {
- return selector ? CssSelector.parse(selector).map(parserSelectorToR3Selector) : [];
- }
- exports.FactoryTarget = void 0;
- (function (FactoryTarget) {
- FactoryTarget[FactoryTarget["Directive"] = 0] = "Directive";
- FactoryTarget[FactoryTarget["Component"] = 1] = "Component";
- FactoryTarget[FactoryTarget["Injectable"] = 2] = "Injectable";
- FactoryTarget[FactoryTarget["Pipe"] = 3] = "Pipe";
- FactoryTarget[FactoryTarget["NgModule"] = 4] = "NgModule";
- })(exports.FactoryTarget || (exports.FactoryTarget = {}));
- var R3TemplateDependencyKind;
- (function (R3TemplateDependencyKind) {
- R3TemplateDependencyKind[R3TemplateDependencyKind["Directive"] = 0] = "Directive";
- R3TemplateDependencyKind[R3TemplateDependencyKind["Pipe"] = 1] = "Pipe";
- R3TemplateDependencyKind[R3TemplateDependencyKind["NgModule"] = 2] = "NgModule";
- })(R3TemplateDependencyKind || (R3TemplateDependencyKind = {}));
- var ViewEncapsulation;
- (function (ViewEncapsulation) {
- ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated";
- // Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
- ViewEncapsulation[ViewEncapsulation["None"] = 2] = "None";
- ViewEncapsulation[ViewEncapsulation["ShadowDom"] = 3] = "ShadowDom";
- })(ViewEncapsulation || (ViewEncapsulation = {}));
- /**
- * A lazily created TextEncoder instance for converting strings into UTF-8 bytes
- */
- let textEncoder;
- /**
- * Return the message id or compute it using the XLIFF1 digest.
- */
- function digest$1(message) {
- return message.id || computeDigest(message);
- }
- /**
- * Compute the message id using the XLIFF1 digest.
- */
- function computeDigest(message) {
- return sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
- }
- /**
- * Return the message id or compute it using the XLIFF2/XMB/$localize digest.
- */
- function decimalDigest(message) {
- return message.id || computeDecimalDigest(message);
- }
- /**
- * Compute the message id using the XLIFF2/XMB/$localize digest.
- */
- function computeDecimalDigest(message) {
- const visitor = new _SerializerIgnoreIcuExpVisitor();
- const parts = message.nodes.map((a) => a.visit(visitor, null));
- return computeMsgId(parts.join(''), message.meaning);
- }
- /**
- * Serialize the i18n ast to something xml-like in order to generate an UID.
- *
- * The visitor is also used in the i18n parser tests
- *
- * @internal
- */
- class _SerializerVisitor {
- visitText(text, context) {
- return text.value;
- }
- visitContainer(container, context) {
- return `[${container.children.map((child) => child.visit(this)).join(', ')}]`;
- }
- visitIcu(icu, context) {
- const strCases = Object.keys(icu.cases).map((k) => `${k} {${icu.cases[k].visit(this)}}`);
- return `{${icu.expression}, ${icu.type}, ${strCases.join(', ')}}`;
- }
- visitTagPlaceholder(ph, context) {
- return ph.isVoid
- ? `<ph tag name="${ph.startName}"/>`
- : `<ph tag name="${ph.startName}">${ph.children
- .map((child) => child.visit(this))
- .join(', ')}</ph name="${ph.closeName}">`;
- }
- visitPlaceholder(ph, context) {
- return ph.value ? `<ph name="${ph.name}">${ph.value}</ph>` : `<ph name="${ph.name}"/>`;
- }
- visitIcuPlaceholder(ph, context) {
- return `<ph icu name="${ph.name}">${ph.value.visit(this)}</ph>`;
- }
- visitBlockPlaceholder(ph, context) {
- return `<ph block name="${ph.startName}">${ph.children
- .map((child) => child.visit(this))
- .join(', ')}</ph name="${ph.closeName}">`;
- }
- }
- const serializerVisitor$1 = new _SerializerVisitor();
- function serializeNodes(nodes) {
- return nodes.map((a) => a.visit(serializerVisitor$1, null));
- }
- /**
- * Serialize the i18n ast to something xml-like in order to generate an UID.
- *
- * Ignore the ICU expressions so that message IDs stays identical if only the expression changes.
- *
- * @internal
- */
- class _SerializerIgnoreIcuExpVisitor extends _SerializerVisitor {
- visitIcu(icu) {
- let strCases = Object.keys(icu.cases).map((k) => `${k} {${icu.cases[k].visit(this)}}`);
- // Do not take the expression into account
- return `{${icu.type}, ${strCases.join(', ')}}`;
- }
- }
- /**
- * Compute the SHA1 of the given string
- *
- * see https://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf
- *
- * WARNING: this function has not been designed not tested with security in mind.
- * DO NOT USE IT IN A SECURITY SENSITIVE CONTEXT.
- */
- function sha1(str) {
- textEncoder ??= new TextEncoder();
- const utf8 = [...textEncoder.encode(str)];
- const words32 = bytesToWords32(utf8, Endian.Big);
- const len = utf8.length * 8;
- const w = new Uint32Array(80);
- let a = 0x67452301, b = 0xefcdab89, c = 0x98badcfe, d = 0x10325476, e = 0xc3d2e1f0;
- words32[len >> 5] |= 0x80 << (24 - (len % 32));
- words32[(((len + 64) >> 9) << 4) + 15] = len;
- for (let i = 0; i < words32.length; i += 16) {
- const h0 = a, h1 = b, h2 = c, h3 = d, h4 = e;
- for (let j = 0; j < 80; j++) {
- if (j < 16) {
- w[j] = words32[i + j];
- }
- else {
- w[j] = rol32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
- }
- const fkVal = fk(j, b, c, d);
- const f = fkVal[0];
- const k = fkVal[1];
- const temp = [rol32(a, 5), f, e, k, w[j]].reduce(add32);
- e = d;
- d = c;
- c = rol32(b, 30);
- b = a;
- a = temp;
- }
- a = add32(a, h0);
- b = add32(b, h1);
- c = add32(c, h2);
- d = add32(d, h3);
- e = add32(e, h4);
- }
- // Convert the output parts to a 160-bit hexadecimal string
- return toHexU32(a) + toHexU32(b) + toHexU32(c) + toHexU32(d) + toHexU32(e);
- }
- /**
- * Convert and format a number as a string representing a 32-bit unsigned hexadecimal number.
- * @param value The value to format as a string.
- * @returns A hexadecimal string representing the value.
- */
- function toHexU32(value) {
- // unsigned right shift of zero ensures an unsigned 32-bit number
- return (value >>> 0).toString(16).padStart(8, '0');
- }
- function fk(index, b, c, d) {
- if (index < 20) {
- return [(b & c) | (~b & d), 0x5a827999];
- }
- if (index < 40) {
- return [b ^ c ^ d, 0x6ed9eba1];
- }
- if (index < 60) {
- return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];
- }
- return [b ^ c ^ d, 0xca62c1d6];
- }
- /**
- * Compute the fingerprint of the given string
- *
- * The output is 64 bit number encoded as a decimal string
- *
- * based on:
- * https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java
- */
- function fingerprint(str) {
- textEncoder ??= new TextEncoder();
- const utf8 = textEncoder.encode(str);
- const view = new DataView(utf8.buffer, utf8.byteOffset, utf8.byteLength);
- let hi = hash32(view, utf8.length, 0);
- let lo = hash32(view, utf8.length, 102072);
- if (hi == 0 && (lo == 0 || lo == 1)) {
- hi = hi ^ 0x130f9bef;
- lo = lo ^ -1801410264;
- }
- return (BigInt.asUintN(32, BigInt(hi)) << BigInt(32)) | BigInt.asUintN(32, BigInt(lo));
- }
- function computeMsgId(msg, meaning = '') {
- let msgFingerprint = fingerprint(msg);
- if (meaning) {
- // Rotate the 64-bit message fingerprint one bit to the left and then add the meaning
- // fingerprint.
- msgFingerprint =
- BigInt.asUintN(64, msgFingerprint << BigInt(1)) |
- ((msgFingerprint >> BigInt(63)) & BigInt(1));
- msgFingerprint += fingerprint(meaning);
- }
- return BigInt.asUintN(63, msgFingerprint).toString();
- }
- function hash32(view, length, c) {
- let a = 0x9e3779b9, b = 0x9e3779b9;
- let index = 0;
- const end = length - 12;
- for (; index <= end; index += 12) {
- a += view.getUint32(index, true);
- b += view.getUint32(index + 4, true);
- c += view.getUint32(index + 8, true);
- const res = mix(a, b, c);
- (a = res[0]), (b = res[1]), (c = res[2]);
- }
- const remainder = length - index;
- // the first byte of c is reserved for the length
- c += length;
- if (remainder >= 4) {
- a += view.getUint32(index, true);
- index += 4;
- if (remainder >= 8) {
- b += view.getUint32(index, true);
- index += 4;
- // Partial 32-bit word for c
- if (remainder >= 9) {
- c += view.getUint8(index++) << 8;
- }
- if (remainder >= 10) {
- c += view.getUint8(index++) << 16;
- }
- if (remainder === 11) {
- c += view.getUint8(index++) << 24;
- }
- }
- else {
- // Partial 32-bit word for b
- if (remainder >= 5) {
- b += view.getUint8(index++);
- }
- if (remainder >= 6) {
- b += view.getUint8(index++) << 8;
- }
- if (remainder === 7) {
- b += view.getUint8(index++) << 16;
- }
- }
- }
- else {
- // Partial 32-bit word for a
- if (remainder >= 1) {
- a += view.getUint8(index++);
- }
- if (remainder >= 2) {
- a += view.getUint8(index++) << 8;
- }
- if (remainder === 3) {
- a += view.getUint8(index++) << 16;
- }
- }
- return mix(a, b, c)[2];
- }
- function mix(a, b, c) {
- a -= b;
- a -= c;
- a ^= c >>> 13;
- b -= c;
- b -= a;
- b ^= a << 8;
- c -= a;
- c -= b;
- c ^= b >>> 13;
- a -= b;
- a -= c;
- a ^= c >>> 12;
- b -= c;
- b -= a;
- b ^= a << 16;
- c -= a;
- c -= b;
- c ^= b >>> 5;
- a -= b;
- a -= c;
- a ^= c >>> 3;
- b -= c;
- b -= a;
- b ^= a << 10;
- c -= a;
- c -= b;
- c ^= b >>> 15;
- return [a, b, c];
- }
- // Utils
- var Endian;
- (function (Endian) {
- Endian[Endian["Little"] = 0] = "Little";
- Endian[Endian["Big"] = 1] = "Big";
- })(Endian || (Endian = {}));
- function add32(a, b) {
- return add32to64(a, b)[1];
- }
- function add32to64(a, b) {
- const low = (a & 0xffff) + (b & 0xffff);
- const high = (a >>> 16) + (b >>> 16) + (low >>> 16);
- return [high >>> 16, (high << 16) | (low & 0xffff)];
- }
- // Rotate a 32b number left `count` position
- function rol32(a, count) {
- return (a << count) | (a >>> (32 - count));
- }
- function bytesToWords32(bytes, endian) {
- const size = (bytes.length + 3) >>> 2;
- const words32 = [];
- for (let i = 0; i < size; i++) {
- words32[i] = wordAt(bytes, i * 4, endian);
- }
- return words32;
- }
- function byteAt(bytes, index) {
- return index >= bytes.length ? 0 : bytes[index];
- }
- function wordAt(bytes, index, endian) {
- let word = 0;
- if (endian === Endian.Big) {
- for (let i = 0; i < 4; i++) {
- word += byteAt(bytes, index + i) << (24 - 8 * i);
- }
- }
- else {
- for (let i = 0; i < 4; i++) {
- word += byteAt(bytes, index + i) << (8 * i);
- }
- }
- return word;
- }
- //// Types
- var TypeModifier;
- (function (TypeModifier) {
- TypeModifier[TypeModifier["None"] = 0] = "None";
- TypeModifier[TypeModifier["Const"] = 1] = "Const";
- })(TypeModifier || (TypeModifier = {}));
- class Type {
- modifiers;
- constructor(modifiers = TypeModifier.None) {
- this.modifiers = modifiers;
- }
- hasModifier(modifier) {
- return (this.modifiers & modifier) !== 0;
- }
- }
- var BuiltinTypeName;
- (function (BuiltinTypeName) {
- BuiltinTypeName[BuiltinTypeName["Dynamic"] = 0] = "Dynamic";
- BuiltinTypeName[BuiltinTypeName["Bool"] = 1] = "Bool";
- BuiltinTypeName[BuiltinTypeName["String"] = 2] = "String";
- BuiltinTypeName[BuiltinTypeName["Int"] = 3] = "Int";
- BuiltinTypeName[BuiltinTypeName["Number"] = 4] = "Number";
- BuiltinTypeName[BuiltinTypeName["Function"] = 5] = "Function";
- BuiltinTypeName[BuiltinTypeName["Inferred"] = 6] = "Inferred";
- BuiltinTypeName[BuiltinTypeName["None"] = 7] = "None";
- })(BuiltinTypeName || (BuiltinTypeName = {}));
- class BuiltinType extends Type {
- name;
- constructor(name, modifiers) {
- super(modifiers);
- this.name = name;
- }
- visitType(visitor, context) {
- return visitor.visitBuiltinType(this, context);
- }
- }
- class ExpressionType extends Type {
- value;
- typeParams;
- constructor(value, modifiers, typeParams = null) {
- super(modifiers);
- this.value = value;
- this.typeParams = typeParams;
- }
- visitType(visitor, context) {
- return visitor.visitExpressionType(this, context);
- }
- }
- class TransplantedType extends Type {
- type;
- constructor(type, modifiers) {
- super(modifiers);
- this.type = type;
- }
- visitType(visitor, context) {
- return visitor.visitTransplantedType(this, context);
- }
- }
- const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
- const INFERRED_TYPE = new BuiltinType(BuiltinTypeName.Inferred);
- const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
- new BuiltinType(BuiltinTypeName.Int);
- const NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
- const STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
- new BuiltinType(BuiltinTypeName.Function);
- const NONE_TYPE = new BuiltinType(BuiltinTypeName.None);
- ///// Expressions
- var UnaryOperator;
- (function (UnaryOperator) {
- UnaryOperator[UnaryOperator["Minus"] = 0] = "Minus";
- UnaryOperator[UnaryOperator["Plus"] = 1] = "Plus";
- })(UnaryOperator || (UnaryOperator = {}));
- var BinaryOperator;
- (function (BinaryOperator) {
- BinaryOperator[BinaryOperator["Equals"] = 0] = "Equals";
- BinaryOperator[BinaryOperator["NotEquals"] = 1] = "NotEquals";
- BinaryOperator[BinaryOperator["Identical"] = 2] = "Identical";
- BinaryOperator[BinaryOperator["NotIdentical"] = 3] = "NotIdentical";
- BinaryOperator[BinaryOperator["Minus"] = 4] = "Minus";
- BinaryOperator[BinaryOperator["Plus"] = 5] = "Plus";
- BinaryOperator[BinaryOperator["Divide"] = 6] = "Divide";
- BinaryOperator[BinaryOperator["Multiply"] = 7] = "Multiply";
- BinaryOperator[BinaryOperator["Modulo"] = 8] = "Modulo";
- BinaryOperator[BinaryOperator["And"] = 9] = "And";
- BinaryOperator[BinaryOperator["Or"] = 10] = "Or";
- BinaryOperator[BinaryOperator["BitwiseOr"] = 11] = "BitwiseOr";
- BinaryOperator[BinaryOperator["BitwiseAnd"] = 12] = "BitwiseAnd";
- BinaryOperator[BinaryOperator["Lower"] = 13] = "Lower";
- BinaryOperator[BinaryOperator["LowerEquals"] = 14] = "LowerEquals";
- BinaryOperator[BinaryOperator["Bigger"] = 15] = "Bigger";
- BinaryOperator[BinaryOperator["BiggerEquals"] = 16] = "BiggerEquals";
- BinaryOperator[BinaryOperator["NullishCoalesce"] = 17] = "NullishCoalesce";
- })(BinaryOperator || (BinaryOperator = {}));
- function nullSafeIsEquivalent(base, other) {
- if (base == null || other == null) {
- return base == other;
- }
- return base.isEquivalent(other);
- }
- function areAllEquivalentPredicate(base, other, equivalentPredicate) {
- const len = base.length;
- if (len !== other.length) {
- return false;
- }
- for (let i = 0; i < len; i++) {
- if (!equivalentPredicate(base[i], other[i])) {
- return false;
- }
- }
- return true;
- }
- function areAllEquivalent(base, other) {
- return areAllEquivalentPredicate(base, other, (baseElement, otherElement) => baseElement.isEquivalent(otherElement));
- }
- class Expression {
- type;
- sourceSpan;
- constructor(type, sourceSpan) {
- this.type = type || null;
- this.sourceSpan = sourceSpan || null;
- }
- prop(name, sourceSpan) {
- return new ReadPropExpr(this, name, null, sourceSpan);
- }
- key(index, type, sourceSpan) {
- return new ReadKeyExpr(this, index, type, sourceSpan);
- }
- callFn(params, sourceSpan, pure) {
- return new InvokeFunctionExpr(this, params, null, sourceSpan, pure);
- }
- instantiate(params, type, sourceSpan) {
- return new InstantiateExpr(this, params, type, sourceSpan);
- }
- conditional(trueCase, falseCase = null, sourceSpan) {
- return new ConditionalExpr(this, trueCase, falseCase, null, sourceSpan);
- }
- equals(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.Equals, this, rhs, null, sourceSpan);
- }
- notEquals(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.NotEquals, this, rhs, null, sourceSpan);
- }
- identical(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.Identical, this, rhs, null, sourceSpan);
- }
- notIdentical(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.NotIdentical, this, rhs, null, sourceSpan);
- }
- minus(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.Minus, this, rhs, null, sourceSpan);
- }
- plus(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.Plus, this, rhs, null, sourceSpan);
- }
- divide(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.Divide, this, rhs, null, sourceSpan);
- }
- multiply(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.Multiply, this, rhs, null, sourceSpan);
- }
- modulo(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.Modulo, this, rhs, null, sourceSpan);
- }
- and(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.And, this, rhs, null, sourceSpan);
- }
- bitwiseOr(rhs, sourceSpan, parens = true) {
- return new BinaryOperatorExpr(BinaryOperator.BitwiseOr, this, rhs, null, sourceSpan, parens);
- }
- bitwiseAnd(rhs, sourceSpan, parens = true) {
- return new BinaryOperatorExpr(BinaryOperator.BitwiseAnd, this, rhs, null, sourceSpan, parens);
- }
- or(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.Or, this, rhs, null, sourceSpan);
- }
- lower(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.Lower, this, rhs, null, sourceSpan);
- }
- lowerEquals(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.LowerEquals, this, rhs, null, sourceSpan);
- }
- bigger(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.Bigger, this, rhs, null, sourceSpan);
- }
- biggerEquals(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.BiggerEquals, this, rhs, null, sourceSpan);
- }
- isBlank(sourceSpan) {
- // Note: We use equals by purpose here to compare to null and undefined in JS.
- // We use the typed null to allow strictNullChecks to narrow types.
- return this.equals(TYPED_NULL_EXPR, sourceSpan);
- }
- nullishCoalesce(rhs, sourceSpan) {
- return new BinaryOperatorExpr(BinaryOperator.NullishCoalesce, this, rhs, null, sourceSpan);
- }
- toStmt() {
- return new ExpressionStatement(this, null);
- }
- }
- class ReadVarExpr extends Expression {
- name;
- constructor(name, type, sourceSpan) {
- super(type, sourceSpan);
- this.name = name;
- }
- isEquivalent(e) {
- return e instanceof ReadVarExpr && this.name === e.name;
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitReadVarExpr(this, context);
- }
- clone() {
- return new ReadVarExpr(this.name, this.type, this.sourceSpan);
- }
- set(value) {
- return new WriteVarExpr(this.name, value, null, this.sourceSpan);
- }
- }
- class TypeofExpr extends Expression {
- expr;
- constructor(expr, type, sourceSpan) {
- super(type, sourceSpan);
- this.expr = expr;
- }
- visitExpression(visitor, context) {
- return visitor.visitTypeofExpr(this, context);
- }
- isEquivalent(e) {
- return e instanceof TypeofExpr && e.expr.isEquivalent(this.expr);
- }
- isConstant() {
- return this.expr.isConstant();
- }
- clone() {
- return new TypeofExpr(this.expr.clone());
- }
- }
- class WrappedNodeExpr extends Expression {
- node;
- constructor(node, type, sourceSpan) {
- super(type, sourceSpan);
- this.node = node;
- }
- isEquivalent(e) {
- return e instanceof WrappedNodeExpr && this.node === e.node;
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitWrappedNodeExpr(this, context);
- }
- clone() {
- return new WrappedNodeExpr(this.node, this.type, this.sourceSpan);
- }
- }
- class WriteVarExpr extends Expression {
- name;
- value;
- constructor(name, value, type, sourceSpan) {
- super(type || value.type, sourceSpan);
- this.name = name;
- this.value = value;
- }
- isEquivalent(e) {
- return e instanceof WriteVarExpr && this.name === e.name && this.value.isEquivalent(e.value);
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitWriteVarExpr(this, context);
- }
- clone() {
- return new WriteVarExpr(this.name, this.value.clone(), this.type, this.sourceSpan);
- }
- toDeclStmt(type, modifiers) {
- return new DeclareVarStmt(this.name, this.value, type, modifiers, this.sourceSpan);
- }
- toConstDecl() {
- return this.toDeclStmt(INFERRED_TYPE, exports.StmtModifier.Final);
- }
- }
- class WriteKeyExpr extends Expression {
- receiver;
- index;
- value;
- constructor(receiver, index, value, type, sourceSpan) {
- super(type || value.type, sourceSpan);
- this.receiver = receiver;
- this.index = index;
- this.value = value;
- }
- isEquivalent(e) {
- return (e instanceof WriteKeyExpr &&
- this.receiver.isEquivalent(e.receiver) &&
- this.index.isEquivalent(e.index) &&
- this.value.isEquivalent(e.value));
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitWriteKeyExpr(this, context);
- }
- clone() {
- return new WriteKeyExpr(this.receiver.clone(), this.index.clone(), this.value.clone(), this.type, this.sourceSpan);
- }
- }
- class WritePropExpr extends Expression {
- receiver;
- name;
- value;
- constructor(receiver, name, value, type, sourceSpan) {
- super(type || value.type, sourceSpan);
- this.receiver = receiver;
- this.name = name;
- this.value = value;
- }
- isEquivalent(e) {
- return (e instanceof WritePropExpr &&
- this.receiver.isEquivalent(e.receiver) &&
- this.name === e.name &&
- this.value.isEquivalent(e.value));
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitWritePropExpr(this, context);
- }
- clone() {
- return new WritePropExpr(this.receiver.clone(), this.name, this.value.clone(), this.type, this.sourceSpan);
- }
- }
- class InvokeFunctionExpr extends Expression {
- fn;
- args;
- pure;
- constructor(fn, args, type, sourceSpan, pure = false) {
- super(type, sourceSpan);
- this.fn = fn;
- this.args = args;
- this.pure = pure;
- }
- // An alias for fn, which allows other logic to handle calls and property reads together.
- get receiver() {
- return this.fn;
- }
- isEquivalent(e) {
- return (e instanceof InvokeFunctionExpr &&
- this.fn.isEquivalent(e.fn) &&
- areAllEquivalent(this.args, e.args) &&
- this.pure === e.pure);
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitInvokeFunctionExpr(this, context);
- }
- clone() {
- return new InvokeFunctionExpr(this.fn.clone(), this.args.map((arg) => arg.clone()), this.type, this.sourceSpan, this.pure);
- }
- }
- class TaggedTemplateLiteralExpr extends Expression {
- tag;
- template;
- constructor(tag, template, type, sourceSpan) {
- super(type, sourceSpan);
- this.tag = tag;
- this.template = template;
- }
- isEquivalent(e) {
- return (e instanceof TaggedTemplateLiteralExpr &&
- this.tag.isEquivalent(e.tag) &&
- this.template.isEquivalent(e.template));
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitTaggedTemplateLiteralExpr(this, context);
- }
- clone() {
- return new TaggedTemplateLiteralExpr(this.tag.clone(), this.template.clone(), this.type, this.sourceSpan);
- }
- }
- class InstantiateExpr extends Expression {
- classExpr;
- args;
- constructor(classExpr, args, type, sourceSpan) {
- super(type, sourceSpan);
- this.classExpr = classExpr;
- this.args = args;
- }
- isEquivalent(e) {
- return (e instanceof InstantiateExpr &&
- this.classExpr.isEquivalent(e.classExpr) &&
- areAllEquivalent(this.args, e.args));
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitInstantiateExpr(this, context);
- }
- clone() {
- return new InstantiateExpr(this.classExpr.clone(), this.args.map((arg) => arg.clone()), this.type, this.sourceSpan);
- }
- }
- class LiteralExpr extends Expression {
- value;
- constructor(value, type, sourceSpan) {
- super(type, sourceSpan);
- this.value = value;
- }
- isEquivalent(e) {
- return e instanceof LiteralExpr && this.value === e.value;
- }
- isConstant() {
- return true;
- }
- visitExpression(visitor, context) {
- return visitor.visitLiteralExpr(this, context);
- }
- clone() {
- return new LiteralExpr(this.value, this.type, this.sourceSpan);
- }
- }
- class TemplateLiteralExpr extends Expression {
- elements;
- expressions;
- constructor(elements, expressions, sourceSpan) {
- super(null, sourceSpan);
- this.elements = elements;
- this.expressions = expressions;
- }
- isEquivalent(e) {
- return (e instanceof TemplateLiteralExpr &&
- areAllEquivalentPredicate(this.elements, e.elements, (a, b) => a.text === b.text) &&
- areAllEquivalent(this.expressions, e.expressions));
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitTemplateLiteralExpr(this, context);
- }
- clone() {
- return new TemplateLiteralExpr(this.elements.map((el) => el.clone()), this.expressions.map((expr) => expr.clone()));
- }
- }
- class TemplateLiteralElementExpr extends Expression {
- text;
- rawText;
- constructor(text, sourceSpan, rawText) {
- super(STRING_TYPE, sourceSpan);
- this.text = text;
- // If `rawText` is not provided, "fake" the raw string by escaping the following sequences:
- // - "\" would otherwise indicate that the next character is a control character.
- // - "`" and "${" are template string control sequences that would otherwise prematurely
- // indicate the end of the template literal element.
- // Note that we can't rely on the `sourceSpan` here, because it may be incorrect (see
- // https://github.com/angular/angular/pull/60267#discussion_r1986402524).
- this.rawText = rawText ?? escapeForTemplateLiteral(escapeSlashes(text));
- }
- visitExpression(visitor, context) {
- return visitor.visitTemplateLiteralElementExpr(this, context);
- }
- isEquivalent(e) {
- return (e instanceof TemplateLiteralElementExpr && e.text === this.text && e.rawText === this.rawText);
- }
- isConstant() {
- return true;
- }
- clone() {
- return new TemplateLiteralElementExpr(this.text, this.sourceSpan, this.rawText);
- }
- }
- class LiteralPiece {
- text;
- sourceSpan;
- constructor(text, sourceSpan) {
- this.text = text;
- this.sourceSpan = sourceSpan;
- }
- }
- class PlaceholderPiece {
- text;
- sourceSpan;
- associatedMessage;
- /**
- * Create a new instance of a `PlaceholderPiece`.
- *
- * @param text the name of this placeholder (e.g. `PH_1`).
- * @param sourceSpan the location of this placeholder in its localized message the source code.
- * @param associatedMessage reference to another message that this placeholder is associated with.
- * The `associatedMessage` is mainly used to provide a relationship to an ICU message that has
- * been extracted out from the message containing the placeholder.
- */
- constructor(text, sourceSpan, associatedMessage) {
- this.text = text;
- this.sourceSpan = sourceSpan;
- this.associatedMessage = associatedMessage;
- }
- }
- const MEANING_SEPARATOR$1 = '|';
- const ID_SEPARATOR$1 = '@@';
- const LEGACY_ID_INDICATOR = '␟';
- class LocalizedString extends Expression {
- metaBlock;
- messageParts;
- placeHolderNames;
- expressions;
- constructor(metaBlock, messageParts, placeHolderNames, expressions, sourceSpan) {
- super(STRING_TYPE, sourceSpan);
- this.metaBlock = metaBlock;
- this.messageParts = messageParts;
- this.placeHolderNames = placeHolderNames;
- this.expressions = expressions;
- }
- isEquivalent(e) {
- // return e instanceof LocalizedString && this.message === e.message;
- return false;
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitLocalizedString(this, context);
- }
- clone() {
- return new LocalizedString(this.metaBlock, this.messageParts, this.placeHolderNames, this.expressions.map((expr) => expr.clone()), this.sourceSpan);
- }
- /**
- * Serialize the given `meta` and `messagePart` into "cooked" and "raw" strings that can be used
- * in a `$localize` tagged string. The format of the metadata is the same as that parsed by
- * `parseI18nMeta()`.
- *
- * @param meta The metadata to serialize
- * @param messagePart The first part of the tagged string
- */
- serializeI18nHead() {
- let metaBlock = this.metaBlock.description || '';
- if (this.metaBlock.meaning) {
- metaBlock = `${this.metaBlock.meaning}${MEANING_SEPARATOR$1}${metaBlock}`;
- }
- if (this.metaBlock.customId) {
- metaBlock = `${metaBlock}${ID_SEPARATOR$1}${this.metaBlock.customId}`;
- }
- if (this.metaBlock.legacyIds) {
- this.metaBlock.legacyIds.forEach((legacyId) => {
- metaBlock = `${metaBlock}${LEGACY_ID_INDICATOR}${legacyId}`;
- });
- }
- return createCookedRawString(metaBlock, this.messageParts[0].text, this.getMessagePartSourceSpan(0));
- }
- getMessagePartSourceSpan(i) {
- return this.messageParts[i]?.sourceSpan ?? this.sourceSpan;
- }
- getPlaceholderSourceSpan(i) {
- return (this.placeHolderNames[i]?.sourceSpan ?? this.expressions[i]?.sourceSpan ?? this.sourceSpan);
- }
- /**
- * Serialize the given `placeholderName` and `messagePart` into "cooked" and "raw" strings that
- * can be used in a `$localize` tagged string.
- *
- * The format is `:<placeholder-name>[@@<associated-id>]:`.
- *
- * The `associated-id` is the message id of the (usually an ICU) message to which this placeholder
- * refers.
- *
- * @param partIndex The index of the message part to serialize.
- */
- serializeI18nTemplatePart(partIndex) {
- const placeholder = this.placeHolderNames[partIndex - 1];
- const messagePart = this.messageParts[partIndex];
- let metaBlock = placeholder.text;
- if (placeholder.associatedMessage?.legacyIds.length === 0) {
- metaBlock += `${ID_SEPARATOR$1}${computeMsgId(placeholder.associatedMessage.messageString, placeholder.associatedMessage.meaning)}`;
- }
- return createCookedRawString(metaBlock, messagePart.text, this.getMessagePartSourceSpan(partIndex));
- }
- }
- const escapeSlashes = (str) => str.replace(/\\/g, '\\\\');
- const escapeStartingColon = (str) => str.replace(/^:/, '\\:');
- const escapeColons = (str) => str.replace(/:/g, '\\:');
- const escapeForTemplateLiteral = (str) => str.replace(/`/g, '\\`').replace(/\${/g, '$\\{');
- /**
- * Creates a `{cooked, raw}` object from the `metaBlock` and `messagePart`.
- *
- * The `raw` text must have various character sequences escaped:
- * * "\" would otherwise indicate that the next character is a control character.
- * * "`" and "${" are template string control sequences that would otherwise prematurely indicate
- * the end of a message part.
- * * ":" inside a metablock would prematurely indicate the end of the metablock.
- * * ":" at the start of a messagePart with no metablock would erroneously indicate the start of a
- * metablock.
- *
- * @param metaBlock Any metadata that should be prepended to the string
- * @param messagePart The message part of the string
- */
- function createCookedRawString(metaBlock, messagePart, range) {
- if (metaBlock === '') {
- return {
- cooked: messagePart,
- raw: escapeForTemplateLiteral(escapeStartingColon(escapeSlashes(messagePart))),
- range,
- };
- }
- else {
- return {
- cooked: `:${metaBlock}:${messagePart}`,
- raw: escapeForTemplateLiteral(`:${escapeColons(escapeSlashes(metaBlock))}:${escapeSlashes(messagePart)}`),
- range,
- };
- }
- }
- class ExternalExpr extends Expression {
- value;
- typeParams;
- constructor(value, type, typeParams = null, sourceSpan) {
- super(type, sourceSpan);
- this.value = value;
- this.typeParams = typeParams;
- }
- isEquivalent(e) {
- return (e instanceof ExternalExpr &&
- this.value.name === e.value.name &&
- this.value.moduleName === e.value.moduleName);
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitExternalExpr(this, context);
- }
- clone() {
- return new ExternalExpr(this.value, this.type, this.typeParams, this.sourceSpan);
- }
- }
- class ExternalReference {
- moduleName;
- name;
- constructor(moduleName, name) {
- this.moduleName = moduleName;
- this.name = name;
- }
- }
- class ConditionalExpr extends Expression {
- condition;
- falseCase;
- trueCase;
- constructor(condition, trueCase, falseCase = null, type, sourceSpan) {
- super(type || trueCase.type, sourceSpan);
- this.condition = condition;
- this.falseCase = falseCase;
- this.trueCase = trueCase;
- }
- isEquivalent(e) {
- return (e instanceof ConditionalExpr &&
- this.condition.isEquivalent(e.condition) &&
- this.trueCase.isEquivalent(e.trueCase) &&
- nullSafeIsEquivalent(this.falseCase, e.falseCase));
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitConditionalExpr(this, context);
- }
- clone() {
- return new ConditionalExpr(this.condition.clone(), this.trueCase.clone(), this.falseCase?.clone(), this.type, this.sourceSpan);
- }
- }
- class DynamicImportExpr extends Expression {
- url;
- urlComment;
- constructor(url, sourceSpan, urlComment) {
- super(null, sourceSpan);
- this.url = url;
- this.urlComment = urlComment;
- }
- isEquivalent(e) {
- return e instanceof DynamicImportExpr && this.url === e.url && this.urlComment === e.urlComment;
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitDynamicImportExpr(this, context);
- }
- clone() {
- return new DynamicImportExpr(typeof this.url === 'string' ? this.url : this.url.clone(), this.sourceSpan, this.urlComment);
- }
- }
- class NotExpr extends Expression {
- condition;
- constructor(condition, sourceSpan) {
- super(BOOL_TYPE, sourceSpan);
- this.condition = condition;
- }
- isEquivalent(e) {
- return e instanceof NotExpr && this.condition.isEquivalent(e.condition);
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitNotExpr(this, context);
- }
- clone() {
- return new NotExpr(this.condition.clone(), this.sourceSpan);
- }
- }
- class FnParam {
- name;
- type;
- constructor(name, type = null) {
- this.name = name;
- this.type = type;
- }
- isEquivalent(param) {
- return this.name === param.name;
- }
- clone() {
- return new FnParam(this.name, this.type);
- }
- }
- class FunctionExpr extends Expression {
- params;
- statements;
- name;
- constructor(params, statements, type, sourceSpan, name) {
- super(type, sourceSpan);
- this.params = params;
- this.statements = statements;
- this.name = name;
- }
- isEquivalent(e) {
- return ((e instanceof FunctionExpr || e instanceof DeclareFunctionStmt) &&
- areAllEquivalent(this.params, e.params) &&
- areAllEquivalent(this.statements, e.statements));
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitFunctionExpr(this, context);
- }
- toDeclStmt(name, modifiers) {
- return new DeclareFunctionStmt(name, this.params, this.statements, this.type, modifiers, this.sourceSpan);
- }
- clone() {
- // TODO: Should we deep clone statements?
- return new FunctionExpr(this.params.map((p) => p.clone()), this.statements, this.type, this.sourceSpan, this.name);
- }
- }
- class ArrowFunctionExpr extends Expression {
- params;
- body;
- // Note that `body: Expression` represents `() => expr` whereas
- // `body: Statement[]` represents `() => { expr }`.
- constructor(params, body, type, sourceSpan) {
- super(type, sourceSpan);
- this.params = params;
- this.body = body;
- }
- isEquivalent(e) {
- if (!(e instanceof ArrowFunctionExpr) || !areAllEquivalent(this.params, e.params)) {
- return false;
- }
- if (this.body instanceof Expression && e.body instanceof Expression) {
- return this.body.isEquivalent(e.body);
- }
- if (Array.isArray(this.body) && Array.isArray(e.body)) {
- return areAllEquivalent(this.body, e.body);
- }
- return false;
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitArrowFunctionExpr(this, context);
- }
- clone() {
- // TODO: Should we deep clone statements?
- return new ArrowFunctionExpr(this.params.map((p) => p.clone()), Array.isArray(this.body) ? this.body : this.body.clone(), this.type, this.sourceSpan);
- }
- toDeclStmt(name, modifiers) {
- return new DeclareVarStmt(name, this, INFERRED_TYPE, modifiers, this.sourceSpan);
- }
- }
- class UnaryOperatorExpr extends Expression {
- operator;
- expr;
- parens;
- constructor(operator, expr, type, sourceSpan, parens = true) {
- super(type || NUMBER_TYPE, sourceSpan);
- this.operator = operator;
- this.expr = expr;
- this.parens = parens;
- }
- isEquivalent(e) {
- return (e instanceof UnaryOperatorExpr &&
- this.operator === e.operator &&
- this.expr.isEquivalent(e.expr));
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitUnaryOperatorExpr(this, context);
- }
- clone() {
- return new UnaryOperatorExpr(this.operator, this.expr.clone(), this.type, this.sourceSpan, this.parens);
- }
- }
- class BinaryOperatorExpr extends Expression {
- operator;
- rhs;
- parens;
- lhs;
- constructor(operator, lhs, rhs, type, sourceSpan, parens = true) {
- super(type || lhs.type, sourceSpan);
- this.operator = operator;
- this.rhs = rhs;
- this.parens = parens;
- this.lhs = lhs;
- }
- isEquivalent(e) {
- return (e instanceof BinaryOperatorExpr &&
- this.operator === e.operator &&
- this.lhs.isEquivalent(e.lhs) &&
- this.rhs.isEquivalent(e.rhs));
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitBinaryOperatorExpr(this, context);
- }
- clone() {
- return new BinaryOperatorExpr(this.operator, this.lhs.clone(), this.rhs.clone(), this.type, this.sourceSpan, this.parens);
- }
- }
- class ReadPropExpr extends Expression {
- receiver;
- name;
- constructor(receiver, name, type, sourceSpan) {
- super(type, sourceSpan);
- this.receiver = receiver;
- this.name = name;
- }
- // An alias for name, which allows other logic to handle property reads and keyed reads together.
- get index() {
- return this.name;
- }
- isEquivalent(e) {
- return (e instanceof ReadPropExpr && this.receiver.isEquivalent(e.receiver) && this.name === e.name);
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitReadPropExpr(this, context);
- }
- set(value) {
- return new WritePropExpr(this.receiver, this.name, value, null, this.sourceSpan);
- }
- clone() {
- return new ReadPropExpr(this.receiver.clone(), this.name, this.type, this.sourceSpan);
- }
- }
- class ReadKeyExpr extends Expression {
- receiver;
- index;
- constructor(receiver, index, type, sourceSpan) {
- super(type, sourceSpan);
- this.receiver = receiver;
- this.index = index;
- }
- isEquivalent(e) {
- return (e instanceof ReadKeyExpr &&
- this.receiver.isEquivalent(e.receiver) &&
- this.index.isEquivalent(e.index));
- }
- isConstant() {
- return false;
- }
- visitExpression(visitor, context) {
- return visitor.visitReadKeyExpr(this, context);
- }
- set(value) {
- return new WriteKeyExpr(this.receiver, this.index, value, null, this.sourceSpan);
- }
- clone() {
- return new ReadKeyExpr(this.receiver.clone(), this.index.clone(), this.type, this.sourceSpan);
- }
- }
- class LiteralArrayExpr extends Expression {
- entries;
- constructor(entries, type, sourceSpan) {
- super(type, sourceSpan);
- this.entries = entries;
- }
- isConstant() {
- return this.entries.every((e) => e.isConstant());
- }
- isEquivalent(e) {
- return e instanceof LiteralArrayExpr && areAllEquivalent(this.entries, e.entries);
- }
- visitExpression(visitor, context) {
- return visitor.visitLiteralArrayExpr(this, context);
- }
- clone() {
- return new LiteralArrayExpr(this.entries.map((e) => e.clone()), this.type, this.sourceSpan);
- }
- }
- class LiteralMapEntry {
- key;
- value;
- quoted;
- constructor(key, value, quoted) {
- this.key = key;
- this.value = value;
- this.quoted = quoted;
- }
- isEquivalent(e) {
- return this.key === e.key && this.value.isEquivalent(e.value);
- }
- clone() {
- return new LiteralMapEntry(this.key, this.value.clone(), this.quoted);
- }
- }
- class LiteralMapExpr extends Expression {
- entries;
- valueType = null;
- constructor(entries, type, sourceSpan) {
- super(type, sourceSpan);
- this.entries = entries;
- if (type) {
- this.valueType = type.valueType;
- }
- }
- isEquivalent(e) {
- return e instanceof LiteralMapExpr && areAllEquivalent(this.entries, e.entries);
- }
- isConstant() {
- return this.entries.every((e) => e.value.isConstant());
- }
- visitExpression(visitor, context) {
- return visitor.visitLiteralMapExpr(this, context);
- }
- clone() {
- const entriesClone = this.entries.map((entry) => entry.clone());
- return new LiteralMapExpr(entriesClone, this.type, this.sourceSpan);
- }
- }
- const NULL_EXPR = new LiteralExpr(null, null, null);
- const TYPED_NULL_EXPR = new LiteralExpr(null, INFERRED_TYPE, null);
- //// Statements
- exports.StmtModifier = void 0;
- (function (StmtModifier) {
- StmtModifier[StmtModifier["None"] = 0] = "None";
- StmtModifier[StmtModifier["Final"] = 1] = "Final";
- StmtModifier[StmtModifier["Private"] = 2] = "Private";
- StmtModifier[StmtModifier["Exported"] = 4] = "Exported";
- StmtModifier[StmtModifier["Static"] = 8] = "Static";
- })(exports.StmtModifier || (exports.StmtModifier = {}));
- class LeadingComment {
- text;
- multiline;
- trailingNewline;
- constructor(text, multiline, trailingNewline) {
- this.text = text;
- this.multiline = multiline;
- this.trailingNewline = trailingNewline;
- }
- toString() {
- return this.multiline ? ` ${this.text} ` : this.text;
- }
- }
- class JSDocComment extends LeadingComment {
- tags;
- constructor(tags) {
- super('', /* multiline */ true, /* trailingNewline */ true);
- this.tags = tags;
- }
- toString() {
- return serializeTags(this.tags);
- }
- }
- class Statement {
- modifiers;
- sourceSpan;
- leadingComments;
- constructor(modifiers = exports.StmtModifier.None, sourceSpan = null, leadingComments) {
- this.modifiers = modifiers;
- this.sourceSpan = sourceSpan;
- this.leadingComments = leadingComments;
- }
- hasModifier(modifier) {
- return (this.modifiers & modifier) !== 0;
- }
- addLeadingComment(leadingComment) {
- this.leadingComments = this.leadingComments ?? [];
- this.leadingComments.push(leadingComment);
- }
- }
- class DeclareVarStmt extends Statement {
- name;
- value;
- type;
- constructor(name, value, type, modifiers, sourceSpan, leadingComments) {
- super(modifiers, sourceSpan, leadingComments);
- this.name = name;
- this.value = value;
- this.type = type || (value && value.type) || null;
- }
- isEquivalent(stmt) {
- return (stmt instanceof DeclareVarStmt &&
- this.name === stmt.name &&
- (this.value ? !!stmt.value && this.value.isEquivalent(stmt.value) : !stmt.value));
- }
- visitStatement(visitor, context) {
- return visitor.visitDeclareVarStmt(this, context);
- }
- }
- class DeclareFunctionStmt extends Statement {
- name;
- params;
- statements;
- type;
- constructor(name, params, statements, type, modifiers, sourceSpan, leadingComments) {
- super(modifiers, sourceSpan, leadingComments);
- this.name = name;
- this.params = params;
- this.statements = statements;
- this.type = type || null;
- }
- isEquivalent(stmt) {
- return (stmt instanceof DeclareFunctionStmt &&
- areAllEquivalent(this.params, stmt.params) &&
- areAllEquivalent(this.statements, stmt.statements));
- }
- visitStatement(visitor, context) {
- return visitor.visitDeclareFunctionStmt(this, context);
- }
- }
- class ExpressionStatement extends Statement {
- expr;
- constructor(expr, sourceSpan, leadingComments) {
- super(exports.StmtModifier.None, sourceSpan, leadingComments);
- this.expr = expr;
- }
- isEquivalent(stmt) {
- return stmt instanceof ExpressionStatement && this.expr.isEquivalent(stmt.expr);
- }
- visitStatement(visitor, context) {
- return visitor.visitExpressionStmt(this, context);
- }
- }
- class ReturnStatement extends Statement {
- value;
- constructor(value, sourceSpan = null, leadingComments) {
- super(exports.StmtModifier.None, sourceSpan, leadingComments);
- this.value = value;
- }
- isEquivalent(stmt) {
- return stmt instanceof ReturnStatement && this.value.isEquivalent(stmt.value);
- }
- visitStatement(visitor, context) {
- return visitor.visitReturnStmt(this, context);
- }
- }
- class IfStmt extends Statement {
- condition;
- trueCase;
- falseCase;
- constructor(condition, trueCase, falseCase = [], sourceSpan, leadingComments) {
- super(exports.StmtModifier.None, sourceSpan, leadingComments);
- this.condition = condition;
- this.trueCase = trueCase;
- this.falseCase = falseCase;
- }
- isEquivalent(stmt) {
- return (stmt instanceof IfStmt &&
- this.condition.isEquivalent(stmt.condition) &&
- areAllEquivalent(this.trueCase, stmt.trueCase) &&
- areAllEquivalent(this.falseCase, stmt.falseCase));
- }
- visitStatement(visitor, context) {
- return visitor.visitIfStmt(this, context);
- }
- }
- let RecursiveAstVisitor$1 = class RecursiveAstVisitor {
- visitType(ast, context) {
- return ast;
- }
- visitExpression(ast, context) {
- if (ast.type) {
- ast.type.visitType(this, context);
- }
- return ast;
- }
- visitBuiltinType(type, context) {
- return this.visitType(type, context);
- }
- visitExpressionType(type, context) {
- type.value.visitExpression(this, context);
- if (type.typeParams !== null) {
- type.typeParams.forEach((param) => this.visitType(param, context));
- }
- return this.visitType(type, context);
- }
- visitArrayType(type, context) {
- return this.visitType(type, context);
- }
- visitMapType(type, context) {
- return this.visitType(type, context);
- }
- visitTransplantedType(type, context) {
- return type;
- }
- visitWrappedNodeExpr(ast, context) {
- return ast;
- }
- visitTypeofExpr(ast, context) {
- return this.visitExpression(ast, context);
- }
- visitReadVarExpr(ast, context) {
- return this.visitExpression(ast, context);
- }
- visitWriteVarExpr(ast, context) {
- ast.value.visitExpression(this, context);
- return this.visitExpression(ast, context);
- }
- visitWriteKeyExpr(ast, context) {
- ast.receiver.visitExpression(this, context);
- ast.index.visitExpression(this, context);
- ast.value.visitExpression(this, context);
- return this.visitExpression(ast, context);
- }
- visitWritePropExpr(ast, context) {
- ast.receiver.visitExpression(this, context);
- ast.value.visitExpression(this, context);
- return this.visitExpression(ast, context);
- }
- visitDynamicImportExpr(ast, context) {
- return this.visitExpression(ast, context);
- }
- visitInvokeFunctionExpr(ast, context) {
- ast.fn.visitExpression(this, context);
- this.visitAllExpressions(ast.args, context);
- return this.visitExpression(ast, context);
- }
- visitTaggedTemplateLiteralExpr(ast, context) {
- ast.tag.visitExpression(this, context);
- ast.template.visitExpression(this, context);
- return this.visitExpression(ast, context);
- }
- visitInstantiateExpr(ast, context) {
- ast.classExpr.visitExpression(this, context);
- this.visitAllExpressions(ast.args, context);
- return this.visitExpression(ast, context);
- }
- visitLiteralExpr(ast, context) {
- return this.visitExpression(ast, context);
- }
- visitLocalizedString(ast, context) {
- return this.visitExpression(ast, context);
- }
- visitExternalExpr(ast, context) {
- if (ast.typeParams) {
- ast.typeParams.forEach((type) => type.visitType(this, context));
- }
- return this.visitExpression(ast, context);
- }
- visitConditionalExpr(ast, context) {
- ast.condition.visitExpression(this, context);
- ast.trueCase.visitExpression(this, context);
- ast.falseCase.visitExpression(this, context);
- return this.visitExpression(ast, context);
- }
- visitNotExpr(ast, context) {
- ast.condition.visitExpression(this, context);
- return this.visitExpression(ast, context);
- }
- visitFunctionExpr(ast, context) {
- this.visitAllStatements(ast.statements, context);
- return this.visitExpression(ast, context);
- }
- visitArrowFunctionExpr(ast, context) {
- if (Array.isArray(ast.body)) {
- this.visitAllStatements(ast.body, context);
- }
- else {
- // Note: `body.visitExpression`, rather than `this.visitExpressiont(body)`,
- // because the latter won't recurse into the sub-expressions.
- ast.body.visitExpression(this, context);
- }
- return this.visitExpression(ast, context);
- }
- visitUnaryOperatorExpr(ast, context) {
- ast.expr.visitExpression(this, context);
- return this.visitExpression(ast, context);
- }
- visitBinaryOperatorExpr(ast, context) {
- ast.lhs.visitExpression(this, context);
- ast.rhs.visitExpression(this, context);
- return this.visitExpression(ast, context);
- }
- visitReadPropExpr(ast, context) {
- ast.receiver.visitExpression(this, context);
- return this.visitExpression(ast, context);
- }
- visitReadKeyExpr(ast, context) {
- ast.receiver.visitExpression(this, context);
- ast.index.visitExpression(this, context);
- return this.visitExpression(ast, context);
- }
- visitLiteralArrayExpr(ast, context) {
- this.visitAllExpressions(ast.entries, context);
- return this.visitExpression(ast, context);
- }
- visitLiteralMapExpr(ast, context) {
- ast.entries.forEach((entry) => entry.value.visitExpression(this, context));
- return this.visitExpression(ast, context);
- }
- visitCommaExpr(ast, context) {
- this.visitAllExpressions(ast.parts, context);
- return this.visitExpression(ast, context);
- }
- visitTemplateLiteralExpr(ast, context) {
- this.visitAllExpressions(ast.elements, context);
- this.visitAllExpressions(ast.expressions, context);
- return this.visitExpression(ast, context);
- }
- visitTemplateLiteralElementExpr(ast, context) {
- return this.visitExpression(ast, context);
- }
- visitAllExpressions(exprs, context) {
- exprs.forEach((expr) => expr.visitExpression(this, context));
- }
- visitDeclareVarStmt(stmt, context) {
- if (stmt.value) {
- stmt.value.visitExpression(this, context);
- }
- if (stmt.type) {
- stmt.type.visitType(this, context);
- }
- return stmt;
- }
- visitDeclareFunctionStmt(stmt, context) {
- this.visitAllStatements(stmt.statements, context);
- if (stmt.type) {
- stmt.type.visitType(this, context);
- }
- return stmt;
- }
- visitExpressionStmt(stmt, context) {
- stmt.expr.visitExpression(this, context);
- return stmt;
- }
- visitReturnStmt(stmt, context) {
- stmt.value.visitExpression(this, context);
- return stmt;
- }
- visitIfStmt(stmt, context) {
- stmt.condition.visitExpression(this, context);
- this.visitAllStatements(stmt.trueCase, context);
- this.visitAllStatements(stmt.falseCase, context);
- return stmt;
- }
- visitAllStatements(stmts, context) {
- stmts.forEach((stmt) => stmt.visitStatement(this, context));
- }
- };
- function leadingComment(text, multiline = false, trailingNewline = true) {
- return new LeadingComment(text, multiline, trailingNewline);
- }
- function jsDocComment(tags = []) {
- return new JSDocComment(tags);
- }
- function variable(name, type, sourceSpan) {
- return new ReadVarExpr(name, type, sourceSpan);
- }
- function importExpr(id, typeParams = null, sourceSpan) {
- return new ExternalExpr(id, null, typeParams, sourceSpan);
- }
- function expressionType(expr, typeModifiers, typeParams) {
- return new ExpressionType(expr, typeModifiers, typeParams);
- }
- function transplantedType(type, typeModifiers) {
- return new TransplantedType(type, typeModifiers);
- }
- function typeofExpr(expr) {
- return new TypeofExpr(expr);
- }
- function literalArr(values, type, sourceSpan) {
- return new LiteralArrayExpr(values, type, sourceSpan);
- }
- function literalMap(values, type = null) {
- return new LiteralMapExpr(values.map((e) => new LiteralMapEntry(e.key, e.value, e.quoted)), type, null);
- }
- function not(expr, sourceSpan) {
- return new NotExpr(expr, sourceSpan);
- }
- function fn(params, body, type, sourceSpan, name) {
- return new FunctionExpr(params, body, type, sourceSpan, name);
- }
- function arrowFn(params, body, type, sourceSpan) {
- return new ArrowFunctionExpr(params, body, type, sourceSpan);
- }
- function ifStmt(condition, thenClause, elseClause, sourceSpan, leadingComments) {
- return new IfStmt(condition, thenClause, elseClause, sourceSpan, leadingComments);
- }
- function taggedTemplate(tag, template, type, sourceSpan) {
- return new TaggedTemplateLiteralExpr(tag, template, type, sourceSpan);
- }
- function literal$1(value, type, sourceSpan) {
- return new LiteralExpr(value, type, sourceSpan);
- }
- function localizedString(metaBlock, messageParts, placeholderNames, expressions, sourceSpan) {
- return new LocalizedString(metaBlock, messageParts, placeholderNames, expressions, sourceSpan);
- }
- /*
- * Serializes a `Tag` into a string.
- * Returns a string like " @foo {bar} baz" (note the leading whitespace before `@foo`).
- */
- function tagToString(tag) {
- let out = '';
- if (tag.tagName) {
- out += ` @${tag.tagName}`;
- }
- if (tag.text) {
- if (tag.text.match(/\/\*|\*\//)) {
- throw new Error('JSDoc text cannot contain "/*" and "*/"');
- }
- out += ' ' + tag.text.replace(/@/g, '\\@');
- }
- return out;
- }
- function serializeTags(tags) {
- if (tags.length === 0)
- return '';
- if (tags.length === 1 && tags[0].tagName && !tags[0].text) {
- // The JSDOC comment is a single simple tag: e.g `/** @tagname */`.
- return `*${tagToString(tags[0])} `;
- }
- let out = '*\n';
- for (const tag of tags) {
- out += ' *';
- // If the tagToString is multi-line, insert " * " prefixes on lines.
- out += tagToString(tag).replace(/\n/g, '\n * ');
- out += '\n';
- }
- out += ' ';
- return out;
- }
- const CONSTANT_PREFIX = '_c';
- /**
- * `ConstantPool` tries to reuse literal factories when two or more literals are identical.
- * We determine whether literals are identical by creating a key out of their AST using the
- * `KeyVisitor`. This constant is used to replace dynamic expressions which can't be safely
- * converted into a key. E.g. given an expression `{foo: bar()}`, since we don't know what
- * the result of `bar` will be, we create a key that looks like `{foo: <unknown>}`. Note
- * that we use a variable, rather than something like `null` in order to avoid collisions.
- */
- const UNKNOWN_VALUE_KEY = variable('<unknown>');
- /**
- * Context to use when producing a key.
- *
- * This ensures we see the constant not the reference variable when producing
- * a key.
- */
- const KEY_CONTEXT = {};
- /**
- * Generally all primitive values are excluded from the `ConstantPool`, but there is an exclusion
- * for strings that reach a certain length threshold. This constant defines the length threshold for
- * strings.
- */
- const POOL_INCLUSION_LENGTH_THRESHOLD_FOR_STRINGS = 50;
- /**
- * A node that is a place-holder that allows the node to be replaced when the actual
- * node is known.
- *
- * This allows the constant pool to change an expression from a direct reference to
- * a constant to a shared constant. It returns a fix-up node that is later allowed to
- * change the referenced expression.
- */
- class FixupExpression extends Expression {
- resolved;
- original;
- shared = false;
- constructor(resolved) {
- super(resolved.type);
- this.resolved = resolved;
- this.original = resolved;
- }
- visitExpression(visitor, context) {
- if (context === KEY_CONTEXT) {
- // When producing a key we want to traverse the constant not the
- // variable used to refer to it.
- return this.original.visitExpression(visitor, context);
- }
- else {
- return this.resolved.visitExpression(visitor, context);
- }
- }
- isEquivalent(e) {
- return e instanceof FixupExpression && this.resolved.isEquivalent(e.resolved);
- }
- isConstant() {
- return true;
- }
- clone() {
- throw new Error(`Not supported.`);
- }
- fixup(expression) {
- this.resolved = expression;
- this.shared = true;
- }
- }
- /**
- * A constant pool allows a code emitter to share constant in an output context.
- *
- * The constant pool also supports sharing access to ivy definitions references.
- */
- class ConstantPool {
- isClosureCompilerEnabled;
- statements = [];
- literals = new Map();
- literalFactories = new Map();
- sharedConstants = new Map();
- /**
- * Constant pool also tracks claimed names from {@link uniqueName}.
- * This is useful to avoid collisions if variables are intended to be
- * named a certain way- but may conflict. We wouldn't want to always suffix
- * them with unique numbers.
- */
- _claimedNames = new Map();
- nextNameIndex = 0;
- constructor(isClosureCompilerEnabled = false) {
- this.isClosureCompilerEnabled = isClosureCompilerEnabled;
- }
- getConstLiteral(literal, forceShared) {
- if ((literal instanceof LiteralExpr && !isLongStringLiteral(literal)) ||
- literal instanceof FixupExpression) {
- // Do no put simple literals into the constant pool or try to produce a constant for a
- // reference to a constant.
- return literal;
- }
- const key = GenericKeyFn.INSTANCE.keyOf(literal);
- let fixup = this.literals.get(key);
- let newValue = false;
- if (!fixup) {
- fixup = new FixupExpression(literal);
- this.literals.set(key, fixup);
- newValue = true;
- }
- if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
- // Replace the expression with a variable
- const name = this.freshName();
- let definition;
- let usage;
- if (this.isClosureCompilerEnabled && isLongStringLiteral(literal)) {
- // For string literals, Closure will **always** inline the string at
- // **all** usages, duplicating it each time. For large strings, this
- // unnecessarily bloats bundle size. To work around this restriction, we
- // wrap the string in a function, and call that function for each usage.
- // This tricks Closure into using inline logic for functions instead of
- // string literals. Function calls are only inlined if the body is small
- // enough to be worth it. By doing this, very large strings will be
- // shared across multiple usages, rather than duplicating the string at
- // each usage site.
- //
- // const myStr = function() { return "very very very long string"; };
- // const usage1 = myStr();
- // const usage2 = myStr();
- definition = variable(name).set(new FunctionExpr([], // Params.
- [
- // Statements.
- new ReturnStatement(literal),
- ]));
- usage = variable(name).callFn([]);
- }
- else {
- // Just declare and use the variable directly, without a function call
- // indirection. This saves a few bytes and avoids an unnecessary call.
- definition = variable(name).set(literal);
- usage = variable(name);
- }
- this.statements.push(definition.toDeclStmt(INFERRED_TYPE, exports.StmtModifier.Final));
- fixup.fixup(usage);
- }
- return fixup;
- }
- getSharedConstant(def, expr) {
- const key = def.keyOf(expr);
- if (!this.sharedConstants.has(key)) {
- const id = this.freshName();
- this.sharedConstants.set(key, variable(id));
- this.statements.push(def.toSharedConstantDeclaration(id, expr));
- }
- return this.sharedConstants.get(key);
- }
- getLiteralFactory(literal) {
- // Create a pure function that builds an array of a mix of constant and variable expressions
- if (literal instanceof LiteralArrayExpr) {
- const argumentsForKey = literal.entries.map((e) => (e.isConstant() ? e : UNKNOWN_VALUE_KEY));
- const key = GenericKeyFn.INSTANCE.keyOf(literalArr(argumentsForKey));
- return this._getLiteralFactory(key, literal.entries, (entries) => literalArr(entries));
- }
- else {
- const expressionForKey = literalMap(literal.entries.map((e) => ({
- key: e.key,
- value: e.value.isConstant() ? e.value : UNKNOWN_VALUE_KEY,
- quoted: e.quoted,
- })));
- const key = GenericKeyFn.INSTANCE.keyOf(expressionForKey);
- return this._getLiteralFactory(key, literal.entries.map((e) => e.value), (entries) => literalMap(entries.map((value, index) => ({
- key: literal.entries[index].key,
- value,
- quoted: literal.entries[index].quoted,
- }))));
- }
- }
- // TODO: useUniqueName(false) is necessary for naming compatibility with
- // TemplateDefinitionBuilder, but should be removed once Template Pipeline is the default.
- getSharedFunctionReference(fn, prefix, useUniqueName = true) {
- const isArrow = fn instanceof ArrowFunctionExpr;
- for (const current of this.statements) {
- // Arrow functions are saved as variables so we check if the
- // value of the variable is the same as the arrow function.
- if (isArrow && current instanceof DeclareVarStmt && current.value?.isEquivalent(fn)) {
- return variable(current.name);
- }
- // Function declarations are saved as function statements
- // so we compare them directly to the passed-in function.
- if (!isArrow &&
- current instanceof DeclareFunctionStmt &&
- fn instanceof FunctionExpr &&
- fn.isEquivalent(current)) {
- return variable(current.name);
- }
- }
- // Otherwise declare the function.
- const name = useUniqueName ? this.uniqueName(prefix) : prefix;
- this.statements.push(fn instanceof FunctionExpr
- ? fn.toDeclStmt(name, exports.StmtModifier.Final)
- : new DeclareVarStmt(name, fn, INFERRED_TYPE, exports.StmtModifier.Final, fn.sourceSpan));
- return variable(name);
- }
- _getLiteralFactory(key, values, resultMap) {
- let literalFactory = this.literalFactories.get(key);
- const literalFactoryArguments = values.filter((e) => !e.isConstant());
- if (!literalFactory) {
- const resultExpressions = values.map((e, index) => e.isConstant() ? this.getConstLiteral(e, true) : variable(`a${index}`));
- const parameters = resultExpressions
- .filter(isVariable)
- .map((e) => new FnParam(e.name, DYNAMIC_TYPE));
- const pureFunctionDeclaration = arrowFn(parameters, resultMap(resultExpressions), INFERRED_TYPE);
- const name = this.freshName();
- this.statements.push(variable(name)
- .set(pureFunctionDeclaration)
- .toDeclStmt(INFERRED_TYPE, exports.StmtModifier.Final));
- literalFactory = variable(name);
- this.literalFactories.set(key, literalFactory);
- }
- return { literalFactory, literalFactoryArguments };
- }
- /**
- * Produce a unique name in the context of this pool.
- *
- * The name might be unique among different prefixes if any of the prefixes end in
- * a digit so the prefix should be a constant string (not based on user input) and
- * must not end in a digit.
- */
- uniqueName(name, alwaysIncludeSuffix = true) {
- const count = this._claimedNames.get(name) ?? 0;
- const result = count === 0 && !alwaysIncludeSuffix ? `${name}` : `${name}${count}`;
- this._claimedNames.set(name, count + 1);
- return result;
- }
- freshName() {
- return this.uniqueName(CONSTANT_PREFIX);
- }
- }
- class GenericKeyFn {
- static INSTANCE = new GenericKeyFn();
- keyOf(expr) {
- if (expr instanceof LiteralExpr && typeof expr.value === 'string') {
- return `"${expr.value}"`;
- }
- else if (expr instanceof LiteralExpr) {
- return String(expr.value);
- }
- else if (expr instanceof LiteralArrayExpr) {
- const entries = [];
- for (const entry of expr.entries) {
- entries.push(this.keyOf(entry));
- }
- return `[${entries.join(',')}]`;
- }
- else if (expr instanceof LiteralMapExpr) {
- const entries = [];
- for (const entry of expr.entries) {
- let key = entry.key;
- if (entry.quoted) {
- key = `"${key}"`;
- }
- entries.push(key + ':' + this.keyOf(entry.value));
- }
- return `{${entries.join(',')}}`;
- }
- else if (expr instanceof ExternalExpr) {
- return `import("${expr.value.moduleName}", ${expr.value.name})`;
- }
- else if (expr instanceof ReadVarExpr) {
- return `read(${expr.name})`;
- }
- else if (expr instanceof TypeofExpr) {
- return `typeof(${this.keyOf(expr.expr)})`;
- }
- else {
- throw new Error(`${this.constructor.name} does not handle expressions of type ${expr.constructor.name}`);
- }
- }
- }
- function isVariable(e) {
- return e instanceof ReadVarExpr;
- }
- function isLongStringLiteral(expr) {
- return (expr instanceof LiteralExpr &&
- typeof expr.value === 'string' &&
- expr.value.length >= POOL_INCLUSION_LENGTH_THRESHOLD_FOR_STRINGS);
- }
- const CORE = '@angular/core';
- class Identifiers {
- /* Methods */
- static NEW_METHOD = 'factory';
- static TRANSFORM_METHOD = 'transform';
- static PATCH_DEPS = 'patchedDeps';
- static core = { name: null, moduleName: CORE };
- /* Instructions */
- static namespaceHTML = { name: 'ɵɵnamespaceHTML', moduleName: CORE };
- static namespaceMathML = { name: 'ɵɵnamespaceMathML', moduleName: CORE };
- static namespaceSVG = { name: 'ɵɵnamespaceSVG', moduleName: CORE };
- static element = { name: 'ɵɵelement', moduleName: CORE };
- static elementStart = { name: 'ɵɵelementStart', moduleName: CORE };
- static elementEnd = { name: 'ɵɵelementEnd', moduleName: CORE };
- static advance = { name: 'ɵɵadvance', moduleName: CORE };
- static syntheticHostProperty = {
- name: 'ɵɵsyntheticHostProperty',
- moduleName: CORE,
- };
- static syntheticHostListener = {
- name: 'ɵɵsyntheticHostListener',
- moduleName: CORE,
- };
- static attribute = { name: 'ɵɵattribute', moduleName: CORE };
- static attributeInterpolate1 = {
- name: 'ɵɵattributeInterpolate1',
- moduleName: CORE,
- };
- static attributeInterpolate2 = {
- name: 'ɵɵattributeInterpolate2',
- moduleName: CORE,
- };
- static attributeInterpolate3 = {
- name: 'ɵɵattributeInterpolate3',
- moduleName: CORE,
- };
- static attributeInterpolate4 = {
- name: 'ɵɵattributeInterpolate4',
- moduleName: CORE,
- };
- static attributeInterpolate5 = {
- name: 'ɵɵattributeInterpolate5',
- moduleName: CORE,
- };
- static attributeInterpolate6 = {
- name: 'ɵɵattributeInterpolate6',
- moduleName: CORE,
- };
- static attributeInterpolate7 = {
- name: 'ɵɵattributeInterpolate7',
- moduleName: CORE,
- };
- static attributeInterpolate8 = {
- name: 'ɵɵattributeInterpolate8',
- moduleName: CORE,
- };
- static attributeInterpolateV = {
- name: 'ɵɵattributeInterpolateV',
- moduleName: CORE,
- };
- static classProp = { name: 'ɵɵclassProp', moduleName: CORE };
- static elementContainerStart = {
- name: 'ɵɵelementContainerStart',
- moduleName: CORE,
- };
- static elementContainerEnd = {
- name: 'ɵɵelementContainerEnd',
- moduleName: CORE,
- };
- static elementContainer = { name: 'ɵɵelementContainer', moduleName: CORE };
- static styleMap = { name: 'ɵɵstyleMap', moduleName: CORE };
- static styleMapInterpolate1 = {
- name: 'ɵɵstyleMapInterpolate1',
- moduleName: CORE,
- };
- static styleMapInterpolate2 = {
- name: 'ɵɵstyleMapInterpolate2',
- moduleName: CORE,
- };
- static styleMapInterpolate3 = {
- name: 'ɵɵstyleMapInterpolate3',
- moduleName: CORE,
- };
- static styleMapInterpolate4 = {
- name: 'ɵɵstyleMapInterpolate4',
- moduleName: CORE,
- };
- static styleMapInterpolate5 = {
- name: 'ɵɵstyleMapInterpolate5',
- moduleName: CORE,
- };
- static styleMapInterpolate6 = {
- name: 'ɵɵstyleMapInterpolate6',
- moduleName: CORE,
- };
- static styleMapInterpolate7 = {
- name: 'ɵɵstyleMapInterpolate7',
- moduleName: CORE,
- };
- static styleMapInterpolate8 = {
- name: 'ɵɵstyleMapInterpolate8',
- moduleName: CORE,
- };
- static styleMapInterpolateV = {
- name: 'ɵɵstyleMapInterpolateV',
- moduleName: CORE,
- };
- static classMap = { name: 'ɵɵclassMap', moduleName: CORE };
- static classMapInterpolate1 = {
- name: 'ɵɵclassMapInterpolate1',
- moduleName: CORE,
- };
- static classMapInterpolate2 = {
- name: 'ɵɵclassMapInterpolate2',
- moduleName: CORE,
- };
- static classMapInterpolate3 = {
- name: 'ɵɵclassMapInterpolate3',
- moduleName: CORE,
- };
- static classMapInterpolate4 = {
- name: 'ɵɵclassMapInterpolate4',
- moduleName: CORE,
- };
- static classMapInterpolate5 = {
- name: 'ɵɵclassMapInterpolate5',
- moduleName: CORE,
- };
- static classMapInterpolate6 = {
- name: 'ɵɵclassMapInterpolate6',
- moduleName: CORE,
- };
- static classMapInterpolate7 = {
- name: 'ɵɵclassMapInterpolate7',
- moduleName: CORE,
- };
- static classMapInterpolate8 = {
- name: 'ɵɵclassMapInterpolate8',
- moduleName: CORE,
- };
- static classMapInterpolateV = {
- name: 'ɵɵclassMapInterpolateV',
- moduleName: CORE,
- };
- static styleProp = { name: 'ɵɵstyleProp', moduleName: CORE };
- static stylePropInterpolate1 = {
- name: 'ɵɵstylePropInterpolate1',
- moduleName: CORE,
- };
- static stylePropInterpolate2 = {
- name: 'ɵɵstylePropInterpolate2',
- moduleName: CORE,
- };
- static stylePropInterpolate3 = {
- name: 'ɵɵstylePropInterpolate3',
- moduleName: CORE,
- };
- static stylePropInterpolate4 = {
- name: 'ɵɵstylePropInterpolate4',
- moduleName: CORE,
- };
- static stylePropInterpolate5 = {
- name: 'ɵɵstylePropInterpolate5',
- moduleName: CORE,
- };
- static stylePropInterpolate6 = {
- name: 'ɵɵstylePropInterpolate6',
- moduleName: CORE,
- };
- static stylePropInterpolate7 = {
- name: 'ɵɵstylePropInterpolate7',
- moduleName: CORE,
- };
- static stylePropInterpolate8 = {
- name: 'ɵɵstylePropInterpolate8',
- moduleName: CORE,
- };
- static stylePropInterpolateV = {
- name: 'ɵɵstylePropInterpolateV',
- moduleName: CORE,
- };
- static nextContext = { name: 'ɵɵnextContext', moduleName: CORE };
- static resetView = { name: 'ɵɵresetView', moduleName: CORE };
- static templateCreate = { name: 'ɵɵtemplate', moduleName: CORE };
- static defer = { name: 'ɵɵdefer', moduleName: CORE };
- static deferWhen = { name: 'ɵɵdeferWhen', moduleName: CORE };
- static deferOnIdle = { name: 'ɵɵdeferOnIdle', moduleName: CORE };
- static deferOnImmediate = { name: 'ɵɵdeferOnImmediate', moduleName: CORE };
- static deferOnTimer = { name: 'ɵɵdeferOnTimer', moduleName: CORE };
- static deferOnHover = { name: 'ɵɵdeferOnHover', moduleName: CORE };
- static deferOnInteraction = { name: 'ɵɵdeferOnInteraction', moduleName: CORE };
- static deferOnViewport = { name: 'ɵɵdeferOnViewport', moduleName: CORE };
- static deferPrefetchWhen = { name: 'ɵɵdeferPrefetchWhen', moduleName: CORE };
- static deferPrefetchOnIdle = {
- name: 'ɵɵdeferPrefetchOnIdle',
- moduleName: CORE,
- };
- static deferPrefetchOnImmediate = {
- name: 'ɵɵdeferPrefetchOnImmediate',
- moduleName: CORE,
- };
- static deferPrefetchOnTimer = {
- name: 'ɵɵdeferPrefetchOnTimer',
- moduleName: CORE,
- };
- static deferPrefetchOnHover = {
- name: 'ɵɵdeferPrefetchOnHover',
- moduleName: CORE,
- };
- static deferPrefetchOnInteraction = {
- name: 'ɵɵdeferPrefetchOnInteraction',
- moduleName: CORE,
- };
- static deferPrefetchOnViewport = {
- name: 'ɵɵdeferPrefetchOnViewport',
- moduleName: CORE,
- };
- static deferHydrateWhen = { name: 'ɵɵdeferHydrateWhen', moduleName: CORE };
- static deferHydrateNever = { name: 'ɵɵdeferHydrateNever', moduleName: CORE };
- static deferHydrateOnIdle = {
- name: 'ɵɵdeferHydrateOnIdle',
- moduleName: CORE,
- };
- static deferHydrateOnImmediate = {
- name: 'ɵɵdeferHydrateOnImmediate',
- moduleName: CORE,
- };
- static deferHydrateOnTimer = {
- name: 'ɵɵdeferHydrateOnTimer',
- moduleName: CORE,
- };
- static deferHydrateOnHover = {
- name: 'ɵɵdeferHydrateOnHover',
- moduleName: CORE,
- };
- static deferHydrateOnInteraction = {
- name: 'ɵɵdeferHydrateOnInteraction',
- moduleName: CORE,
- };
- static deferHydrateOnViewport = {
- name: 'ɵɵdeferHydrateOnViewport',
- moduleName: CORE,
- };
- static deferEnableTimerScheduling = {
- name: 'ɵɵdeferEnableTimerScheduling',
- moduleName: CORE,
- };
- static conditional = { name: 'ɵɵconditional', moduleName: CORE };
- static repeater = { name: 'ɵɵrepeater', moduleName: CORE };
- static repeaterCreate = { name: 'ɵɵrepeaterCreate', moduleName: CORE };
- static repeaterTrackByIndex = {
- name: 'ɵɵrepeaterTrackByIndex',
- moduleName: CORE,
- };
- static repeaterTrackByIdentity = {
- name: 'ɵɵrepeaterTrackByIdentity',
- moduleName: CORE,
- };
- static componentInstance = { name: 'ɵɵcomponentInstance', moduleName: CORE };
- static text = { name: 'ɵɵtext', moduleName: CORE };
- static enableBindings = { name: 'ɵɵenableBindings', moduleName: CORE };
- static disableBindings = { name: 'ɵɵdisableBindings', moduleName: CORE };
- static getCurrentView = { name: 'ɵɵgetCurrentView', moduleName: CORE };
- static textInterpolate = { name: 'ɵɵtextInterpolate', moduleName: CORE };
- static textInterpolate1 = { name: 'ɵɵtextInterpolate1', moduleName: CORE };
- static textInterpolate2 = { name: 'ɵɵtextInterpolate2', moduleName: CORE };
- static textInterpolate3 = { name: 'ɵɵtextInterpolate3', moduleName: CORE };
- static textInterpolate4 = { name: 'ɵɵtextInterpolate4', moduleName: CORE };
- static textInterpolate5 = { name: 'ɵɵtextInterpolate5', moduleName: CORE };
- static textInterpolate6 = { name: 'ɵɵtextInterpolate6', moduleName: CORE };
- static textInterpolate7 = { name: 'ɵɵtextInterpolate7', moduleName: CORE };
- static textInterpolate8 = { name: 'ɵɵtextInterpolate8', moduleName: CORE };
- static textInterpolateV = { name: 'ɵɵtextInterpolateV', moduleName: CORE };
- static restoreView = { name: 'ɵɵrestoreView', moduleName: CORE };
- static pureFunction0 = { name: 'ɵɵpureFunction0', moduleName: CORE };
- static pureFunction1 = { name: 'ɵɵpureFunction1', moduleName: CORE };
- static pureFunction2 = { name: 'ɵɵpureFunction2', moduleName: CORE };
- static pureFunction3 = { name: 'ɵɵpureFunction3', moduleName: CORE };
- static pureFunction4 = { name: 'ɵɵpureFunction4', moduleName: CORE };
- static pureFunction5 = { name: 'ɵɵpureFunction5', moduleName: CORE };
- static pureFunction6 = { name: 'ɵɵpureFunction6', moduleName: CORE };
- static pureFunction7 = { name: 'ɵɵpureFunction7', moduleName: CORE };
- static pureFunction8 = { name: 'ɵɵpureFunction8', moduleName: CORE };
- static pureFunctionV = { name: 'ɵɵpureFunctionV', moduleName: CORE };
- static pipeBind1 = { name: 'ɵɵpipeBind1', moduleName: CORE };
- static pipeBind2 = { name: 'ɵɵpipeBind2', moduleName: CORE };
- static pipeBind3 = { name: 'ɵɵpipeBind3', moduleName: CORE };
- static pipeBind4 = { name: 'ɵɵpipeBind4', moduleName: CORE };
- static pipeBindV = { name: 'ɵɵpipeBindV', moduleName: CORE };
- static hostProperty = { name: 'ɵɵhostProperty', moduleName: CORE };
- static property = { name: 'ɵɵproperty', moduleName: CORE };
- static propertyInterpolate = {
- name: 'ɵɵpropertyInterpolate',
- moduleName: CORE,
- };
- static propertyInterpolate1 = {
- name: 'ɵɵpropertyInterpolate1',
- moduleName: CORE,
- };
- static propertyInterpolate2 = {
- name: 'ɵɵpropertyInterpolate2',
- moduleName: CORE,
- };
- static propertyInterpolate3 = {
- name: 'ɵɵpropertyInterpolate3',
- moduleName: CORE,
- };
- static propertyInterpolate4 = {
- name: 'ɵɵpropertyInterpolate4',
- moduleName: CORE,
- };
- static propertyInterpolate5 = {
- name: 'ɵɵpropertyInterpolate5',
- moduleName: CORE,
- };
- static propertyInterpolate6 = {
- name: 'ɵɵpropertyInterpolate6',
- moduleName: CORE,
- };
- static propertyInterpolate7 = {
- name: 'ɵɵpropertyInterpolate7',
- moduleName: CORE,
- };
- static propertyInterpolate8 = {
- name: 'ɵɵpropertyInterpolate8',
- moduleName: CORE,
- };
- static propertyInterpolateV = {
- name: 'ɵɵpropertyInterpolateV',
- moduleName: CORE,
- };
- static i18n = { name: 'ɵɵi18n', moduleName: CORE };
- static i18nAttributes = { name: 'ɵɵi18nAttributes', moduleName: CORE };
- static i18nExp = { name: 'ɵɵi18nExp', moduleName: CORE };
- static i18nStart = { name: 'ɵɵi18nStart', moduleName: CORE };
- static i18nEnd = { name: 'ɵɵi18nEnd', moduleName: CORE };
- static i18nApply = { name: 'ɵɵi18nApply', moduleName: CORE };
- static i18nPostprocess = { name: 'ɵɵi18nPostprocess', moduleName: CORE };
- static pipe = { name: 'ɵɵpipe', moduleName: CORE };
- static projection = { name: 'ɵɵprojection', moduleName: CORE };
- static projectionDef = { name: 'ɵɵprojectionDef', moduleName: CORE };
- static reference = { name: 'ɵɵreference', moduleName: CORE };
- static inject = { name: 'ɵɵinject', moduleName: CORE };
- static injectAttribute = { name: 'ɵɵinjectAttribute', moduleName: CORE };
- static directiveInject = { name: 'ɵɵdirectiveInject', moduleName: CORE };
- static invalidFactory = { name: 'ɵɵinvalidFactory', moduleName: CORE };
- static invalidFactoryDep = { name: 'ɵɵinvalidFactoryDep', moduleName: CORE };
- static templateRefExtractor = {
- name: 'ɵɵtemplateRefExtractor',
- moduleName: CORE,
- };
- static forwardRef = { name: 'forwardRef', moduleName: CORE };
- static resolveForwardRef = { name: 'resolveForwardRef', moduleName: CORE };
- static replaceMetadata = { name: 'ɵɵreplaceMetadata', moduleName: CORE };
- static getReplaceMetadataURL = {
- name: 'ɵɵgetReplaceMetadataURL',
- moduleName: CORE,
- };
- static ɵɵdefineInjectable = { name: 'ɵɵdefineInjectable', moduleName: CORE };
- static declareInjectable = { name: 'ɵɵngDeclareInjectable', moduleName: CORE };
- static InjectableDeclaration = {
- name: 'ɵɵInjectableDeclaration',
- moduleName: CORE,
- };
- static resolveWindow = { name: 'ɵɵresolveWindow', moduleName: CORE };
- static resolveDocument = { name: 'ɵɵresolveDocument', moduleName: CORE };
- static resolveBody = { name: 'ɵɵresolveBody', moduleName: CORE };
- static getComponentDepsFactory = {
- name: 'ɵɵgetComponentDepsFactory',
- moduleName: CORE,
- };
- static defineComponent = { name: 'ɵɵdefineComponent', moduleName: CORE };
- static declareComponent = { name: 'ɵɵngDeclareComponent', moduleName: CORE };
- static setComponentScope = { name: 'ɵɵsetComponentScope', moduleName: CORE };
- static ChangeDetectionStrategy = {
- name: 'ChangeDetectionStrategy',
- moduleName: CORE,
- };
- static ViewEncapsulation = {
- name: 'ViewEncapsulation',
- moduleName: CORE,
- };
- static ComponentDeclaration = {
- name: 'ɵɵComponentDeclaration',
- moduleName: CORE,
- };
- static FactoryDeclaration = {
- name: 'ɵɵFactoryDeclaration',
- moduleName: CORE,
- };
- static declareFactory = { name: 'ɵɵngDeclareFactory', moduleName: CORE };
- static FactoryTarget = { name: 'ɵɵFactoryTarget', moduleName: CORE };
- static defineDirective = { name: 'ɵɵdefineDirective', moduleName: CORE };
- static declareDirective = { name: 'ɵɵngDeclareDirective', moduleName: CORE };
- static DirectiveDeclaration = {
- name: 'ɵɵDirectiveDeclaration',
- moduleName: CORE,
- };
- static InjectorDef = { name: 'ɵɵInjectorDef', moduleName: CORE };
- static InjectorDeclaration = {
- name: 'ɵɵInjectorDeclaration',
- moduleName: CORE,
- };
- static defineInjector = { name: 'ɵɵdefineInjector', moduleName: CORE };
- static declareInjector = { name: 'ɵɵngDeclareInjector', moduleName: CORE };
- static NgModuleDeclaration = {
- name: 'ɵɵNgModuleDeclaration',
- moduleName: CORE,
- };
- static ModuleWithProviders = {
- name: 'ModuleWithProviders',
- moduleName: CORE,
- };
- static defineNgModule = { name: 'ɵɵdefineNgModule', moduleName: CORE };
- static declareNgModule = { name: 'ɵɵngDeclareNgModule', moduleName: CORE };
- static setNgModuleScope = { name: 'ɵɵsetNgModuleScope', moduleName: CORE };
- static registerNgModuleType = {
- name: 'ɵɵregisterNgModuleType',
- moduleName: CORE,
- };
- static PipeDeclaration = { name: 'ɵɵPipeDeclaration', moduleName: CORE };
- static definePipe = { name: 'ɵɵdefinePipe', moduleName: CORE };
- static declarePipe = { name: 'ɵɵngDeclarePipe', moduleName: CORE };
- static declareClassMetadata = {
- name: 'ɵɵngDeclareClassMetadata',
- moduleName: CORE,
- };
- static declareClassMetadataAsync = {
- name: 'ɵɵngDeclareClassMetadataAsync',
- moduleName: CORE,
- };
- static setClassMetadata = { name: 'ɵsetClassMetadata', moduleName: CORE };
- static setClassMetadataAsync = {
- name: 'ɵsetClassMetadataAsync',
- moduleName: CORE,
- };
- static setClassDebugInfo = { name: 'ɵsetClassDebugInfo', moduleName: CORE };
- static queryRefresh = { name: 'ɵɵqueryRefresh', moduleName: CORE };
- static viewQuery = { name: 'ɵɵviewQuery', moduleName: CORE };
- static loadQuery = { name: 'ɵɵloadQuery', moduleName: CORE };
- static contentQuery = { name: 'ɵɵcontentQuery', moduleName: CORE };
- // Signal queries
- static viewQuerySignal = { name: 'ɵɵviewQuerySignal', moduleName: CORE };
- static contentQuerySignal = { name: 'ɵɵcontentQuerySignal', moduleName: CORE };
- static queryAdvance = { name: 'ɵɵqueryAdvance', moduleName: CORE };
- // Two-way bindings
- static twoWayProperty = { name: 'ɵɵtwoWayProperty', moduleName: CORE };
- static twoWayBindingSet = { name: 'ɵɵtwoWayBindingSet', moduleName: CORE };
- static twoWayListener = { name: 'ɵɵtwoWayListener', moduleName: CORE };
- static declareLet = { name: 'ɵɵdeclareLet', moduleName: CORE };
- static storeLet = { name: 'ɵɵstoreLet', moduleName: CORE };
- static readContextLet = { name: 'ɵɵreadContextLet', moduleName: CORE };
- static attachSourceLocations = {
- name: 'ɵɵattachSourceLocations',
- moduleName: CORE,
- };
- static NgOnChangesFeature = { name: 'ɵɵNgOnChangesFeature', moduleName: CORE };
- static InheritDefinitionFeature = {
- name: 'ɵɵInheritDefinitionFeature',
- moduleName: CORE,
- };
- static CopyDefinitionFeature = {
- name: 'ɵɵCopyDefinitionFeature',
- moduleName: CORE,
- };
- static ProvidersFeature = { name: 'ɵɵProvidersFeature', moduleName: CORE };
- static HostDirectivesFeature = {
- name: 'ɵɵHostDirectivesFeature',
- moduleName: CORE,
- };
- static ExternalStylesFeature = {
- name: 'ɵɵExternalStylesFeature',
- moduleName: CORE,
- };
- static listener = { name: 'ɵɵlistener', moduleName: CORE };
- static getInheritedFactory = {
- name: 'ɵɵgetInheritedFactory',
- moduleName: CORE,
- };
- // sanitization-related functions
- static sanitizeHtml = { name: 'ɵɵsanitizeHtml', moduleName: CORE };
- static sanitizeStyle = { name: 'ɵɵsanitizeStyle', moduleName: CORE };
- static sanitizeResourceUrl = {
- name: 'ɵɵsanitizeResourceUrl',
- moduleName: CORE,
- };
- static sanitizeScript = { name: 'ɵɵsanitizeScript', moduleName: CORE };
- static sanitizeUrl = { name: 'ɵɵsanitizeUrl', moduleName: CORE };
- static sanitizeUrlOrResourceUrl = {
- name: 'ɵɵsanitizeUrlOrResourceUrl',
- moduleName: CORE,
- };
- static trustConstantHtml = { name: 'ɵɵtrustConstantHtml', moduleName: CORE };
- static trustConstantResourceUrl = {
- name: 'ɵɵtrustConstantResourceUrl',
- moduleName: CORE,
- };
- static validateIframeAttribute = {
- name: 'ɵɵvalidateIframeAttribute',
- moduleName: CORE,
- };
- // type-checking
- static InputSignalBrandWriteType = { name: 'ɵINPUT_SIGNAL_BRAND_WRITE_TYPE', moduleName: CORE };
- static UnwrapDirectiveSignalInputs = { name: 'ɵUnwrapDirectiveSignalInputs', moduleName: CORE };
- static unwrapWritableSignal = { name: 'ɵunwrapWritableSignal', moduleName: CORE };
- }
- const DASH_CASE_REGEXP = /-+([a-z0-9])/g;
- function dashCaseToCamelCase(input) {
- return input.replace(DASH_CASE_REGEXP, (...m) => m[1].toUpperCase());
- }
- function splitAtColon(input, defaultValues) {
- return _splitAt(input, ':', defaultValues);
- }
- function splitAtPeriod(input, defaultValues) {
- return _splitAt(input, '.', defaultValues);
- }
- function _splitAt(input, character, defaultValues) {
- const characterIndex = input.indexOf(character);
- if (characterIndex == -1)
- return defaultValues;
- return [input.slice(0, characterIndex).trim(), input.slice(characterIndex + 1).trim()];
- }
- function utf8Encode(str) {
- let encoded = [];
- for (let index = 0; index < str.length; index++) {
- let codePoint = str.charCodeAt(index);
- // decode surrogate
- // see https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
- if (codePoint >= 0xd800 && codePoint <= 0xdbff && str.length > index + 1) {
- const low = str.charCodeAt(index + 1);
- if (low >= 0xdc00 && low <= 0xdfff) {
- index++;
- codePoint = ((codePoint - 0xd800) << 10) + low - 0xdc00 + 0x10000;
- }
- }
- if (codePoint <= 0x7f) {
- encoded.push(codePoint);
- }
- else if (codePoint <= 0x7ff) {
- encoded.push(((codePoint >> 6) & 0x1f) | 0xc0, (codePoint & 0x3f) | 0x80);
- }
- else if (codePoint <= 0xffff) {
- encoded.push((codePoint >> 12) | 0xe0, ((codePoint >> 6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80);
- }
- else if (codePoint <= 0x1fffff) {
- encoded.push(((codePoint >> 18) & 0x07) | 0xf0, ((codePoint >> 12) & 0x3f) | 0x80, ((codePoint >> 6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80);
- }
- }
- return encoded;
- }
- function stringify(token) {
- if (typeof token === 'string') {
- return token;
- }
- if (Array.isArray(token)) {
- return `[${token.map(stringify).join(', ')}]`;
- }
- if (token == null) {
- return '' + token;
- }
- const name = token.overriddenName || token.name;
- if (name) {
- return `${name}`;
- }
- if (!token.toString) {
- return 'object';
- }
- // WARNING: do not try to `JSON.stringify(token)` here
- // see https://github.com/angular/angular/issues/23440
- const result = token.toString();
- if (result == null) {
- return '' + result;
- }
- const newLineIndex = result.indexOf('\n');
- return newLineIndex >= 0 ? result.slice(0, newLineIndex) : result;
- }
- class Version {
- full;
- major;
- minor;
- patch;
- constructor(full) {
- this.full = full;
- const splits = full.split('.');
- this.major = splits[0];
- this.minor = splits[1];
- this.patch = splits.slice(2).join('.');
- }
- }
- const _global = globalThis;
- const V1_TO_18 = /^([1-9]|1[0-8])\./;
- function getJitStandaloneDefaultForVersion(version) {
- if (version.startsWith('0.')) {
- // 0.0.0 is always "latest", default is true.
- return true;
- }
- if (V1_TO_18.test(version)) {
- // Angular v2 - v18 default is false.
- return false;
- }
- // All other Angular versions (v19+) default to true.
- return true;
- }
- // https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
- const VERSION = 3;
- const JS_B64_PREFIX = '# sourceMappingURL=data:application/json;base64,';
- class SourceMapGenerator {
- file;
- sourcesContent = new Map();
- lines = [];
- lastCol0 = 0;
- hasMappings = false;
- constructor(file = null) {
- this.file = file;
- }
- // The content is `null` when the content is expected to be loaded using the URL
- addSource(url, content = null) {
- if (!this.sourcesContent.has(url)) {
- this.sourcesContent.set(url, content);
- }
- return this;
- }
- addLine() {
- this.lines.push([]);
- this.lastCol0 = 0;
- return this;
- }
- addMapping(col0, sourceUrl, sourceLine0, sourceCol0) {
- if (!this.currentLine) {
- throw new Error(`A line must be added before mappings can be added`);
- }
- if (sourceUrl != null && !this.sourcesContent.has(sourceUrl)) {
- throw new Error(`Unknown source file "${sourceUrl}"`);
- }
- if (col0 == null) {
- throw new Error(`The column in the generated code must be provided`);
- }
- if (col0 < this.lastCol0) {
- throw new Error(`Mapping should be added in output order`);
- }
- if (sourceUrl && (sourceLine0 == null || sourceCol0 == null)) {
- throw new Error(`The source location must be provided when a source url is provided`);
- }
- this.hasMappings = true;
- this.lastCol0 = col0;
- this.currentLine.push({ col0, sourceUrl, sourceLine0, sourceCol0 });
- return this;
- }
- /**
- * @internal strip this from published d.ts files due to
- * https://github.com/microsoft/TypeScript/issues/36216
- */
- get currentLine() {
- return this.lines.slice(-1)[0];
- }
- toJSON() {
- if (!this.hasMappings) {
- return null;
- }
- const sourcesIndex = new Map();
- const sources = [];
- const sourcesContent = [];
- Array.from(this.sourcesContent.keys()).forEach((url, i) => {
- sourcesIndex.set(url, i);
- sources.push(url);
- sourcesContent.push(this.sourcesContent.get(url) || null);
- });
- let mappings = '';
- let lastCol0 = 0;
- let lastSourceIndex = 0;
- let lastSourceLine0 = 0;
- let lastSourceCol0 = 0;
- this.lines.forEach((segments) => {
- lastCol0 = 0;
- mappings += segments
- .map((segment) => {
- // zero-based starting column of the line in the generated code
- let segAsStr = toBase64VLQ(segment.col0 - lastCol0);
- lastCol0 = segment.col0;
- if (segment.sourceUrl != null) {
- // zero-based index into the “sources” list
- segAsStr += toBase64VLQ(sourcesIndex.get(segment.sourceUrl) - lastSourceIndex);
- lastSourceIndex = sourcesIndex.get(segment.sourceUrl);
- // the zero-based starting line in the original source
- segAsStr += toBase64VLQ(segment.sourceLine0 - lastSourceLine0);
- lastSourceLine0 = segment.sourceLine0;
- // the zero-based starting column in the original source
- segAsStr += toBase64VLQ(segment.sourceCol0 - lastSourceCol0);
- lastSourceCol0 = segment.sourceCol0;
- }
- return segAsStr;
- })
- .join(',');
- mappings += ';';
- });
- mappings = mappings.slice(0, -1);
- return {
- 'file': this.file || '',
- 'version': VERSION,
- 'sourceRoot': '',
- 'sources': sources,
- 'sourcesContent': sourcesContent,
- 'mappings': mappings,
- };
- }
- toJsComment() {
- return this.hasMappings
- ? '//' + JS_B64_PREFIX + toBase64String(JSON.stringify(this, null, 0))
- : '';
- }
- }
- function toBase64String(value) {
- let b64 = '';
- const encoded = utf8Encode(value);
- for (let i = 0; i < encoded.length;) {
- const i1 = encoded[i++];
- const i2 = i < encoded.length ? encoded[i++] : null;
- const i3 = i < encoded.length ? encoded[i++] : null;
- b64 += toBase64Digit(i1 >> 2);
- b64 += toBase64Digit(((i1 & 3) << 4) | (i2 === null ? 0 : i2 >> 4));
- b64 += i2 === null ? '=' : toBase64Digit(((i2 & 15) << 2) | (i3 === null ? 0 : i3 >> 6));
- b64 += i2 === null || i3 === null ? '=' : toBase64Digit(i3 & 63);
- }
- return b64;
- }
- function toBase64VLQ(value) {
- value = value < 0 ? (-value << 1) + 1 : value << 1;
- let out = '';
- do {
- let digit = value & 31;
- value = value >> 5;
- if (value > 0) {
- digit = digit | 32;
- }
- out += toBase64Digit(digit);
- } while (value > 0);
- return out;
- }
- const B64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
- function toBase64Digit(value) {
- if (value < 0 || value >= 64) {
- throw new Error(`Can only encode value in the range [0, 63]`);
- }
- return B64_DIGITS[value];
- }
- const _SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n|\r|\$/g;
- const _LEGAL_IDENTIFIER_RE = /^[$A-Z_][0-9A-Z_$]*$/i;
- const _INDENT_WITH = ' ';
- class _EmittedLine {
- indent;
- partsLength = 0;
- parts = [];
- srcSpans = [];
- constructor(indent) {
- this.indent = indent;
- }
- }
- class EmitterVisitorContext {
- _indent;
- static createRoot() {
- return new EmitterVisitorContext(0);
- }
- _lines;
- constructor(_indent) {
- this._indent = _indent;
- this._lines = [new _EmittedLine(_indent)];
- }
- /**
- * @internal strip this from published d.ts files due to
- * https://github.com/microsoft/TypeScript/issues/36216
- */
- get _currentLine() {
- return this._lines[this._lines.length - 1];
- }
- println(from, lastPart = '') {
- this.print(from || null, lastPart, true);
- }
- lineIsEmpty() {
- return this._currentLine.parts.length === 0;
- }
- lineLength() {
- return this._currentLine.indent * _INDENT_WITH.length + this._currentLine.partsLength;
- }
- print(from, part, newLine = false) {
- if (part.length > 0) {
- this._currentLine.parts.push(part);
- this._currentLine.partsLength += part.length;
- this._currentLine.srcSpans.push((from && from.sourceSpan) || null);
- }
- if (newLine) {
- this._lines.push(new _EmittedLine(this._indent));
- }
- }
- removeEmptyLastLine() {
- if (this.lineIsEmpty()) {
- this._lines.pop();
- }
- }
- incIndent() {
- this._indent++;
- if (this.lineIsEmpty()) {
- this._currentLine.indent = this._indent;
- }
- }
- decIndent() {
- this._indent--;
- if (this.lineIsEmpty()) {
- this._currentLine.indent = this._indent;
- }
- }
- toSource() {
- return this.sourceLines
- .map((l) => (l.parts.length > 0 ? _createIndent(l.indent) + l.parts.join('') : ''))
- .join('\n');
- }
- toSourceMapGenerator(genFilePath, startsAtLine = 0) {
- const map = new SourceMapGenerator(genFilePath);
- let firstOffsetMapped = false;
- const mapFirstOffsetIfNeeded = () => {
- if (!firstOffsetMapped) {
- // Add a single space so that tools won't try to load the file from disk.
- // Note: We are using virtual urls like `ng:///`, so we have to
- // provide a content here.
- map.addSource(genFilePath, ' ').addMapping(0, genFilePath, 0, 0);
- firstOffsetMapped = true;
- }
- };
- for (let i = 0; i < startsAtLine; i++) {
- map.addLine();
- mapFirstOffsetIfNeeded();
- }
- this.sourceLines.forEach((line, lineIdx) => {
- map.addLine();
- const spans = line.srcSpans;
- const parts = line.parts;
- let col0 = line.indent * _INDENT_WITH.length;
- let spanIdx = 0;
- // skip leading parts without source spans
- while (spanIdx < spans.length && !spans[spanIdx]) {
- col0 += parts[spanIdx].length;
- spanIdx++;
- }
- if (spanIdx < spans.length && lineIdx === 0 && col0 === 0) {
- firstOffsetMapped = true;
- }
- else {
- mapFirstOffsetIfNeeded();
- }
- while (spanIdx < spans.length) {
- const span = spans[spanIdx];
- const source = span.start.file;
- const sourceLine = span.start.line;
- const sourceCol = span.start.col;
- map
- .addSource(source.url, source.content)
- .addMapping(col0, source.url, sourceLine, sourceCol);
- col0 += parts[spanIdx].length;
- spanIdx++;
- // assign parts without span or the same span to the previous segment
- while (spanIdx < spans.length && (span === spans[spanIdx] || !spans[spanIdx])) {
- col0 += parts[spanIdx].length;
- spanIdx++;
- }
- }
- });
- return map;
- }
- spanOf(line, column) {
- const emittedLine = this._lines[line];
- if (emittedLine) {
- let columnsLeft = column - _createIndent(emittedLine.indent).length;
- for (let partIndex = 0; partIndex < emittedLine.parts.length; partIndex++) {
- const part = emittedLine.parts[partIndex];
- if (part.length > columnsLeft) {
- return emittedLine.srcSpans[partIndex];
- }
- columnsLeft -= part.length;
- }
- }
- return null;
- }
- /**
- * @internal strip this from published d.ts files due to
- * https://github.com/microsoft/TypeScript/issues/36216
- */
- get sourceLines() {
- if (this._lines.length && this._lines[this._lines.length - 1].parts.length === 0) {
- return this._lines.slice(0, -1);
- }
- return this._lines;
- }
- }
- class AbstractEmitterVisitor {
- _escapeDollarInStrings;
- constructor(_escapeDollarInStrings) {
- this._escapeDollarInStrings = _escapeDollarInStrings;
- }
- printLeadingComments(stmt, ctx) {
- if (stmt.leadingComments === undefined) {
- return;
- }
- for (const comment of stmt.leadingComments) {
- if (comment instanceof JSDocComment) {
- ctx.print(stmt, `/*${comment.toString()}*/`, comment.trailingNewline);
- }
- else {
- if (comment.multiline) {
- ctx.print(stmt, `/* ${comment.text} */`, comment.trailingNewline);
- }
- else {
- comment.text.split('\n').forEach((line) => {
- ctx.println(stmt, `// ${line}`);
- });
- }
- }
- }
- }
- visitExpressionStmt(stmt, ctx) {
- this.printLeadingComments(stmt, ctx);
- stmt.expr.visitExpression(this, ctx);
- ctx.println(stmt, ';');
- return null;
- }
- visitReturnStmt(stmt, ctx) {
- this.printLeadingComments(stmt, ctx);
- ctx.print(stmt, `return `);
- stmt.value.visitExpression(this, ctx);
- ctx.println(stmt, ';');
- return null;
- }
- visitIfStmt(stmt, ctx) {
- this.printLeadingComments(stmt, ctx);
- ctx.print(stmt, `if (`);
- stmt.condition.visitExpression(this, ctx);
- ctx.print(stmt, `) {`);
- const hasElseCase = stmt.falseCase != null && stmt.falseCase.length > 0;
- if (stmt.trueCase.length <= 1 && !hasElseCase) {
- ctx.print(stmt, ` `);
- this.visitAllStatements(stmt.trueCase, ctx);
- ctx.removeEmptyLastLine();
- ctx.print(stmt, ` `);
- }
- else {
- ctx.println();
- ctx.incIndent();
- this.visitAllStatements(stmt.trueCase, ctx);
- ctx.decIndent();
- if (hasElseCase) {
- ctx.println(stmt, `} else {`);
- ctx.incIndent();
- this.visitAllStatements(stmt.falseCase, ctx);
- ctx.decIndent();
- }
- }
- ctx.println(stmt, `}`);
- return null;
- }
- visitWriteVarExpr(expr, ctx) {
- const lineWasEmpty = ctx.lineIsEmpty();
- if (!lineWasEmpty) {
- ctx.print(expr, '(');
- }
- ctx.print(expr, `${expr.name} = `);
- expr.value.visitExpression(this, ctx);
- if (!lineWasEmpty) {
- ctx.print(expr, ')');
- }
- return null;
- }
- visitWriteKeyExpr(expr, ctx) {
- const lineWasEmpty = ctx.lineIsEmpty();
- if (!lineWasEmpty) {
- ctx.print(expr, '(');
- }
- expr.receiver.visitExpression(this, ctx);
- ctx.print(expr, `[`);
- expr.index.visitExpression(this, ctx);
- ctx.print(expr, `] = `);
- expr.value.visitExpression(this, ctx);
- if (!lineWasEmpty) {
- ctx.print(expr, ')');
- }
- return null;
- }
- visitWritePropExpr(expr, ctx) {
- const lineWasEmpty = ctx.lineIsEmpty();
- if (!lineWasEmpty) {
- ctx.print(expr, '(');
- }
- expr.receiver.visitExpression(this, ctx);
- ctx.print(expr, `.${expr.name} = `);
- expr.value.visitExpression(this, ctx);
- if (!lineWasEmpty) {
- ctx.print(expr, ')');
- }
- return null;
- }
- visitInvokeFunctionExpr(expr, ctx) {
- const shouldParenthesize = expr.fn instanceof ArrowFunctionExpr;
- if (shouldParenthesize) {
- ctx.print(expr.fn, '(');
- }
- expr.fn.visitExpression(this, ctx);
- if (shouldParenthesize) {
- ctx.print(expr.fn, ')');
- }
- ctx.print(expr, `(`);
- this.visitAllExpressions(expr.args, ctx, ',');
- ctx.print(expr, `)`);
- return null;
- }
- visitTaggedTemplateLiteralExpr(expr, ctx) {
- expr.tag.visitExpression(this, ctx);
- expr.template.visitExpression(this, ctx);
- return null;
- }
- visitTemplateLiteralExpr(expr, ctx) {
- ctx.print(expr, '`');
- for (let i = 0; i < expr.elements.length; i++) {
- expr.elements[i].visitExpression(this, ctx);
- const expression = i < expr.expressions.length ? expr.expressions[i] : null;
- if (expression !== null) {
- ctx.print(expression, '${');
- expression.visitExpression(this, ctx);
- ctx.print(expression, '}');
- }
- }
- ctx.print(expr, '`');
- }
- visitTemplateLiteralElementExpr(expr, ctx) {
- ctx.print(expr, expr.rawText);
- }
- visitWrappedNodeExpr(ast, ctx) {
- throw new Error('Abstract emitter cannot visit WrappedNodeExpr.');
- }
- visitTypeofExpr(expr, ctx) {
- ctx.print(expr, 'typeof ');
- expr.expr.visitExpression(this, ctx);
- }
- visitReadVarExpr(ast, ctx) {
- ctx.print(ast, ast.name);
- return null;
- }
- visitInstantiateExpr(ast, ctx) {
- ctx.print(ast, `new `);
- ast.classExpr.visitExpression(this, ctx);
- ctx.print(ast, `(`);
- this.visitAllExpressions(ast.args, ctx, ',');
- ctx.print(ast, `)`);
- return null;
- }
- visitLiteralExpr(ast, ctx) {
- const value = ast.value;
- if (typeof value === 'string') {
- ctx.print(ast, escapeIdentifier(value, this._escapeDollarInStrings));
- }
- else {
- ctx.print(ast, `${value}`);
- }
- return null;
- }
- visitLocalizedString(ast, ctx) {
- const head = ast.serializeI18nHead();
- ctx.print(ast, '$localize `' + head.raw);
- for (let i = 1; i < ast.messageParts.length; i++) {
- ctx.print(ast, '${');
- ast.expressions[i - 1].visitExpression(this, ctx);
- ctx.print(ast, `}${ast.serializeI18nTemplatePart(i).raw}`);
- }
- ctx.print(ast, '`');
- return null;
- }
- visitConditionalExpr(ast, ctx) {
- ctx.print(ast, `(`);
- ast.condition.visitExpression(this, ctx);
- ctx.print(ast, '? ');
- ast.trueCase.visitExpression(this, ctx);
- ctx.print(ast, ': ');
- ast.falseCase.visitExpression(this, ctx);
- ctx.print(ast, `)`);
- return null;
- }
- visitDynamicImportExpr(ast, ctx) {
- ctx.print(ast, `import(${ast.url})`);
- }
- visitNotExpr(ast, ctx) {
- ctx.print(ast, '!');
- ast.condition.visitExpression(this, ctx);
- return null;
- }
- visitUnaryOperatorExpr(ast, ctx) {
- let opStr;
- switch (ast.operator) {
- case UnaryOperator.Plus:
- opStr = '+';
- break;
- case UnaryOperator.Minus:
- opStr = '-';
- break;
- default:
- throw new Error(`Unknown operator ${ast.operator}`);
- }
- if (ast.parens)
- ctx.print(ast, `(`);
- ctx.print(ast, opStr);
- ast.expr.visitExpression(this, ctx);
- if (ast.parens)
- ctx.print(ast, `)`);
- return null;
- }
- visitBinaryOperatorExpr(ast, ctx) {
- let opStr;
- switch (ast.operator) {
- case BinaryOperator.Equals:
- opStr = '==';
- break;
- case BinaryOperator.Identical:
- opStr = '===';
- break;
- case BinaryOperator.NotEquals:
- opStr = '!=';
- break;
- case BinaryOperator.NotIdentical:
- opStr = '!==';
- break;
- case BinaryOperator.And:
- opStr = '&&';
- break;
- case BinaryOperator.BitwiseOr:
- opStr = '|';
- break;
- case BinaryOperator.BitwiseAnd:
- opStr = '&';
- break;
- case BinaryOperator.Or:
- opStr = '||';
- break;
- case BinaryOperator.Plus:
- opStr = '+';
- break;
- case BinaryOperator.Minus:
- opStr = '-';
- break;
- case BinaryOperator.Divide:
- opStr = '/';
- break;
- case BinaryOperator.Multiply:
- opStr = '*';
- break;
- case BinaryOperator.Modulo:
- opStr = '%';
- break;
- case BinaryOperator.Lower:
- opStr = '<';
- break;
- case BinaryOperator.LowerEquals:
- opStr = '<=';
- break;
- case BinaryOperator.Bigger:
- opStr = '>';
- break;
- case BinaryOperator.BiggerEquals:
- opStr = '>=';
- break;
- case BinaryOperator.NullishCoalesce:
- opStr = '??';
- break;
- default:
- throw new Error(`Unknown operator ${ast.operator}`);
- }
- if (ast.parens)
- ctx.print(ast, `(`);
- ast.lhs.visitExpression(this, ctx);
- ctx.print(ast, ` ${opStr} `);
- ast.rhs.visitExpression(this, ctx);
- if (ast.parens)
- ctx.print(ast, `)`);
- return null;
- }
- visitReadPropExpr(ast, ctx) {
- ast.receiver.visitExpression(this, ctx);
- ctx.print(ast, `.`);
- ctx.print(ast, ast.name);
- return null;
- }
- visitReadKeyExpr(ast, ctx) {
- ast.receiver.visitExpression(this, ctx);
- ctx.print(ast, `[`);
- ast.index.visitExpression(this, ctx);
- ctx.print(ast, `]`);
- return null;
- }
- visitLiteralArrayExpr(ast, ctx) {
- ctx.print(ast, `[`);
- this.visitAllExpressions(ast.entries, ctx, ',');
- ctx.print(ast, `]`);
- return null;
- }
- visitLiteralMapExpr(ast, ctx) {
- ctx.print(ast, `{`);
- this.visitAllObjects((entry) => {
- ctx.print(ast, `${escapeIdentifier(entry.key, this._escapeDollarInStrings, entry.quoted)}:`);
- entry.value.visitExpression(this, ctx);
- }, ast.entries, ctx, ',');
- ctx.print(ast, `}`);
- return null;
- }
- visitCommaExpr(ast, ctx) {
- ctx.print(ast, '(');
- this.visitAllExpressions(ast.parts, ctx, ',');
- ctx.print(ast, ')');
- return null;
- }
- visitAllExpressions(expressions, ctx, separator) {
- this.visitAllObjects((expr) => expr.visitExpression(this, ctx), expressions, ctx, separator);
- }
- visitAllObjects(handler, expressions, ctx, separator) {
- let incrementedIndent = false;
- for (let i = 0; i < expressions.length; i++) {
- if (i > 0) {
- if (ctx.lineLength() > 80) {
- ctx.print(null, separator, true);
- if (!incrementedIndent) {
- // continuation are marked with double indent.
- ctx.incIndent();
- ctx.incIndent();
- incrementedIndent = true;
- }
- }
- else {
- ctx.print(null, separator, false);
- }
- }
- handler(expressions[i]);
- }
- if (incrementedIndent) {
- // continuation are marked with double indent.
- ctx.decIndent();
- ctx.decIndent();
- }
- }
- visitAllStatements(statements, ctx) {
- statements.forEach((stmt) => stmt.visitStatement(this, ctx));
- }
- }
- function escapeIdentifier(input, escapeDollar, alwaysQuote = true) {
- if (input == null) {
- return null;
- }
- const body = input.replace(_SINGLE_QUOTE_ESCAPE_STRING_RE, (...match) => {
- if (match[0] == '$') {
- return escapeDollar ? '\\$' : '$';
- }
- else if (match[0] == '\n') {
- return '\\n';
- }
- else if (match[0] == '\r') {
- return '\\r';
- }
- else {
- return `\\${match[0]}`;
- }
- });
- const requiresQuotes = alwaysQuote || !_LEGAL_IDENTIFIER_RE.test(body);
- return requiresQuotes ? `'${body}'` : body;
- }
- function _createIndent(count) {
- let res = '';
- for (let i = 0; i < count; i++) {
- res += _INDENT_WITH;
- }
- return res;
- }
- function typeWithParameters(type, numParams) {
- if (numParams === 0) {
- return expressionType(type);
- }
- const params = [];
- for (let i = 0; i < numParams; i++) {
- params.push(DYNAMIC_TYPE);
- }
- return expressionType(type, undefined, params);
- }
- function getSafePropertyAccessString(accessor, name) {
- const escapedName = escapeIdentifier(name, false, false);
- return escapedName !== name ? `${accessor}[${escapedName}]` : `${accessor}.${name}`;
- }
- function jitOnlyGuardedExpression(expr) {
- return guardedExpression('ngJitMode', expr);
- }
- function devOnlyGuardedExpression(expr) {
- return guardedExpression('ngDevMode', expr);
- }
- function guardedExpression(guard, expr) {
- const guardExpr = new ExternalExpr({ name: guard, moduleName: null });
- const guardNotDefined = new BinaryOperatorExpr(BinaryOperator.Identical, new TypeofExpr(guardExpr), literal$1('undefined'));
- const guardUndefinedOrTrue = new BinaryOperatorExpr(BinaryOperator.Or, guardNotDefined, guardExpr,
- /* type */ undefined,
- /* sourceSpan */ undefined, true);
- return new BinaryOperatorExpr(BinaryOperator.And, guardUndefinedOrTrue, expr);
- }
- function wrapReference(value) {
- const wrapped = new WrappedNodeExpr(value);
- return { value: wrapped, type: wrapped };
- }
- function refsToArray(refs, shouldForwardDeclare) {
- const values = literalArr(refs.map((ref) => ref.value));
- return shouldForwardDeclare ? arrowFn([], values) : values;
- }
- function createMayBeForwardRefExpression(expression, forwardRef) {
- return { expression, forwardRef };
- }
- /**
- * Convert a `MaybeForwardRefExpression` to an `Expression`, possibly wrapping its expression in a
- * `forwardRef()` call.
- *
- * If `MaybeForwardRefExpression.forwardRef` is `ForwardRefHandling.Unwrapped` then the expression
- * was originally wrapped in a `forwardRef()` call to prevent the value from being eagerly evaluated
- * in the code.
- *
- * See `packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts` and
- * `packages/compiler/src/jit_compiler_facade.ts` for more information.
- */
- function convertFromMaybeForwardRefExpression({ expression, forwardRef, }) {
- switch (forwardRef) {
- case 0 /* ForwardRefHandling.None */:
- case 1 /* ForwardRefHandling.Wrapped */:
- return expression;
- case 2 /* ForwardRefHandling.Unwrapped */:
- return generateForwardRef(expression);
- }
- }
- /**
- * Generate an expression that has the given `expr` wrapped in the following form:
- *
- * ```ts
- * forwardRef(() => expr)
- * ```
- */
- function generateForwardRef(expr) {
- return importExpr(Identifiers.forwardRef).callFn([arrowFn([], expr)]);
- }
- var R3FactoryDelegateType;
- (function (R3FactoryDelegateType) {
- R3FactoryDelegateType[R3FactoryDelegateType["Class"] = 0] = "Class";
- R3FactoryDelegateType[R3FactoryDelegateType["Function"] = 1] = "Function";
- })(R3FactoryDelegateType || (R3FactoryDelegateType = {}));
- /**
- * Construct a factory function expression for the given `R3FactoryMetadata`.
- */
- function compileFactoryFunction(meta) {
- const t = variable('__ngFactoryType__');
- let baseFactoryVar = null;
- // The type to instantiate via constructor invocation. If there is no delegated factory, meaning
- // this type is always created by constructor invocation, then this is the type-to-create
- // parameter provided by the user (t) if specified, or the current type if not. If there is a
- // delegated factory (which is used to create the current type) then this is only the type-to-
- // create parameter (t).
- const typeForCtor = !isDelegatedFactoryMetadata(meta)
- ? new BinaryOperatorExpr(BinaryOperator.Or, t, meta.type.value)
- : t;
- let ctorExpr = null;
- if (meta.deps !== null) {
- // There is a constructor (either explicitly or implicitly defined).
- if (meta.deps !== 'invalid') {
- ctorExpr = new InstantiateExpr(typeForCtor, injectDependencies(meta.deps, meta.target));
- }
- }
- else {
- // There is no constructor, use the base class' factory to construct typeForCtor.
- baseFactoryVar = variable(`ɵ${meta.name}_BaseFactory`);
- ctorExpr = baseFactoryVar.callFn([typeForCtor]);
- }
- const body = [];
- let retExpr = null;
- function makeConditionalFactory(nonCtorExpr) {
- const r = variable('__ngConditionalFactory__');
- body.push(r.set(NULL_EXPR).toDeclStmt());
- const ctorStmt = ctorExpr !== null
- ? r.set(ctorExpr).toStmt()
- : importExpr(Identifiers.invalidFactory).callFn([]).toStmt();
- body.push(ifStmt(t, [ctorStmt], [r.set(nonCtorExpr).toStmt()]));
- return r;
- }
- if (isDelegatedFactoryMetadata(meta)) {
- // This type is created with a delegated factory. If a type parameter is not specified, call
- // the factory instead.
- const delegateArgs = injectDependencies(meta.delegateDeps, meta.target);
- // Either call `new delegate(...)` or `delegate(...)` depending on meta.delegateType.
- const factoryExpr = new (meta.delegateType === R3FactoryDelegateType.Class ? InstantiateExpr : InvokeFunctionExpr)(meta.delegate, delegateArgs);
- retExpr = makeConditionalFactory(factoryExpr);
- }
- else if (isExpressionFactoryMetadata(meta)) {
- // TODO(alxhub): decide whether to lower the value here or in the caller
- retExpr = makeConditionalFactory(meta.expression);
- }
- else {
- retExpr = ctorExpr;
- }
- if (retExpr === null) {
- // The expression cannot be formed so render an `ɵɵinvalidFactory()` call.
- body.push(importExpr(Identifiers.invalidFactory).callFn([]).toStmt());
- }
- else if (baseFactoryVar !== null) {
- // This factory uses a base factory, so call `ɵɵgetInheritedFactory()` to compute it.
- const getInheritedFactoryCall = importExpr(Identifiers.getInheritedFactory).callFn([meta.type.value]);
- // Memoize the base factoryFn: `baseFactory || (baseFactory = ɵɵgetInheritedFactory(...))`
- const baseFactory = new BinaryOperatorExpr(BinaryOperator.Or, baseFactoryVar, baseFactoryVar.set(getInheritedFactoryCall));
- body.push(new ReturnStatement(baseFactory.callFn([typeForCtor])));
- }
- else {
- // This is straightforward factory, just return it.
- body.push(new ReturnStatement(retExpr));
- }
- let factoryFn = fn([new FnParam(t.name, DYNAMIC_TYPE)], body, INFERRED_TYPE, undefined, `${meta.name}_Factory`);
- if (baseFactoryVar !== null) {
- // There is a base factory variable so wrap its declaration along with the factory function into
- // an IIFE.
- factoryFn = arrowFn([], [new DeclareVarStmt(baseFactoryVar.name), new ReturnStatement(factoryFn)])
- .callFn([], /* sourceSpan */ undefined, /* pure */ true);
- }
- return {
- expression: factoryFn,
- statements: [],
- type: createFactoryType(meta),
- };
- }
- function createFactoryType(meta) {
- const ctorDepsType = meta.deps !== null && meta.deps !== 'invalid' ? createCtorDepsType(meta.deps) : NONE_TYPE;
- return expressionType(importExpr(Identifiers.FactoryDeclaration, [
- typeWithParameters(meta.type.type, meta.typeArgumentCount),
- ctorDepsType,
- ]));
- }
- function injectDependencies(deps, target) {
- return deps.map((dep, index) => compileInjectDependency(dep, target, index));
- }
- function compileInjectDependency(dep, target, index) {
- // Interpret the dependency according to its resolved type.
- if (dep.token === null) {
- return importExpr(Identifiers.invalidFactoryDep).callFn([literal$1(index)]);
- }
- else if (dep.attributeNameType === null) {
- // Build up the injection flags according to the metadata.
- const flags = 0 /* InjectFlags.Default */ |
- (dep.self ? 2 /* InjectFlags.Self */ : 0) |
- (dep.skipSelf ? 4 /* InjectFlags.SkipSelf */ : 0) |
- (dep.host ? 1 /* InjectFlags.Host */ : 0) |
- (dep.optional ? 8 /* InjectFlags.Optional */ : 0) |
- (target === exports.FactoryTarget.Pipe ? 16 /* InjectFlags.ForPipe */ : 0);
- // If this dependency is optional or otherwise has non-default flags, then additional
- // parameters describing how to inject the dependency must be passed to the inject function
- // that's being used.
- let flagsParam = flags !== 0 /* InjectFlags.Default */ || dep.optional ? literal$1(flags) : null;
- // Build up the arguments to the injectFn call.
- const injectArgs = [dep.token];
- if (flagsParam) {
- injectArgs.push(flagsParam);
- }
- const injectFn = getInjectFn(target);
- return importExpr(injectFn).callFn(injectArgs);
- }
- else {
- // The `dep.attributeTypeName` value is defined, which indicates that this is an `@Attribute()`
- // type dependency. For the generated JS we still want to use the `dep.token` value in case the
- // name given for the attribute is not a string literal. For example given `@Attribute(foo())`,
- // we want to generate `ɵɵinjectAttribute(foo())`.
- //
- // The `dep.attributeTypeName` is only actually used (in `createCtorDepType()`) to generate
- // typings.
- return importExpr(Identifiers.injectAttribute).callFn([dep.token]);
- }
- }
- function createCtorDepsType(deps) {
- let hasTypes = false;
- const attributeTypes = deps.map((dep) => {
- const type = createCtorDepType(dep);
- if (type !== null) {
- hasTypes = true;
- return type;
- }
- else {
- return literal$1(null);
- }
- });
- if (hasTypes) {
- return expressionType(literalArr(attributeTypes));
- }
- else {
- return NONE_TYPE;
- }
- }
- function createCtorDepType(dep) {
- const entries = [];
- if (dep.attributeNameType !== null) {
- entries.push({ key: 'attribute', value: dep.attributeNameType, quoted: false });
- }
- if (dep.optional) {
- entries.push({ key: 'optional', value: literal$1(true), quoted: false });
- }
- if (dep.host) {
- entries.push({ key: 'host', value: literal$1(true), quoted: false });
- }
- if (dep.self) {
- entries.push({ key: 'self', value: literal$1(true), quoted: false });
- }
- if (dep.skipSelf) {
- entries.push({ key: 'skipSelf', value: literal$1(true), quoted: false });
- }
- return entries.length > 0 ? literalMap(entries) : null;
- }
- function isDelegatedFactoryMetadata(meta) {
- return meta.delegateType !== undefined;
- }
- function isExpressionFactoryMetadata(meta) {
- return meta.expression !== undefined;
- }
- function getInjectFn(target) {
- switch (target) {
- case exports.FactoryTarget.Component:
- case exports.FactoryTarget.Directive:
- case exports.FactoryTarget.Pipe:
- return Identifiers.directiveInject;
- case exports.FactoryTarget.NgModule:
- case exports.FactoryTarget.Injectable:
- default:
- return Identifiers.inject;
- }
- }
- class ParserError {
- input;
- errLocation;
- ctxLocation;
- message;
- constructor(message, input, errLocation, ctxLocation) {
- this.input = input;
- this.errLocation = errLocation;
- this.ctxLocation = ctxLocation;
- this.message = `Parser Error: ${message} ${errLocation} [${input}] in ${ctxLocation}`;
- }
- }
- class ParseSpan {
- start;
- end;
- constructor(start, end) {
- this.start = start;
- this.end = end;
- }
- toAbsolute(absoluteOffset) {
- return new AbsoluteSourceSpan(absoluteOffset + this.start, absoluteOffset + this.end);
- }
- }
- class AST {
- span;
- sourceSpan;
- constructor(span,
- /**
- * Absolute location of the expression AST in a source code file.
- */
- sourceSpan) {
- this.span = span;
- this.sourceSpan = sourceSpan;
- }
- toString() {
- return 'AST';
- }
- }
- class ASTWithName extends AST {
- nameSpan;
- constructor(span, sourceSpan, nameSpan) {
- super(span, sourceSpan);
- this.nameSpan = nameSpan;
- }
- }
- let EmptyExpr$1 = class EmptyExpr extends AST {
- visit(visitor, context = null) {
- // do nothing
- }
- };
- class ImplicitReceiver extends AST {
- visit(visitor, context = null) {
- return visitor.visitImplicitReceiver(this, context);
- }
- }
- /**
- * Receiver when something is accessed through `this` (e.g. `this.foo`). Note that this class
- * inherits from `ImplicitReceiver`, because accessing something through `this` is treated the
- * same as accessing it implicitly inside of an Angular template (e.g. `[attr.title]="this.title"`
- * is the same as `[attr.title]="title"`.). Inheriting allows for the `this` accesses to be treated
- * the same as implicit ones, except for a couple of exceptions like `$event` and `$any`.
- * TODO: we should find a way for this class not to extend from `ImplicitReceiver` in the future.
- */
- class ThisReceiver extends ImplicitReceiver {
- visit(visitor, context = null) {
- return visitor.visitThisReceiver?.(this, context);
- }
- }
- /**
- * Multiple expressions separated by a semicolon.
- */
- class Chain extends AST {
- expressions;
- constructor(span, sourceSpan, expressions) {
- super(span, sourceSpan);
- this.expressions = expressions;
- }
- visit(visitor, context = null) {
- return visitor.visitChain(this, context);
- }
- }
- class Conditional extends AST {
- condition;
- trueExp;
- falseExp;
- constructor(span, sourceSpan, condition, trueExp, falseExp) {
- super(span, sourceSpan);
- this.condition = condition;
- this.trueExp = trueExp;
- this.falseExp = falseExp;
- }
- visit(visitor, context = null) {
- return visitor.visitConditional(this, context);
- }
- }
- class PropertyRead extends ASTWithName {
- receiver;
- name;
- constructor(span, sourceSpan, nameSpan, receiver, name) {
- super(span, sourceSpan, nameSpan);
- this.receiver = receiver;
- this.name = name;
- }
- visit(visitor, context = null) {
- return visitor.visitPropertyRead(this, context);
- }
- }
- class PropertyWrite extends ASTWithName {
- receiver;
- name;
- value;
- constructor(span, sourceSpan, nameSpan, receiver, name, value) {
- super(span, sourceSpan, nameSpan);
- this.receiver = receiver;
- this.name = name;
- this.value = value;
- }
- visit(visitor, context = null) {
- return visitor.visitPropertyWrite(this, context);
- }
- }
- class SafePropertyRead extends ASTWithName {
- receiver;
- name;
- constructor(span, sourceSpan, nameSpan, receiver, name) {
- super(span, sourceSpan, nameSpan);
- this.receiver = receiver;
- this.name = name;
- }
- visit(visitor, context = null) {
- return visitor.visitSafePropertyRead(this, context);
- }
- }
- class KeyedRead extends AST {
- receiver;
- key;
- constructor(span, sourceSpan, receiver, key) {
- super(span, sourceSpan);
- this.receiver = receiver;
- this.key = key;
- }
- visit(visitor, context = null) {
- return visitor.visitKeyedRead(this, context);
- }
- }
- class SafeKeyedRead extends AST {
- receiver;
- key;
- constructor(span, sourceSpan, receiver, key) {
- super(span, sourceSpan);
- this.receiver = receiver;
- this.key = key;
- }
- visit(visitor, context = null) {
- return visitor.visitSafeKeyedRead(this, context);
- }
- }
- class KeyedWrite extends AST {
- receiver;
- key;
- value;
- constructor(span, sourceSpan, receiver, key, value) {
- super(span, sourceSpan);
- this.receiver = receiver;
- this.key = key;
- this.value = value;
- }
- visit(visitor, context = null) {
- return visitor.visitKeyedWrite(this, context);
- }
- }
- class BindingPipe extends ASTWithName {
- exp;
- name;
- args;
- constructor(span, sourceSpan, exp, name, args, nameSpan) {
- super(span, sourceSpan, nameSpan);
- this.exp = exp;
- this.name = name;
- this.args = args;
- }
- visit(visitor, context = null) {
- return visitor.visitPipe(this, context);
- }
- }
- class LiteralPrimitive extends AST {
- value;
- constructor(span, sourceSpan, value) {
- super(span, sourceSpan);
- this.value = value;
- }
- visit(visitor, context = null) {
- return visitor.visitLiteralPrimitive(this, context);
- }
- }
- class LiteralArray extends AST {
- expressions;
- constructor(span, sourceSpan, expressions) {
- super(span, sourceSpan);
- this.expressions = expressions;
- }
- visit(visitor, context = null) {
- return visitor.visitLiteralArray(this, context);
- }
- }
- class LiteralMap extends AST {
- keys;
- values;
- constructor(span, sourceSpan, keys, values) {
- super(span, sourceSpan);
- this.keys = keys;
- this.values = values;
- }
- visit(visitor, context = null) {
- return visitor.visitLiteralMap(this, context);
- }
- }
- let Interpolation$1 = class Interpolation extends AST {
- strings;
- expressions;
- constructor(span, sourceSpan, strings, expressions) {
- super(span, sourceSpan);
- this.strings = strings;
- this.expressions = expressions;
- }
- visit(visitor, context = null) {
- return visitor.visitInterpolation(this, context);
- }
- };
- class Binary extends AST {
- operation;
- left;
- right;
- constructor(span, sourceSpan, operation, left, right) {
- super(span, sourceSpan);
- this.operation = operation;
- this.left = left;
- this.right = right;
- }
- visit(visitor, context = null) {
- return visitor.visitBinary(this, context);
- }
- }
- /**
- * For backwards compatibility reasons, `Unary` inherits from `Binary` and mimics the binary AST
- * node that was originally used. This inheritance relation can be deleted in some future major,
- * after consumers have been given a chance to fully support Unary.
- */
- class Unary extends Binary {
- operator;
- expr;
- // Redeclare the properties that are inherited from `Binary` as `never`, as consumers should not
- // depend on these fields when operating on `Unary`.
- left = null;
- right = null;
- operation = null;
- /**
- * Creates a unary minus expression "-x", represented as `Binary` using "0 - x".
- */
- static createMinus(span, sourceSpan, expr) {
- return new Unary(span, sourceSpan, '-', expr, '-', new LiteralPrimitive(span, sourceSpan, 0), expr);
- }
- /**
- * Creates a unary plus expression "+x", represented as `Binary` using "x - 0".
- */
- static createPlus(span, sourceSpan, expr) {
- return new Unary(span, sourceSpan, '+', expr, '-', expr, new LiteralPrimitive(span, sourceSpan, 0));
- }
- /**
- * During the deprecation period this constructor is private, to avoid consumers from creating
- * a `Unary` with the fallback properties for `Binary`.
- */
- constructor(span, sourceSpan, operator, expr, binaryOp, binaryLeft, binaryRight) {
- super(span, sourceSpan, binaryOp, binaryLeft, binaryRight);
- this.operator = operator;
- this.expr = expr;
- }
- visit(visitor, context = null) {
- if (visitor.visitUnary !== undefined) {
- return visitor.visitUnary(this, context);
- }
- return visitor.visitBinary(this, context);
- }
- }
- class PrefixNot extends AST {
- expression;
- constructor(span, sourceSpan, expression) {
- super(span, sourceSpan);
- this.expression = expression;
- }
- visit(visitor, context = null) {
- return visitor.visitPrefixNot(this, context);
- }
- }
- class TypeofExpression extends AST {
- expression;
- constructor(span, sourceSpan, expression) {
- super(span, sourceSpan);
- this.expression = expression;
- }
- visit(visitor, context = null) {
- return visitor.visitTypeofExpression(this, context);
- }
- }
- class NonNullAssert extends AST {
- expression;
- constructor(span, sourceSpan, expression) {
- super(span, sourceSpan);
- this.expression = expression;
- }
- visit(visitor, context = null) {
- return visitor.visitNonNullAssert(this, context);
- }
- }
- class Call extends AST {
- receiver;
- args;
- argumentSpan;
- constructor(span, sourceSpan, receiver, args, argumentSpan) {
- super(span, sourceSpan);
- this.receiver = receiver;
- this.args = args;
- this.argumentSpan = argumentSpan;
- }
- visit(visitor, context = null) {
- return visitor.visitCall(this, context);
- }
- }
- class SafeCall extends AST {
- receiver;
- args;
- argumentSpan;
- constructor(span, sourceSpan, receiver, args, argumentSpan) {
- super(span, sourceSpan);
- this.receiver = receiver;
- this.args = args;
- this.argumentSpan = argumentSpan;
- }
- visit(visitor, context = null) {
- return visitor.visitSafeCall(this, context);
- }
- }
- class TemplateLiteral extends AST {
- elements;
- expressions;
- constructor(span, sourceSpan, elements, expressions) {
- super(span, sourceSpan);
- this.elements = elements;
- this.expressions = expressions;
- }
- visit(visitor, context) {
- return visitor.visitTemplateLiteral(this, context);
- }
- }
- class TemplateLiteralElement extends AST {
- text;
- constructor(span, sourceSpan, text) {
- super(span, sourceSpan);
- this.text = text;
- }
- visit(visitor, context) {
- return visitor.visitTemplateLiteralElement(this, context);
- }
- }
- /**
- * Records the absolute position of a text span in a source file, where `start` and `end` are the
- * starting and ending byte offsets, respectively, of the text span in a source file.
- */
- class AbsoluteSourceSpan {
- start;
- end;
- constructor(start, end) {
- this.start = start;
- this.end = end;
- }
- }
- class ASTWithSource extends AST {
- ast;
- source;
- location;
- errors;
- constructor(ast, source, location, absoluteOffset, errors) {
- super(new ParseSpan(0, source === null ? 0 : source.length), new AbsoluteSourceSpan(absoluteOffset, source === null ? absoluteOffset : absoluteOffset + source.length));
- this.ast = ast;
- this.source = source;
- this.location = location;
- this.errors = errors;
- }
- visit(visitor, context = null) {
- if (visitor.visitASTWithSource) {
- return visitor.visitASTWithSource(this, context);
- }
- return this.ast.visit(visitor, context);
- }
- toString() {
- return `${this.source} in ${this.location}`;
- }
- }
- class VariableBinding {
- sourceSpan;
- key;
- value;
- /**
- * @param sourceSpan entire span of the binding.
- * @param key name of the LHS along with its span.
- * @param value optional value for the RHS along with its span.
- */
- constructor(sourceSpan, key, value) {
- this.sourceSpan = sourceSpan;
- this.key = key;
- this.value = value;
- }
- }
- class ExpressionBinding {
- sourceSpan;
- key;
- value;
- /**
- * @param sourceSpan entire span of the binding.
- * @param key binding name, like ngForOf, ngForTrackBy, ngIf, along with its
- * span. Note that the length of the span may not be the same as
- * `key.source.length`. For example,
- * 1. key.source = ngFor, key.span is for "ngFor"
- * 2. key.source = ngForOf, key.span is for "of"
- * 3. key.source = ngForTrackBy, key.span is for "trackBy"
- * @param value optional expression for the RHS.
- */
- constructor(sourceSpan, key, value) {
- this.sourceSpan = sourceSpan;
- this.key = key;
- this.value = value;
- }
- }
- class RecursiveAstVisitor {
- visit(ast, context) {
- // The default implementation just visits every node.
- // Classes that extend RecursiveAstVisitor should override this function
- // to selectively visit the specified node.
- ast.visit(this, context);
- }
- visitUnary(ast, context) {
- this.visit(ast.expr, context);
- }
- visitBinary(ast, context) {
- this.visit(ast.left, context);
- this.visit(ast.right, context);
- }
- visitChain(ast, context) {
- this.visitAll(ast.expressions, context);
- }
- visitConditional(ast, context) {
- this.visit(ast.condition, context);
- this.visit(ast.trueExp, context);
- this.visit(ast.falseExp, context);
- }
- visitPipe(ast, context) {
- this.visit(ast.exp, context);
- this.visitAll(ast.args, context);
- }
- visitImplicitReceiver(ast, context) { }
- visitThisReceiver(ast, context) { }
- visitInterpolation(ast, context) {
- this.visitAll(ast.expressions, context);
- }
- visitKeyedRead(ast, context) {
- this.visit(ast.receiver, context);
- this.visit(ast.key, context);
- }
- visitKeyedWrite(ast, context) {
- this.visit(ast.receiver, context);
- this.visit(ast.key, context);
- this.visit(ast.value, context);
- }
- visitLiteralArray(ast, context) {
- this.visitAll(ast.expressions, context);
- }
- visitLiteralMap(ast, context) {
- this.visitAll(ast.values, context);
- }
- visitLiteralPrimitive(ast, context) { }
- visitPrefixNot(ast, context) {
- this.visit(ast.expression, context);
- }
- visitTypeofExpression(ast, context) {
- this.visit(ast.expression, context);
- }
- visitNonNullAssert(ast, context) {
- this.visit(ast.expression, context);
- }
- visitPropertyRead(ast, context) {
- this.visit(ast.receiver, context);
- }
- visitPropertyWrite(ast, context) {
- this.visit(ast.receiver, context);
- this.visit(ast.value, context);
- }
- visitSafePropertyRead(ast, context) {
- this.visit(ast.receiver, context);
- }
- visitSafeKeyedRead(ast, context) {
- this.visit(ast.receiver, context);
- this.visit(ast.key, context);
- }
- visitCall(ast, context) {
- this.visit(ast.receiver, context);
- this.visitAll(ast.args, context);
- }
- visitSafeCall(ast, context) {
- this.visit(ast.receiver, context);
- this.visitAll(ast.args, context);
- }
- visitTemplateLiteral(ast, context) {
- // Iterate in the declaration order. Note that there will
- // always be one expression less than the number of elements.
- for (let i = 0; i < ast.elements.length; i++) {
- this.visit(ast.elements[i], context);
- const expression = i < ast.expressions.length ? ast.expressions[i] : null;
- if (expression !== null) {
- this.visit(expression, context);
- }
- }
- }
- visitTemplateLiteralElement(ast, context) { }
- // This is not part of the AstVisitor interface, just a helper method
- visitAll(asts, context) {
- for (const ast of asts) {
- this.visit(ast, context);
- }
- }
- }
- // Bindings
- class ParsedProperty {
- name;
- expression;
- type;
- sourceSpan;
- keySpan;
- valueSpan;
- isLiteral;
- isAnimation;
- constructor(name, expression, type, sourceSpan, keySpan, valueSpan) {
- this.name = name;
- this.expression = expression;
- this.type = type;
- this.sourceSpan = sourceSpan;
- this.keySpan = keySpan;
- this.valueSpan = valueSpan;
- this.isLiteral = this.type === ParsedPropertyType.LITERAL_ATTR;
- this.isAnimation = this.type === ParsedPropertyType.ANIMATION;
- }
- }
- var ParsedPropertyType;
- (function (ParsedPropertyType) {
- ParsedPropertyType[ParsedPropertyType["DEFAULT"] = 0] = "DEFAULT";
- ParsedPropertyType[ParsedPropertyType["LITERAL_ATTR"] = 1] = "LITERAL_ATTR";
- ParsedPropertyType[ParsedPropertyType["ANIMATION"] = 2] = "ANIMATION";
- ParsedPropertyType[ParsedPropertyType["TWO_WAY"] = 3] = "TWO_WAY";
- })(ParsedPropertyType || (ParsedPropertyType = {}));
- exports.ParsedEventType = void 0;
- (function (ParsedEventType) {
- // DOM or Directive event
- ParsedEventType[ParsedEventType["Regular"] = 0] = "Regular";
- // Animation specific event
- ParsedEventType[ParsedEventType["Animation"] = 1] = "Animation";
- // Event side of a two-way binding (e.g. `[(property)]="expression"`).
- ParsedEventType[ParsedEventType["TwoWay"] = 2] = "TwoWay";
- })(exports.ParsedEventType || (exports.ParsedEventType = {}));
- class ParsedEvent {
- name;
- targetOrPhase;
- type;
- handler;
- sourceSpan;
- handlerSpan;
- keySpan;
- constructor(name, targetOrPhase, type, handler, sourceSpan, handlerSpan, keySpan) {
- this.name = name;
- this.targetOrPhase = targetOrPhase;
- this.type = type;
- this.handler = handler;
- this.sourceSpan = sourceSpan;
- this.handlerSpan = handlerSpan;
- this.keySpan = keySpan;
- }
- }
- /**
- * ParsedVariable represents a variable declaration in a microsyntax expression.
- */
- class ParsedVariable {
- name;
- value;
- sourceSpan;
- keySpan;
- valueSpan;
- constructor(name, value, sourceSpan, keySpan, valueSpan) {
- this.name = name;
- this.value = value;
- this.sourceSpan = sourceSpan;
- this.keySpan = keySpan;
- this.valueSpan = valueSpan;
- }
- }
- exports.BindingType = void 0;
- (function (BindingType) {
- // A regular binding to a property (e.g. `[property]="expression"`).
- BindingType[BindingType["Property"] = 0] = "Property";
- // A binding to an element attribute (e.g. `[attr.name]="expression"`).
- BindingType[BindingType["Attribute"] = 1] = "Attribute";
- // A binding to a CSS class (e.g. `[class.name]="condition"`).
- BindingType[BindingType["Class"] = 2] = "Class";
- // A binding to a style rule (e.g. `[style.rule]="expression"`).
- BindingType[BindingType["Style"] = 3] = "Style";
- // A binding to an animation reference (e.g. `[animate.key]="expression"`).
- BindingType[BindingType["Animation"] = 4] = "Animation";
- // Property side of a two-way binding (e.g. `[(property)]="expression"`).
- BindingType[BindingType["TwoWay"] = 5] = "TwoWay";
- })(exports.BindingType || (exports.BindingType = {}));
- class BoundElementProperty {
- name;
- type;
- securityContext;
- value;
- unit;
- sourceSpan;
- keySpan;
- valueSpan;
- constructor(name, type, securityContext, value, unit, sourceSpan, keySpan, valueSpan) {
- this.name = name;
- this.type = type;
- this.securityContext = securityContext;
- this.value = value;
- this.unit = unit;
- this.sourceSpan = sourceSpan;
- this.keySpan = keySpan;
- this.valueSpan = valueSpan;
- }
- }
- exports.TagContentType = void 0;
- (function (TagContentType) {
- TagContentType[TagContentType["RAW_TEXT"] = 0] = "RAW_TEXT";
- TagContentType[TagContentType["ESCAPABLE_RAW_TEXT"] = 1] = "ESCAPABLE_RAW_TEXT";
- TagContentType[TagContentType["PARSABLE_DATA"] = 2] = "PARSABLE_DATA";
- })(exports.TagContentType || (exports.TagContentType = {}));
- function splitNsName(elementName, fatal = true) {
- if (elementName[0] != ':') {
- return [null, elementName];
- }
- const colonIndex = elementName.indexOf(':', 1);
- if (colonIndex === -1) {
- if (fatal) {
- throw new Error(`Unsupported format "${elementName}" expecting ":namespace:name"`);
- }
- else {
- return [null, elementName];
- }
- }
- return [elementName.slice(1, colonIndex), elementName.slice(colonIndex + 1)];
- }
- // `<ng-container>` tags work the same regardless the namespace
- function isNgContainer(tagName) {
- return splitNsName(tagName)[1] === 'ng-container';
- }
- // `<ng-content>` tags work the same regardless the namespace
- function isNgContent(tagName) {
- return splitNsName(tagName)[1] === 'ng-content';
- }
- // `<ng-template>` tags work the same regardless the namespace
- function isNgTemplate(tagName) {
- return splitNsName(tagName)[1] === 'ng-template';
- }
- function getNsPrefix(fullName) {
- return fullName === null ? null : splitNsName(fullName)[0];
- }
- function mergeNsAndName(prefix, localName) {
- return prefix ? `:${prefix}:${localName}` : localName;
- }
- /**
- * This is an R3 `Node`-like wrapper for a raw `html.Comment` node. We do not currently
- * require the implementation of a visitor for Comments as they are only collected at
- * the top-level of the R3 AST, and only if `Render3ParseOptions['collectCommentNodes']`
- * is true.
- */
- let Comment$1 = class Comment {
- value;
- sourceSpan;
- constructor(value, sourceSpan) {
- this.value = value;
- this.sourceSpan = sourceSpan;
- }
- visit(_visitor) {
- throw new Error('visit() not implemented for Comment');
- }
- };
- let Text$3 = class Text {
- value;
- sourceSpan;
- constructor(value, sourceSpan) {
- this.value = value;
- this.sourceSpan = sourceSpan;
- }
- visit(visitor) {
- return visitor.visitText(this);
- }
- };
- class BoundText {
- value;
- sourceSpan;
- i18n;
- constructor(value, sourceSpan, i18n) {
- this.value = value;
- this.sourceSpan = sourceSpan;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitBoundText(this);
- }
- }
- /**
- * Represents a text attribute in the template.
- *
- * `valueSpan` may not be present in cases where there is no value `<div a></div>`.
- * `keySpan` may also not be present for synthetic attributes from ICU expansions.
- */
- class TextAttribute {
- name;
- value;
- sourceSpan;
- keySpan;
- valueSpan;
- i18n;
- constructor(name, value, sourceSpan, keySpan, valueSpan, i18n) {
- this.name = name;
- this.value = value;
- this.sourceSpan = sourceSpan;
- this.keySpan = keySpan;
- this.valueSpan = valueSpan;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitTextAttribute(this);
- }
- }
- class BoundAttribute {
- name;
- type;
- securityContext;
- value;
- unit;
- sourceSpan;
- keySpan;
- valueSpan;
- i18n;
- constructor(name, type, securityContext, value, unit, sourceSpan, keySpan, valueSpan, i18n) {
- this.name = name;
- this.type = type;
- this.securityContext = securityContext;
- this.value = value;
- this.unit = unit;
- this.sourceSpan = sourceSpan;
- this.keySpan = keySpan;
- this.valueSpan = valueSpan;
- this.i18n = i18n;
- }
- static fromBoundElementProperty(prop, i18n) {
- if (prop.keySpan === undefined) {
- throw new Error(`Unexpected state: keySpan must be defined for bound attributes but was not for ${prop.name}: ${prop.sourceSpan}`);
- }
- return new BoundAttribute(prop.name, prop.type, prop.securityContext, prop.value, prop.unit, prop.sourceSpan, prop.keySpan, prop.valueSpan, i18n);
- }
- visit(visitor) {
- return visitor.visitBoundAttribute(this);
- }
- }
- class BoundEvent {
- name;
- type;
- handler;
- target;
- phase;
- sourceSpan;
- handlerSpan;
- keySpan;
- constructor(name, type, handler, target, phase, sourceSpan, handlerSpan, keySpan) {
- this.name = name;
- this.type = type;
- this.handler = handler;
- this.target = target;
- this.phase = phase;
- this.sourceSpan = sourceSpan;
- this.handlerSpan = handlerSpan;
- this.keySpan = keySpan;
- }
- static fromParsedEvent(event) {
- const target = event.type === exports.ParsedEventType.Regular ? event.targetOrPhase : null;
- const phase = event.type === exports.ParsedEventType.Animation ? event.targetOrPhase : null;
- if (event.keySpan === undefined) {
- throw new Error(`Unexpected state: keySpan must be defined for bound event but was not for ${event.name}: ${event.sourceSpan}`);
- }
- return new BoundEvent(event.name, event.type, event.handler, target, phase, event.sourceSpan, event.handlerSpan, event.keySpan);
- }
- visit(visitor) {
- return visitor.visitBoundEvent(this);
- }
- }
- let Element$1 = class Element {
- name;
- attributes;
- inputs;
- outputs;
- children;
- references;
- sourceSpan;
- startSourceSpan;
- endSourceSpan;
- i18n;
- constructor(name, attributes, inputs, outputs, children, references, sourceSpan, startSourceSpan, endSourceSpan, i18n) {
- this.name = name;
- this.attributes = attributes;
- this.inputs = inputs;
- this.outputs = outputs;
- this.children = children;
- this.references = references;
- this.sourceSpan = sourceSpan;
- this.startSourceSpan = startSourceSpan;
- this.endSourceSpan = endSourceSpan;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitElement(this);
- }
- };
- class DeferredTrigger {
- nameSpan;
- sourceSpan;
- prefetchSpan;
- whenOrOnSourceSpan;
- hydrateSpan;
- constructor(nameSpan, sourceSpan, prefetchSpan, whenOrOnSourceSpan, hydrateSpan) {
- this.nameSpan = nameSpan;
- this.sourceSpan = sourceSpan;
- this.prefetchSpan = prefetchSpan;
- this.whenOrOnSourceSpan = whenOrOnSourceSpan;
- this.hydrateSpan = hydrateSpan;
- }
- visit(visitor) {
- return visitor.visitDeferredTrigger(this);
- }
- }
- class BoundDeferredTrigger extends DeferredTrigger {
- value;
- constructor(value, sourceSpan, prefetchSpan, whenSourceSpan, hydrateSpan) {
- // BoundDeferredTrigger is for 'when' triggers. These aren't really "triggers" and don't have a
- // nameSpan. Trigger names are the built in event triggers like hover, interaction, etc.
- super(/** nameSpan */ null, sourceSpan, prefetchSpan, whenSourceSpan, hydrateSpan);
- this.value = value;
- }
- }
- class NeverDeferredTrigger extends DeferredTrigger {
- }
- class IdleDeferredTrigger extends DeferredTrigger {
- }
- class ImmediateDeferredTrigger extends DeferredTrigger {
- }
- class HoverDeferredTrigger extends DeferredTrigger {
- reference;
- constructor(reference, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan) {
- super(nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan);
- this.reference = reference;
- }
- }
- class TimerDeferredTrigger extends DeferredTrigger {
- delay;
- constructor(delay, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan) {
- super(nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan);
- this.delay = delay;
- }
- }
- class InteractionDeferredTrigger extends DeferredTrigger {
- reference;
- constructor(reference, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan) {
- super(nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan);
- this.reference = reference;
- }
- }
- class ViewportDeferredTrigger extends DeferredTrigger {
- reference;
- constructor(reference, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan) {
- super(nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan);
- this.reference = reference;
- }
- }
- class BlockNode {
- nameSpan;
- sourceSpan;
- startSourceSpan;
- endSourceSpan;
- constructor(nameSpan, sourceSpan, startSourceSpan, endSourceSpan) {
- this.nameSpan = nameSpan;
- this.sourceSpan = sourceSpan;
- this.startSourceSpan = startSourceSpan;
- this.endSourceSpan = endSourceSpan;
- }
- }
- class DeferredBlockPlaceholder extends BlockNode {
- children;
- minimumTime;
- i18n;
- constructor(children, minimumTime, nameSpan, sourceSpan, startSourceSpan, endSourceSpan, i18n) {
- super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
- this.children = children;
- this.minimumTime = minimumTime;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitDeferredBlockPlaceholder(this);
- }
- }
- class DeferredBlockLoading extends BlockNode {
- children;
- afterTime;
- minimumTime;
- i18n;
- constructor(children, afterTime, minimumTime, nameSpan, sourceSpan, startSourceSpan, endSourceSpan, i18n) {
- super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
- this.children = children;
- this.afterTime = afterTime;
- this.minimumTime = minimumTime;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitDeferredBlockLoading(this);
- }
- }
- class DeferredBlockError extends BlockNode {
- children;
- i18n;
- constructor(children, nameSpan, sourceSpan, startSourceSpan, endSourceSpan, i18n) {
- super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
- this.children = children;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitDeferredBlockError(this);
- }
- }
- class DeferredBlock extends BlockNode {
- children;
- placeholder;
- loading;
- error;
- mainBlockSpan;
- i18n;
- triggers;
- prefetchTriggers;
- hydrateTriggers;
- definedTriggers;
- definedPrefetchTriggers;
- definedHydrateTriggers;
- constructor(children, triggers, prefetchTriggers, hydrateTriggers, placeholder, loading, error, nameSpan, sourceSpan, mainBlockSpan, startSourceSpan, endSourceSpan, i18n) {
- super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
- this.children = children;
- this.placeholder = placeholder;
- this.loading = loading;
- this.error = error;
- this.mainBlockSpan = mainBlockSpan;
- this.i18n = i18n;
- this.triggers = triggers;
- this.prefetchTriggers = prefetchTriggers;
- this.hydrateTriggers = hydrateTriggers;
- // We cache the keys since we know that they won't change and we
- // don't want to enumarate them every time we're traversing the AST.
- this.definedTriggers = Object.keys(triggers);
- this.definedPrefetchTriggers = Object.keys(prefetchTriggers);
- this.definedHydrateTriggers = Object.keys(hydrateTriggers);
- }
- visit(visitor) {
- return visitor.visitDeferredBlock(this);
- }
- visitAll(visitor) {
- // Visit the hydrate triggers first to match their insertion order.
- this.visitTriggers(this.definedHydrateTriggers, this.hydrateTriggers, visitor);
- this.visitTriggers(this.definedTriggers, this.triggers, visitor);
- this.visitTriggers(this.definedPrefetchTriggers, this.prefetchTriggers, visitor);
- visitAll$1(visitor, this.children);
- const remainingBlocks = [this.placeholder, this.loading, this.error].filter((x) => x !== null);
- visitAll$1(visitor, remainingBlocks);
- }
- visitTriggers(keys, triggers, visitor) {
- visitAll$1(visitor, keys.map((k) => triggers[k]));
- }
- }
- class SwitchBlock extends BlockNode {
- expression;
- cases;
- unknownBlocks;
- constructor(expression, cases,
- /**
- * These blocks are only captured to allow for autocompletion in the language service. They
- * aren't meant to be processed in any other way.
- */
- unknownBlocks, sourceSpan, startSourceSpan, endSourceSpan, nameSpan) {
- super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
- this.expression = expression;
- this.cases = cases;
- this.unknownBlocks = unknownBlocks;
- }
- visit(visitor) {
- return visitor.visitSwitchBlock(this);
- }
- }
- class SwitchBlockCase extends BlockNode {
- expression;
- children;
- i18n;
- constructor(expression, children, sourceSpan, startSourceSpan, endSourceSpan, nameSpan, i18n) {
- super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
- this.expression = expression;
- this.children = children;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitSwitchBlockCase(this);
- }
- }
- class ForLoopBlock extends BlockNode {
- item;
- expression;
- trackBy;
- trackKeywordSpan;
- contextVariables;
- children;
- empty;
- mainBlockSpan;
- i18n;
- constructor(item, expression, trackBy, trackKeywordSpan, contextVariables, children, empty, sourceSpan, mainBlockSpan, startSourceSpan, endSourceSpan, nameSpan, i18n) {
- super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
- this.item = item;
- this.expression = expression;
- this.trackBy = trackBy;
- this.trackKeywordSpan = trackKeywordSpan;
- this.contextVariables = contextVariables;
- this.children = children;
- this.empty = empty;
- this.mainBlockSpan = mainBlockSpan;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitForLoopBlock(this);
- }
- }
- class ForLoopBlockEmpty extends BlockNode {
- children;
- i18n;
- constructor(children, sourceSpan, startSourceSpan, endSourceSpan, nameSpan, i18n) {
- super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
- this.children = children;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitForLoopBlockEmpty(this);
- }
- }
- class IfBlock extends BlockNode {
- branches;
- constructor(branches, sourceSpan, startSourceSpan, endSourceSpan, nameSpan) {
- super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
- this.branches = branches;
- }
- visit(visitor) {
- return visitor.visitIfBlock(this);
- }
- }
- class IfBlockBranch extends BlockNode {
- expression;
- children;
- expressionAlias;
- i18n;
- constructor(expression, children, expressionAlias, sourceSpan, startSourceSpan, endSourceSpan, nameSpan, i18n) {
- super(nameSpan, sourceSpan, startSourceSpan, endSourceSpan);
- this.expression = expression;
- this.children = children;
- this.expressionAlias = expressionAlias;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitIfBlockBranch(this);
- }
- }
- class UnknownBlock {
- name;
- sourceSpan;
- nameSpan;
- constructor(name, sourceSpan, nameSpan) {
- this.name = name;
- this.sourceSpan = sourceSpan;
- this.nameSpan = nameSpan;
- }
- visit(visitor) {
- return visitor.visitUnknownBlock(this);
- }
- }
- let LetDeclaration$1 = class LetDeclaration {
- name;
- value;
- sourceSpan;
- nameSpan;
- valueSpan;
- constructor(name, value, sourceSpan, nameSpan, valueSpan) {
- this.name = name;
- this.value = value;
- this.sourceSpan = sourceSpan;
- this.nameSpan = nameSpan;
- this.valueSpan = valueSpan;
- }
- visit(visitor) {
- return visitor.visitLetDeclaration(this);
- }
- };
- class Template {
- tagName;
- attributes;
- inputs;
- outputs;
- templateAttrs;
- children;
- references;
- variables;
- sourceSpan;
- startSourceSpan;
- endSourceSpan;
- i18n;
- constructor(
- // tagName is the name of the container element, if applicable.
- // `null` is a special case for when there is a structural directive on an `ng-template` so
- // the renderer can differentiate between the synthetic template and the one written in the
- // file.
- tagName, attributes, inputs, outputs, templateAttrs, children, references, variables, sourceSpan, startSourceSpan, endSourceSpan, i18n) {
- this.tagName = tagName;
- this.attributes = attributes;
- this.inputs = inputs;
- this.outputs = outputs;
- this.templateAttrs = templateAttrs;
- this.children = children;
- this.references = references;
- this.variables = variables;
- this.sourceSpan = sourceSpan;
- this.startSourceSpan = startSourceSpan;
- this.endSourceSpan = endSourceSpan;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitTemplate(this);
- }
- }
- class Content {
- selector;
- attributes;
- children;
- sourceSpan;
- i18n;
- name = 'ng-content';
- constructor(selector, attributes, children, sourceSpan, i18n) {
- this.selector = selector;
- this.attributes = attributes;
- this.children = children;
- this.sourceSpan = sourceSpan;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitContent(this);
- }
- }
- class Variable {
- name;
- value;
- sourceSpan;
- keySpan;
- valueSpan;
- constructor(name, value, sourceSpan, keySpan, valueSpan) {
- this.name = name;
- this.value = value;
- this.sourceSpan = sourceSpan;
- this.keySpan = keySpan;
- this.valueSpan = valueSpan;
- }
- visit(visitor) {
- return visitor.visitVariable(this);
- }
- }
- let Reference$1 = class Reference {
- name;
- value;
- sourceSpan;
- keySpan;
- valueSpan;
- constructor(name, value, sourceSpan, keySpan, valueSpan) {
- this.name = name;
- this.value = value;
- this.sourceSpan = sourceSpan;
- this.keySpan = keySpan;
- this.valueSpan = valueSpan;
- }
- visit(visitor) {
- return visitor.visitReference(this);
- }
- };
- let Icu$1 = class Icu {
- vars;
- placeholders;
- sourceSpan;
- i18n;
- constructor(vars, placeholders, sourceSpan, i18n) {
- this.vars = vars;
- this.placeholders = placeholders;
- this.sourceSpan = sourceSpan;
- this.i18n = i18n;
- }
- visit(visitor) {
- return visitor.visitIcu(this);
- }
- };
- let RecursiveVisitor$1 = class RecursiveVisitor {
- visitElement(element) {
- visitAll$1(this, element.attributes);
- visitAll$1(this, element.inputs);
- visitAll$1(this, element.outputs);
- visitAll$1(this, element.children);
- visitAll$1(this, element.references);
- }
- visitTemplate(template) {
- visitAll$1(this, template.attributes);
- visitAll$1(this, template.inputs);
- visitAll$1(this, template.outputs);
- visitAll$1(this, template.children);
- visitAll$1(this, template.references);
- visitAll$1(this, template.variables);
- }
- visitDeferredBlock(deferred) {
- deferred.visitAll(this);
- }
- visitDeferredBlockPlaceholder(block) {
- visitAll$1(this, block.children);
- }
- visitDeferredBlockError(block) {
- visitAll$1(this, block.children);
- }
- visitDeferredBlockLoading(block) {
- visitAll$1(this, block.children);
- }
- visitSwitchBlock(block) {
- visitAll$1(this, block.cases);
- }
- visitSwitchBlockCase(block) {
- visitAll$1(this, block.children);
- }
- visitForLoopBlock(block) {
- const blockItems = [block.item, ...block.contextVariables, ...block.children];
- block.empty && blockItems.push(block.empty);
- visitAll$1(this, blockItems);
- }
- visitForLoopBlockEmpty(block) {
- visitAll$1(this, block.children);
- }
- visitIfBlock(block) {
- visitAll$1(this, block.branches);
- }
- visitIfBlockBranch(block) {
- const blockItems = block.children;
- block.expressionAlias && blockItems.push(block.expressionAlias);
- visitAll$1(this, blockItems);
- }
- visitContent(content) {
- visitAll$1(this, content.children);
- }
- visitVariable(variable) { }
- visitReference(reference) { }
- visitTextAttribute(attribute) { }
- visitBoundAttribute(attribute) { }
- visitBoundEvent(attribute) { }
- visitText(text) { }
- visitBoundText(text) { }
- visitIcu(icu) { }
- visitDeferredTrigger(trigger) { }
- visitUnknownBlock(block) { }
- visitLetDeclaration(decl) { }
- };
- function visitAll$1(visitor, nodes) {
- const result = [];
- if (visitor.visit) {
- for (const node of nodes) {
- visitor.visit(node) || node.visit(visitor);
- }
- }
- else {
- for (const node of nodes) {
- const newNode = node.visit(visitor);
- if (newNode) {
- result.push(newNode);
- }
- }
- }
- return result;
- }
- class Message {
- nodes;
- placeholders;
- placeholderToMessage;
- meaning;
- description;
- customId;
- sources;
- id;
- /** The ids to use if there are no custom id and if `i18nLegacyMessageIdFormat` is not empty */
- legacyIds = [];
- messageString;
- /**
- * @param nodes message AST
- * @param placeholders maps placeholder names to static content and their source spans
- * @param placeholderToMessage maps placeholder names to messages (used for nested ICU messages)
- * @param meaning
- * @param description
- * @param customId
- */
- constructor(nodes, placeholders, placeholderToMessage, meaning, description, customId) {
- this.nodes = nodes;
- this.placeholders = placeholders;
- this.placeholderToMessage = placeholderToMessage;
- this.meaning = meaning;
- this.description = description;
- this.customId = customId;
- this.id = this.customId;
- this.messageString = serializeMessage(this.nodes);
- if (nodes.length) {
- this.sources = [
- {
- filePath: nodes[0].sourceSpan.start.file.url,
- startLine: nodes[0].sourceSpan.start.line + 1,
- startCol: nodes[0].sourceSpan.start.col + 1,
- endLine: nodes[nodes.length - 1].sourceSpan.end.line + 1,
- endCol: nodes[0].sourceSpan.start.col + 1,
- },
- ];
- }
- else {
- this.sources = [];
- }
- }
- }
- let Text$2 = class Text {
- value;
- sourceSpan;
- constructor(value, sourceSpan) {
- this.value = value;
- this.sourceSpan = sourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitText(this, context);
- }
- };
- // TODO(vicb): do we really need this node (vs an array) ?
- class Container {
- children;
- sourceSpan;
- constructor(children, sourceSpan) {
- this.children = children;
- this.sourceSpan = sourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitContainer(this, context);
- }
- }
- class Icu {
- expression;
- type;
- cases;
- sourceSpan;
- expressionPlaceholder;
- constructor(expression, type, cases, sourceSpan, expressionPlaceholder) {
- this.expression = expression;
- this.type = type;
- this.cases = cases;
- this.sourceSpan = sourceSpan;
- this.expressionPlaceholder = expressionPlaceholder;
- }
- visit(visitor, context) {
- return visitor.visitIcu(this, context);
- }
- }
- class TagPlaceholder {
- tag;
- attrs;
- startName;
- closeName;
- children;
- isVoid;
- sourceSpan;
- startSourceSpan;
- endSourceSpan;
- constructor(tag, attrs, startName, closeName, children, isVoid,
- // TODO sourceSpan should cover all (we need a startSourceSpan and endSourceSpan)
- sourceSpan, startSourceSpan, endSourceSpan) {
- this.tag = tag;
- this.attrs = attrs;
- this.startName = startName;
- this.closeName = closeName;
- this.children = children;
- this.isVoid = isVoid;
- this.sourceSpan = sourceSpan;
- this.startSourceSpan = startSourceSpan;
- this.endSourceSpan = endSourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitTagPlaceholder(this, context);
- }
- }
- class Placeholder {
- value;
- name;
- sourceSpan;
- constructor(value, name, sourceSpan) {
- this.value = value;
- this.name = name;
- this.sourceSpan = sourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitPlaceholder(this, context);
- }
- }
- class IcuPlaceholder {
- value;
- name;
- sourceSpan;
- /** Used to capture a message computed from a previous processing pass (see `setI18nRefs()`). */
- previousMessage;
- constructor(value, name, sourceSpan) {
- this.value = value;
- this.name = name;
- this.sourceSpan = sourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitIcuPlaceholder(this, context);
- }
- }
- class BlockPlaceholder {
- name;
- parameters;
- startName;
- closeName;
- children;
- sourceSpan;
- startSourceSpan;
- endSourceSpan;
- constructor(name, parameters, startName, closeName, children, sourceSpan, startSourceSpan, endSourceSpan) {
- this.name = name;
- this.parameters = parameters;
- this.startName = startName;
- this.closeName = closeName;
- this.children = children;
- this.sourceSpan = sourceSpan;
- this.startSourceSpan = startSourceSpan;
- this.endSourceSpan = endSourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitBlockPlaceholder(this, context);
- }
- }
- // Clone the AST
- class CloneVisitor {
- visitText(text, context) {
- return new Text$2(text.value, text.sourceSpan);
- }
- visitContainer(container, context) {
- const children = container.children.map((n) => n.visit(this, context));
- return new Container(children, container.sourceSpan);
- }
- visitIcu(icu, context) {
- const cases = {};
- Object.keys(icu.cases).forEach((key) => (cases[key] = icu.cases[key].visit(this, context)));
- const msg = new Icu(icu.expression, icu.type, cases, icu.sourceSpan, icu.expressionPlaceholder);
- return msg;
- }
- visitTagPlaceholder(ph, context) {
- const children = ph.children.map((n) => n.visit(this, context));
- return new TagPlaceholder(ph.tag, ph.attrs, ph.startName, ph.closeName, children, ph.isVoid, ph.sourceSpan, ph.startSourceSpan, ph.endSourceSpan);
- }
- visitPlaceholder(ph, context) {
- return new Placeholder(ph.value, ph.name, ph.sourceSpan);
- }
- visitIcuPlaceholder(ph, context) {
- return new IcuPlaceholder(ph.value, ph.name, ph.sourceSpan);
- }
- visitBlockPlaceholder(ph, context) {
- const children = ph.children.map((n) => n.visit(this, context));
- return new BlockPlaceholder(ph.name, ph.parameters, ph.startName, ph.closeName, children, ph.sourceSpan, ph.startSourceSpan, ph.endSourceSpan);
- }
- }
- // Visit all the nodes recursively
- class RecurseVisitor {
- visitText(text, context) { }
- visitContainer(container, context) {
- container.children.forEach((child) => child.visit(this));
- }
- visitIcu(icu, context) {
- Object.keys(icu.cases).forEach((k) => {
- icu.cases[k].visit(this);
- });
- }
- visitTagPlaceholder(ph, context) {
- ph.children.forEach((child) => child.visit(this));
- }
- visitPlaceholder(ph, context) { }
- visitIcuPlaceholder(ph, context) { }
- visitBlockPlaceholder(ph, context) {
- ph.children.forEach((child) => child.visit(this));
- }
- }
- /**
- * Serialize the message to the Localize backtick string format that would appear in compiled code.
- */
- function serializeMessage(messageNodes) {
- const visitor = new LocalizeMessageStringVisitor();
- const str = messageNodes.map((n) => n.visit(visitor)).join('');
- return str;
- }
- class LocalizeMessageStringVisitor {
- visitText(text) {
- return text.value;
- }
- visitContainer(container) {
- return container.children.map((child) => child.visit(this)).join('');
- }
- visitIcu(icu) {
- const strCases = Object.keys(icu.cases).map((k) => `${k} {${icu.cases[k].visit(this)}}`);
- return `{${icu.expressionPlaceholder}, ${icu.type}, ${strCases.join(' ')}}`;
- }
- visitTagPlaceholder(ph) {
- const children = ph.children.map((child) => child.visit(this)).join('');
- return `{$${ph.startName}}${children}{$${ph.closeName}}`;
- }
- visitPlaceholder(ph) {
- return `{$${ph.name}}`;
- }
- visitIcuPlaceholder(ph) {
- return `{$${ph.name}}`;
- }
- visitBlockPlaceholder(ph) {
- const children = ph.children.map((child) => child.visit(this)).join('');
- return `{$${ph.startName}}${children}{$${ph.closeName}}`;
- }
- }
- class Serializer {
- // Creates a name mapper, see `PlaceholderMapper`
- // Returning `null` means that no name mapping is used.
- createNameMapper(message) {
- return null;
- }
- }
- /**
- * A simple mapper that take a function to transform an internal name to a public name
- */
- class SimplePlaceholderMapper extends RecurseVisitor {
- mapName;
- internalToPublic = {};
- publicToNextId = {};
- publicToInternal = {};
- // create a mapping from the message
- constructor(message, mapName) {
- super();
- this.mapName = mapName;
- message.nodes.forEach((node) => node.visit(this));
- }
- toPublicName(internalName) {
- return this.internalToPublic.hasOwnProperty(internalName)
- ? this.internalToPublic[internalName]
- : null;
- }
- toInternalName(publicName) {
- return this.publicToInternal.hasOwnProperty(publicName)
- ? this.publicToInternal[publicName]
- : null;
- }
- visitText(text, context) {
- return null;
- }
- visitTagPlaceholder(ph, context) {
- this.visitPlaceholderName(ph.startName);
- super.visitTagPlaceholder(ph, context);
- this.visitPlaceholderName(ph.closeName);
- }
- visitPlaceholder(ph, context) {
- this.visitPlaceholderName(ph.name);
- }
- visitBlockPlaceholder(ph, context) {
- this.visitPlaceholderName(ph.startName);
- super.visitBlockPlaceholder(ph, context);
- this.visitPlaceholderName(ph.closeName);
- }
- visitIcuPlaceholder(ph, context) {
- this.visitPlaceholderName(ph.name);
- }
- // XMB placeholders could only contains A-Z, 0-9 and _
- visitPlaceholderName(internalName) {
- if (!internalName || this.internalToPublic.hasOwnProperty(internalName)) {
- return;
- }
- let publicName = this.mapName(internalName);
- if (this.publicToInternal.hasOwnProperty(publicName)) {
- // Create a new XMB when it has already been used
- const nextId = this.publicToNextId[publicName];
- this.publicToNextId[publicName] = nextId + 1;
- publicName = `${publicName}_${nextId}`;
- }
- else {
- this.publicToNextId[publicName] = 1;
- }
- this.internalToPublic[internalName] = publicName;
- this.publicToInternal[publicName] = internalName;
- }
- }
- let _Visitor$2 = class _Visitor {
- visitTag(tag) {
- const strAttrs = this._serializeAttributes(tag.attrs);
- if (tag.children.length == 0) {
- return `<${tag.name}${strAttrs}/>`;
- }
- const strChildren = tag.children.map((node) => node.visit(this));
- return `<${tag.name}${strAttrs}>${strChildren.join('')}</${tag.name}>`;
- }
- visitText(text) {
- return text.value;
- }
- visitDeclaration(decl) {
- return `<?xml${this._serializeAttributes(decl.attrs)} ?>`;
- }
- _serializeAttributes(attrs) {
- const strAttrs = Object.keys(attrs)
- .map((name) => `${name}="${attrs[name]}"`)
- .join(' ');
- return strAttrs.length > 0 ? ' ' + strAttrs : '';
- }
- visitDoctype(doctype) {
- return `<!DOCTYPE ${doctype.rootTag} [\n${doctype.dtd}\n]>`;
- }
- };
- const _visitor = new _Visitor$2();
- function serialize$1(nodes) {
- return nodes.map((node) => node.visit(_visitor)).join('');
- }
- class Declaration {
- attrs = {};
- constructor(unescapedAttrs) {
- Object.keys(unescapedAttrs).forEach((k) => {
- this.attrs[k] = escapeXml(unescapedAttrs[k]);
- });
- }
- visit(visitor) {
- return visitor.visitDeclaration(this);
- }
- }
- class Doctype {
- rootTag;
- dtd;
- constructor(rootTag, dtd) {
- this.rootTag = rootTag;
- this.dtd = dtd;
- }
- visit(visitor) {
- return visitor.visitDoctype(this);
- }
- }
- class Tag {
- name;
- children;
- attrs = {};
- constructor(name, unescapedAttrs = {}, children = []) {
- this.name = name;
- this.children = children;
- Object.keys(unescapedAttrs).forEach((k) => {
- this.attrs[k] = escapeXml(unescapedAttrs[k]);
- });
- }
- visit(visitor) {
- return visitor.visitTag(this);
- }
- }
- let Text$1 = class Text {
- value;
- constructor(unescapedValue) {
- this.value = escapeXml(unescapedValue);
- }
- visit(visitor) {
- return visitor.visitText(this);
- }
- };
- class CR extends Text$1 {
- constructor(ws = 0) {
- super(`\n${new Array(ws + 1).join(' ')}`);
- }
- }
- const _ESCAPED_CHARS = [
- [/&/g, '&'],
- [/"/g, '"'],
- [/'/g, '''],
- [/</g, '<'],
- [/>/g, '>'],
- ];
- // Escape `_ESCAPED_CHARS` characters in the given text with encoded entities
- function escapeXml(text) {
- return _ESCAPED_CHARS.reduce((text, entry) => text.replace(entry[0], entry[1]), text);
- }
- /**
- * Defines the `handler` value on the serialized XMB, indicating that Angular
- * generated the bundle. This is useful for analytics in Translation Console.
- *
- * NOTE: Keep in sync with
- * packages/localize/tools/src/extract/translation_files/xmb_translation_serializer.ts.
- */
- const _XMB_HANDLER = 'angular';
- const _MESSAGES_TAG = 'messagebundle';
- const _MESSAGE_TAG = 'msg';
- const _PLACEHOLDER_TAG = 'ph';
- const _EXAMPLE_TAG = 'ex';
- const _SOURCE_TAG = 'source';
- const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
- <!ATTLIST messagebundle class CDATA #IMPLIED>
- <!ELEMENT msg (#PCDATA|ph|source)*>
- <!ATTLIST msg id CDATA #IMPLIED>
- <!ATTLIST msg seq CDATA #IMPLIED>
- <!ATTLIST msg name CDATA #IMPLIED>
- <!ATTLIST msg desc CDATA #IMPLIED>
- <!ATTLIST msg meaning CDATA #IMPLIED>
- <!ATTLIST msg obsolete (obsolete) #IMPLIED>
- <!ATTLIST msg xml:space (default|preserve) "default">
- <!ATTLIST msg is_hidden CDATA #IMPLIED>
- <!ELEMENT source (#PCDATA)>
- <!ELEMENT ph (#PCDATA|ex)*>
- <!ATTLIST ph name CDATA #REQUIRED>
- <!ELEMENT ex (#PCDATA)>`;
- class Xmb extends Serializer {
- write(messages, locale) {
- const exampleVisitor = new ExampleVisitor();
- const visitor = new _Visitor$1();
- const rootNode = new Tag(_MESSAGES_TAG);
- rootNode.attrs['handler'] = _XMB_HANDLER;
- messages.forEach((message) => {
- const attrs = { id: message.id };
- if (message.description) {
- attrs['desc'] = message.description;
- }
- if (message.meaning) {
- attrs['meaning'] = message.meaning;
- }
- let sourceTags = [];
- message.sources.forEach((source) => {
- sourceTags.push(new Tag(_SOURCE_TAG, {}, [
- new Text$1(`${source.filePath}:${source.startLine}${source.endLine !== source.startLine ? ',' + source.endLine : ''}`),
- ]));
- });
- rootNode.children.push(new CR(2), new Tag(_MESSAGE_TAG, attrs, [...sourceTags, ...visitor.serialize(message.nodes)]));
- });
- rootNode.children.push(new CR());
- return serialize$1([
- new Declaration({ version: '1.0', encoding: 'UTF-8' }),
- new CR(),
- new Doctype(_MESSAGES_TAG, _DOCTYPE),
- new CR(),
- exampleVisitor.addDefaultExamples(rootNode),
- new CR(),
- ]);
- }
- load(content, url) {
- throw new Error('Unsupported');
- }
- digest(message) {
- return digest(message);
- }
- createNameMapper(message) {
- return new SimplePlaceholderMapper(message, toPublicName);
- }
- }
- let _Visitor$1 = class _Visitor {
- visitText(text, context) {
- return [new Text$1(text.value)];
- }
- visitContainer(container, context) {
- const nodes = [];
- container.children.forEach((node) => nodes.push(...node.visit(this)));
- return nodes;
- }
- visitIcu(icu, context) {
- const nodes = [new Text$1(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
- Object.keys(icu.cases).forEach((c) => {
- nodes.push(new Text$1(`${c} {`), ...icu.cases[c].visit(this), new Text$1(`} `));
- });
- nodes.push(new Text$1(`}`));
- return nodes;
- }
- visitTagPlaceholder(ph, context) {
- const startTagAsText = new Text$1(`<${ph.tag}>`);
- const startEx = new Tag(_EXAMPLE_TAG, {}, [startTagAsText]);
- // TC requires PH to have a non empty EX, and uses the text node to show the "original" value.
- const startTagPh = new Tag(_PLACEHOLDER_TAG, { name: ph.startName }, [
- startEx,
- startTagAsText,
- ]);
- if (ph.isVoid) {
- // void tags have no children nor closing tags
- return [startTagPh];
- }
- const closeTagAsText = new Text$1(`</${ph.tag}>`);
- const closeEx = new Tag(_EXAMPLE_TAG, {}, [closeTagAsText]);
- // TC requires PH to have a non empty EX, and uses the text node to show the "original" value.
- const closeTagPh = new Tag(_PLACEHOLDER_TAG, { name: ph.closeName }, [
- closeEx,
- closeTagAsText,
- ]);
- return [startTagPh, ...this.serialize(ph.children), closeTagPh];
- }
- visitPlaceholder(ph, context) {
- const interpolationAsText = new Text$1(`{{${ph.value}}}`);
- // Example tag needs to be not-empty for TC.
- const exTag = new Tag(_EXAMPLE_TAG, {}, [interpolationAsText]);
- return [
- // TC requires PH to have a non empty EX, and uses the text node to show the "original" value.
- new Tag(_PLACEHOLDER_TAG, { name: ph.name }, [exTag, interpolationAsText]),
- ];
- }
- visitBlockPlaceholder(ph, context) {
- const startAsText = new Text$1(`@${ph.name}`);
- const startEx = new Tag(_EXAMPLE_TAG, {}, [startAsText]);
- // TC requires PH to have a non empty EX, and uses the text node to show the "original" value.
- const startTagPh = new Tag(_PLACEHOLDER_TAG, { name: ph.startName }, [startEx, startAsText]);
- const closeAsText = new Text$1(`}`);
- const closeEx = new Tag(_EXAMPLE_TAG, {}, [closeAsText]);
- // TC requires PH to have a non empty EX, and uses the text node to show the "original" value.
- const closeTagPh = new Tag(_PLACEHOLDER_TAG, { name: ph.closeName }, [closeEx, closeAsText]);
- return [startTagPh, ...this.serialize(ph.children), closeTagPh];
- }
- visitIcuPlaceholder(ph, context) {
- const icuExpression = ph.value.expression;
- const icuType = ph.value.type;
- const icuCases = Object.keys(ph.value.cases)
- .map((value) => value + ' {...}')
- .join(' ');
- const icuAsText = new Text$1(`{${icuExpression}, ${icuType}, ${icuCases}}`);
- const exTag = new Tag(_EXAMPLE_TAG, {}, [icuAsText]);
- return [
- // TC requires PH to have a non empty EX, and uses the text node to show the "original" value.
- new Tag(_PLACEHOLDER_TAG, { name: ph.name }, [exTag, icuAsText]),
- ];
- }
- serialize(nodes) {
- return [].concat(...nodes.map((node) => node.visit(this)));
- }
- };
- function digest(message) {
- return decimalDigest(message);
- }
- // TC requires at least one non-empty example on placeholders
- class ExampleVisitor {
- addDefaultExamples(node) {
- node.visit(this);
- return node;
- }
- visitTag(tag) {
- if (tag.name === _PLACEHOLDER_TAG) {
- if (!tag.children || tag.children.length == 0) {
- const exText = new Text$1(tag.attrs['name'] || '...');
- tag.children = [new Tag(_EXAMPLE_TAG, {}, [exText])];
- }
- }
- else if (tag.children) {
- tag.children.forEach((node) => node.visit(this));
- }
- }
- visitText(text) { }
- visitDeclaration(decl) { }
- visitDoctype(doctype) { }
- }
- // XMB/XTB placeholders can only contain A-Z, 0-9 and _
- function toPublicName(internalName) {
- return internalName.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
- }
- /** Name of the i18n attributes **/
- const I18N_ATTR = 'i18n';
- const I18N_ATTR_PREFIX = 'i18n-';
- /** Prefix of var expressions used in ICUs */
- const I18N_ICU_VAR_PREFIX = 'VAR_';
- function isI18nAttribute(name) {
- return name === I18N_ATTR || name.startsWith(I18N_ATTR_PREFIX);
- }
- function hasI18nAttrs(element) {
- return element.attrs.some((attr) => isI18nAttribute(attr.name));
- }
- function icuFromI18nMessage(message) {
- return message.nodes[0];
- }
- /**
- * Format the placeholder names in a map of placeholders to expressions.
- *
- * The placeholder names are converted from "internal" format (e.g. `START_TAG_DIV_1`) to "external"
- * format (e.g. `startTagDiv_1`).
- *
- * @param params A map of placeholder names to expressions.
- * @param useCamelCase whether to camelCase the placeholder name when formatting.
- * @returns A new map of formatted placeholder names to expressions.
- */
- function formatI18nPlaceholderNamesInMap(params = {}, useCamelCase) {
- const _params = {};
- if (params && Object.keys(params).length) {
- Object.keys(params).forEach((key) => (_params[formatI18nPlaceholderName(key, useCamelCase)] = params[key]));
- }
- return _params;
- }
- /**
- * Converts internal placeholder names to public-facing format
- * (for example to use in goog.getMsg call).
- * Example: `START_TAG_DIV_1` is converted to `startTagDiv_1`.
- *
- * @param name The placeholder name that should be formatted
- * @returns Formatted placeholder name
- */
- function formatI18nPlaceholderName(name, useCamelCase = true) {
- const publicName = toPublicName(name);
- if (!useCamelCase) {
- return publicName;
- }
- const chunks = publicName.split('_');
- if (chunks.length === 1) {
- // if no "_" found - just lowercase the value
- return name.toLowerCase();
- }
- let postfix;
- // eject last element if it's a number
- if (/^\d+$/.test(chunks[chunks.length - 1])) {
- postfix = chunks.pop();
- }
- let raw = chunks.shift().toLowerCase();
- if (chunks.length) {
- raw += chunks.map((c) => c.charAt(0).toUpperCase() + c.slice(1).toLowerCase()).join('');
- }
- return postfix ? `${raw}_${postfix}` : raw;
- }
- /**
- * Checks whether an object key contains potentially unsafe chars, thus the key should be wrapped in
- * quotes. Note: we do not wrap all keys into quotes, as it may have impact on minification and may
- * not work in some cases when object keys are mangled by a minifier.
- *
- * TODO(FW-1136): this is a temporary solution, we need to come up with a better way of working with
- * inputs that contain potentially unsafe chars.
- */
- const UNSAFE_OBJECT_KEY_NAME_REGEXP = /[-.]/;
- /** Name of the temporary to use during data binding */
- const TEMPORARY_NAME = '_t';
- /** Name of the context parameter passed into a template function */
- const CONTEXT_NAME = 'ctx';
- /** Name of the RenderFlag passed into a template function */
- const RENDER_FLAGS = 'rf';
- /**
- * Creates an allocator for a temporary variable.
- *
- * A variable declaration is added to the statements the first time the allocator is invoked.
- */
- function temporaryAllocator(pushStatement, name) {
- let temp = null;
- return () => {
- if (!temp) {
- pushStatement(new DeclareVarStmt(TEMPORARY_NAME, undefined, DYNAMIC_TYPE));
- temp = variable(name);
- }
- return temp;
- };
- }
- function asLiteral(value) {
- if (Array.isArray(value)) {
- return literalArr(value.map(asLiteral));
- }
- return literal$1(value, INFERRED_TYPE);
- }
- /**
- * Serializes inputs and outputs for `defineDirective` and `defineComponent`.
- *
- * This will attempt to generate optimized data structures to minimize memory or
- * file size of fully compiled applications.
- */
- function conditionallyCreateDirectiveBindingLiteral(map, forInputs) {
- const keys = Object.getOwnPropertyNames(map);
- if (keys.length === 0) {
- return null;
- }
- return literalMap(keys.map((key) => {
- const value = map[key];
- let declaredName;
- let publicName;
- let minifiedName;
- let expressionValue;
- if (typeof value === 'string') {
- // canonical syntax: `dirProp: publicProp`
- declaredName = key;
- minifiedName = key;
- publicName = value;
- expressionValue = asLiteral(publicName);
- }
- else {
- minifiedName = key;
- declaredName = value.classPropertyName;
- publicName = value.bindingPropertyName;
- const differentDeclaringName = publicName !== declaredName;
- const hasDecoratorInputTransform = value.transformFunction !== null;
- let flags = InputFlags.None;
- // Build up input flags
- if (value.isSignal) {
- flags |= InputFlags.SignalBased;
- }
- if (hasDecoratorInputTransform) {
- flags |= InputFlags.HasDecoratorInputTransform;
- }
- // Inputs, compared to outputs, will track their declared name (for `ngOnChanges`), support
- // decorator input transform functions, or store flag information if there is any.
- if (forInputs &&
- (differentDeclaringName || hasDecoratorInputTransform || flags !== InputFlags.None)) {
- const result = [literal$1(flags), asLiteral(publicName)];
- if (differentDeclaringName || hasDecoratorInputTransform) {
- result.push(asLiteral(declaredName));
- if (hasDecoratorInputTransform) {
- result.push(value.transformFunction);
- }
- }
- expressionValue = literalArr(result);
- }
- else {
- expressionValue = asLiteral(publicName);
- }
- }
- return {
- key: minifiedName,
- // put quotes around keys that contain potentially unsafe characters
- quoted: UNSAFE_OBJECT_KEY_NAME_REGEXP.test(minifiedName),
- value: expressionValue,
- };
- }));
- }
- /**
- * A representation for an object literal used during codegen of definition objects. The generic
- * type `T` allows to reference a documented type of the generated structure, such that the
- * property names that are set can be resolved to their documented declaration.
- */
- class DefinitionMap {
- values = [];
- set(key, value) {
- if (value) {
- const existing = this.values.find((value) => value.key === key);
- if (existing) {
- existing.value = value;
- }
- else {
- this.values.push({ key: key, value, quoted: false });
- }
- }
- }
- toLiteralMap() {
- return literalMap(this.values);
- }
- }
- /**
- * Creates a `CssSelector` from an AST node.
- */
- function createCssSelectorFromNode(node) {
- const elementName = node instanceof Element$1 ? node.name : 'ng-template';
- const attributes = getAttrsForDirectiveMatching(node);
- const cssSelector = new CssSelector();
- const elementNameNoNs = splitNsName(elementName)[1];
- cssSelector.setElement(elementNameNoNs);
- Object.getOwnPropertyNames(attributes).forEach((name) => {
- const nameNoNs = splitNsName(name)[1];
- const value = attributes[name];
- cssSelector.addAttribute(nameNoNs, value);
- if (name.toLowerCase() === 'class') {
- const classes = value.trim().split(/\s+/);
- classes.forEach((className) => cssSelector.addClassName(className));
- }
- });
- return cssSelector;
- }
- /**
- * Extract a map of properties to values for a given element or template node, which can be used
- * by the directive matching machinery.
- *
- * @param elOrTpl the element or template in question
- * @return an object set up for directive matching. For attributes on the element/template, this
- * object maps a property name to its (static) value. For any bindings, this map simply maps the
- * property name to an empty string.
- */
- function getAttrsForDirectiveMatching(elOrTpl) {
- const attributesMap = {};
- if (elOrTpl instanceof Template && elOrTpl.tagName !== 'ng-template') {
- elOrTpl.templateAttrs.forEach((a) => (attributesMap[a.name] = ''));
- }
- else {
- elOrTpl.attributes.forEach((a) => {
- if (!isI18nAttribute(a.name)) {
- attributesMap[a.name] = a.value;
- }
- });
- elOrTpl.inputs.forEach((i) => {
- if (i.type === exports.BindingType.Property || i.type === exports.BindingType.TwoWay) {
- attributesMap[i.name] = '';
- }
- });
- elOrTpl.outputs.forEach((o) => {
- attributesMap[o.name] = '';
- });
- }
- return attributesMap;
- }
- function compileInjectable(meta, resolveForwardRefs) {
- let result = null;
- const factoryMeta = {
- name: meta.name,
- type: meta.type,
- typeArgumentCount: meta.typeArgumentCount,
- deps: [],
- target: exports.FactoryTarget.Injectable,
- };
- if (meta.useClass !== undefined) {
- // meta.useClass has two modes of operation. Either deps are specified, in which case `new` is
- // used to instantiate the class with dependencies injected, or deps are not specified and
- // the factory of the class is used to instantiate it.
- //
- // A special case exists for useClass: Type where Type is the injectable type itself and no
- // deps are specified, in which case 'useClass' is effectively ignored.
- const useClassOnSelf = meta.useClass.expression.isEquivalent(meta.type.value);
- let deps = undefined;
- if (meta.deps !== undefined) {
- deps = meta.deps;
- }
- if (deps !== undefined) {
- // factory: () => new meta.useClass(...deps)
- result = compileFactoryFunction({
- ...factoryMeta,
- delegate: meta.useClass.expression,
- delegateDeps: deps,
- delegateType: R3FactoryDelegateType.Class,
- });
- }
- else if (useClassOnSelf) {
- result = compileFactoryFunction(factoryMeta);
- }
- else {
- result = {
- statements: [],
- expression: delegateToFactory(meta.type.value, meta.useClass.expression, resolveForwardRefs),
- };
- }
- }
- else if (meta.useFactory !== undefined) {
- if (meta.deps !== undefined) {
- result = compileFactoryFunction({
- ...factoryMeta,
- delegate: meta.useFactory,
- delegateDeps: meta.deps || [],
- delegateType: R3FactoryDelegateType.Function,
- });
- }
- else {
- result = { statements: [], expression: arrowFn([], meta.useFactory.callFn([])) };
- }
- }
- else if (meta.useValue !== undefined) {
- // Note: it's safe to use `meta.useValue` instead of the `USE_VALUE in meta` check used for
- // client code because meta.useValue is an Expression which will be defined even if the actual
- // value is undefined.
- result = compileFactoryFunction({
- ...factoryMeta,
- expression: meta.useValue.expression,
- });
- }
- else if (meta.useExisting !== undefined) {
- // useExisting is an `inject` call on the existing token.
- result = compileFactoryFunction({
- ...factoryMeta,
- expression: importExpr(Identifiers.inject).callFn([meta.useExisting.expression]),
- });
- }
- else {
- result = {
- statements: [],
- expression: delegateToFactory(meta.type.value, meta.type.value, resolveForwardRefs),
- };
- }
- const token = meta.type.value;
- const injectableProps = new DefinitionMap();
- injectableProps.set('token', token);
- injectableProps.set('factory', result.expression);
- // Only generate providedIn property if it has a non-null value
- if (meta.providedIn.expression.value !== null) {
- injectableProps.set('providedIn', convertFromMaybeForwardRefExpression(meta.providedIn));
- }
- const expression = importExpr(Identifiers.ɵɵdefineInjectable)
- .callFn([injectableProps.toLiteralMap()], undefined, true);
- return {
- expression,
- type: createInjectableType(meta),
- statements: result.statements,
- };
- }
- function createInjectableType(meta) {
- return new ExpressionType(importExpr(Identifiers.InjectableDeclaration, [
- typeWithParameters(meta.type.type, meta.typeArgumentCount),
- ]));
- }
- function delegateToFactory(type, useType, unwrapForwardRefs) {
- if (type.node === useType.node) {
- // The types are the same, so we can simply delegate directly to the type's factory.
- // ```
- // factory: type.ɵfac
- // ```
- return useType.prop('ɵfac');
- }
- if (!unwrapForwardRefs) {
- // The type is not wrapped in a `forwardRef()`, so we create a simple factory function that
- // accepts a sub-type as an argument.
- // ```
- // factory: function(t) { return useType.ɵfac(t); }
- // ```
- return createFactoryFunction(useType);
- }
- // The useType is actually wrapped in a `forwardRef()` so we need to resolve that before
- // calling its factory.
- // ```
- // factory: function(t) { return core.resolveForwardRef(type).ɵfac(t); }
- // ```
- const unwrappedType = importExpr(Identifiers.resolveForwardRef).callFn([useType]);
- return createFactoryFunction(unwrappedType);
- }
- function createFactoryFunction(type) {
- const t = new FnParam('__ngFactoryType__', DYNAMIC_TYPE);
- return arrowFn([t], type.prop('ɵfac').callFn([variable(t.name)]));
- }
- const UNUSABLE_INTERPOLATION_REGEXPS = [
- /@/, // control flow reserved symbol
- /^\s*$/, // empty
- /[<>]/, // html tag
- /^[{}]$/, // i18n expansion
- /&(#|[a-z])/i, // character reference,
- /^\/\//, // comment
- ];
- function assertInterpolationSymbols(identifier, value) {
- if (value != null && !(Array.isArray(value) && value.length == 2)) {
- throw new Error(`Expected '${identifier}' to be an array, [start, end].`);
- }
- else if (value != null) {
- const start = value[0];
- const end = value[1];
- // Check for unusable interpolation symbols
- UNUSABLE_INTERPOLATION_REGEXPS.forEach((regexp) => {
- if (regexp.test(start) || regexp.test(end)) {
- throw new Error(`['${start}', '${end}'] contains unusable interpolation symbol.`);
- }
- });
- }
- }
- class InterpolationConfig {
- start;
- end;
- static fromArray(markers) {
- if (!markers) {
- return DEFAULT_INTERPOLATION_CONFIG;
- }
- assertInterpolationSymbols('interpolation', markers);
- return new InterpolationConfig(markers[0], markers[1]);
- }
- constructor(start, end) {
- this.start = start;
- this.end = end;
- }
- }
- const DEFAULT_INTERPOLATION_CONFIG = new InterpolationConfig('{{', '}}');
- const DEFAULT_CONTAINER_BLOCKS = new Set(['switch']);
- const $EOF = 0;
- const $BSPACE = 8;
- const $TAB = 9;
- const $LF = 10;
- const $VTAB = 11;
- const $FF = 12;
- const $CR = 13;
- const $SPACE = 32;
- const $BANG = 33;
- const $DQ = 34;
- const $HASH = 35;
- const $$ = 36;
- const $PERCENT = 37;
- const $AMPERSAND = 38;
- const $SQ = 39;
- const $LPAREN = 40;
- const $RPAREN = 41;
- const $STAR = 42;
- const $PLUS = 43;
- const $COMMA = 44;
- const $MINUS = 45;
- const $PERIOD = 46;
- const $SLASH = 47;
- const $COLON = 58;
- const $SEMICOLON = 59;
- const $LT = 60;
- const $EQ = 61;
- const $GT = 62;
- const $QUESTION = 63;
- const $0 = 48;
- const $7 = 55;
- const $9 = 57;
- const $A = 65;
- const $E = 69;
- const $F = 70;
- const $X = 88;
- const $Z = 90;
- const $LBRACKET = 91;
- const $BACKSLASH = 92;
- const $RBRACKET = 93;
- const $CARET = 94;
- const $_ = 95;
- const $a = 97;
- const $b = 98;
- const $e = 101;
- const $f = 102;
- const $n = 110;
- const $r = 114;
- const $t = 116;
- const $u = 117;
- const $v = 118;
- const $x = 120;
- const $z = 122;
- const $LBRACE = 123;
- const $BAR = 124;
- const $RBRACE = 125;
- const $NBSP = 160;
- const $AT = 64;
- const $BT = 96;
- function isWhitespace(code) {
- return (code >= $TAB && code <= $SPACE) || code == $NBSP;
- }
- function isDigit(code) {
- return $0 <= code && code <= $9;
- }
- function isAsciiLetter(code) {
- return (code >= $a && code <= $z) || (code >= $A && code <= $Z);
- }
- function isAsciiHexDigit(code) {
- return (code >= $a && code <= $f) || (code >= $A && code <= $F) || isDigit(code);
- }
- function isNewLine(code) {
- return code === $LF || code === $CR;
- }
- function isOctalDigit(code) {
- return $0 <= code && code <= $7;
- }
- function isQuote(code) {
- return code === $SQ || code === $DQ || code === $BT;
- }
- class ParseLocation {
- file;
- offset;
- line;
- col;
- constructor(file, offset, line, col) {
- this.file = file;
- this.offset = offset;
- this.line = line;
- this.col = col;
- }
- toString() {
- return this.offset != null ? `${this.file.url}@${this.line}:${this.col}` : this.file.url;
- }
- moveBy(delta) {
- const source = this.file.content;
- const len = source.length;
- let offset = this.offset;
- let line = this.line;
- let col = this.col;
- while (offset > 0 && delta < 0) {
- offset--;
- delta++;
- const ch = source.charCodeAt(offset);
- if (ch == $LF) {
- line--;
- const priorLine = source
- .substring(0, offset - 1)
- .lastIndexOf(String.fromCharCode($LF));
- col = priorLine > 0 ? offset - priorLine : offset;
- }
- else {
- col--;
- }
- }
- while (offset < len && delta > 0) {
- const ch = source.charCodeAt(offset);
- offset++;
- delta--;
- if (ch == $LF) {
- line++;
- col = 0;
- }
- else {
- col++;
- }
- }
- return new ParseLocation(this.file, offset, line, col);
- }
- // Return the source around the location
- // Up to `maxChars` or `maxLines` on each side of the location
- getContext(maxChars, maxLines) {
- const content = this.file.content;
- let startOffset = this.offset;
- if (startOffset != null) {
- if (startOffset > content.length - 1) {
- startOffset = content.length - 1;
- }
- let endOffset = startOffset;
- let ctxChars = 0;
- let ctxLines = 0;
- while (ctxChars < maxChars && startOffset > 0) {
- startOffset--;
- ctxChars++;
- if (content[startOffset] == '\n') {
- if (++ctxLines == maxLines) {
- break;
- }
- }
- }
- ctxChars = 0;
- ctxLines = 0;
- while (ctxChars < maxChars && endOffset < content.length - 1) {
- endOffset++;
- ctxChars++;
- if (content[endOffset] == '\n') {
- if (++ctxLines == maxLines) {
- break;
- }
- }
- }
- return {
- before: content.substring(startOffset, this.offset),
- after: content.substring(this.offset, endOffset + 1),
- };
- }
- return null;
- }
- }
- class ParseSourceFile {
- content;
- url;
- constructor(content, url) {
- this.content = content;
- this.url = url;
- }
- }
- class ParseSourceSpan {
- start;
- end;
- fullStart;
- details;
- /**
- * Create an object that holds information about spans of tokens/nodes captured during
- * lexing/parsing of text.
- *
- * @param start
- * The location of the start of the span (having skipped leading trivia).
- * Skipping leading trivia makes source-spans more "user friendly", since things like HTML
- * elements will appear to begin at the start of the opening tag, rather than at the start of any
- * leading trivia, which could include newlines.
- *
- * @param end
- * The location of the end of the span.
- *
- * @param fullStart
- * The start of the token without skipping the leading trivia.
- * This is used by tooling that splits tokens further, such as extracting Angular interpolations
- * from text tokens. Such tooling creates new source-spans relative to the original token's
- * source-span. If leading trivia characters have been skipped then the new source-spans may be
- * incorrectly offset.
- *
- * @param details
- * Additional information (such as identifier names) that should be associated with the span.
- */
- constructor(start, end, fullStart = start, details = null) {
- this.start = start;
- this.end = end;
- this.fullStart = fullStart;
- this.details = details;
- }
- toString() {
- return this.start.file.content.substring(this.start.offset, this.end.offset);
- }
- }
- var ParseErrorLevel;
- (function (ParseErrorLevel) {
- ParseErrorLevel[ParseErrorLevel["WARNING"] = 0] = "WARNING";
- ParseErrorLevel[ParseErrorLevel["ERROR"] = 1] = "ERROR";
- })(ParseErrorLevel || (ParseErrorLevel = {}));
- class ParseError {
- span;
- msg;
- level;
- relatedError;
- constructor(
- /** Location of the error. */
- span,
- /** Error message. */
- msg,
- /** Severity level of the error. */
- level = ParseErrorLevel.ERROR,
- /**
- * Error that caused the error to be surfaced. For example, an error in a sub-expression that
- * couldn't be parsed. Not guaranteed to be defined, but can be used to provide more context.
- */
- relatedError) {
- this.span = span;
- this.msg = msg;
- this.level = level;
- this.relatedError = relatedError;
- }
- contextualMessage() {
- const ctx = this.span.start.getContext(100, 3);
- return ctx
- ? `${this.msg} ("${ctx.before}[${ParseErrorLevel[this.level]} ->]${ctx.after}")`
- : this.msg;
- }
- toString() {
- const details = this.span.details ? `, ${this.span.details}` : '';
- return `${this.contextualMessage()}: ${this.span.start}${details}`;
- }
- }
- /**
- * Generates Source Span object for a given R3 Type for JIT mode.
- *
- * @param kind Component or Directive.
- * @param typeName name of the Component or Directive.
- * @param sourceUrl reference to Component or Directive source.
- * @returns instance of ParseSourceSpan that represent a given Component or Directive.
- */
- function r3JitTypeSourceSpan(kind, typeName, sourceUrl) {
- const sourceFileName = `in ${kind} ${typeName} in ${sourceUrl}`;
- const sourceFile = new ParseSourceFile('', sourceFileName);
- return new ParseSourceSpan(new ParseLocation(sourceFile, -1, -1, -1), new ParseLocation(sourceFile, -1, -1, -1));
- }
- let _anonymousTypeIndex = 0;
- function identifierName(compileIdentifier) {
- if (!compileIdentifier || !compileIdentifier.reference) {
- return null;
- }
- const ref = compileIdentifier.reference;
- if (ref['__anonymousType']) {
- return ref['__anonymousType'];
- }
- if (ref['__forward_ref__']) {
- // We do not want to try to stringify a `forwardRef()` function because that would cause the
- // inner function to be evaluated too early, defeating the whole point of the `forwardRef`.
- return '__forward_ref__';
- }
- let identifier = stringify(ref);
- if (identifier.indexOf('(') >= 0) {
- // case: anonymous functions!
- identifier = `anonymous_${_anonymousTypeIndex++}`;
- ref['__anonymousType'] = identifier;
- }
- else {
- identifier = sanitizeIdentifier(identifier);
- }
- return identifier;
- }
- function sanitizeIdentifier(name) {
- return name.replace(/\W/g, '_');
- }
- /**
- * In TypeScript, tagged template functions expect a "template object", which is an array of
- * "cooked" strings plus a `raw` property that contains an array of "raw" strings. This is
- * typically constructed with a function called `__makeTemplateObject(cooked, raw)`, but it may not
- * be available in all environments.
- *
- * This is a JavaScript polyfill that uses __makeTemplateObject when it's available, but otherwise
- * creates an inline helper with the same functionality.
- *
- * In the inline function, if `Object.defineProperty` is available we use that to attach the `raw`
- * array.
- */
- const makeTemplateObjectPolyfill = '(this&&this.__makeTemplateObject||function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e})';
- class AbstractJsEmitterVisitor extends AbstractEmitterVisitor {
- constructor() {
- super(false);
- }
- visitWrappedNodeExpr(ast, ctx) {
- throw new Error('Cannot emit a WrappedNodeExpr in Javascript.');
- }
- visitDeclareVarStmt(stmt, ctx) {
- ctx.print(stmt, `var ${stmt.name}`);
- if (stmt.value) {
- ctx.print(stmt, ' = ');
- stmt.value.visitExpression(this, ctx);
- }
- ctx.println(stmt, `;`);
- return null;
- }
- visitTaggedTemplateLiteralExpr(ast, ctx) {
- // The following convoluted piece of code is effectively the downlevelled equivalent of
- // ```
- // tag`...`
- // ```
- // which is effectively like:
- // ```
- // tag(__makeTemplateObject(cooked, raw), expression1, expression2, ...);
- // ```
- const elements = ast.template.elements;
- ast.tag.visitExpression(this, ctx);
- ctx.print(ast, `(${makeTemplateObjectPolyfill}(`);
- ctx.print(ast, `[${elements.map((part) => escapeIdentifier(part.text, false)).join(', ')}], `);
- ctx.print(ast, `[${elements.map((part) => escapeIdentifier(part.rawText, false)).join(', ')}])`);
- ast.template.expressions.forEach((expression) => {
- ctx.print(ast, ', ');
- expression.visitExpression(this, ctx);
- });
- ctx.print(ast, ')');
- return null;
- }
- visitTemplateLiteralExpr(expr, ctx) {
- ctx.print(expr, '`');
- for (let i = 0; i < expr.elements.length; i++) {
- expr.elements[i].visitExpression(this, ctx);
- const expression = i < expr.expressions.length ? expr.expressions[i] : null;
- if (expression !== null) {
- ctx.print(expression, '${');
- expression.visitExpression(this, ctx);
- ctx.print(expression, '}');
- }
- }
- ctx.print(expr, '`');
- }
- visitTemplateLiteralElementExpr(expr, ctx) {
- ctx.print(expr, expr.rawText);
- return null;
- }
- visitFunctionExpr(ast, ctx) {
- ctx.print(ast, `function${ast.name ? ' ' + ast.name : ''}(`);
- this._visitParams(ast.params, ctx);
- ctx.println(ast, `) {`);
- ctx.incIndent();
- this.visitAllStatements(ast.statements, ctx);
- ctx.decIndent();
- ctx.print(ast, `}`);
- return null;
- }
- visitArrowFunctionExpr(ast, ctx) {
- ctx.print(ast, '(');
- this._visitParams(ast.params, ctx);
- ctx.print(ast, ') =>');
- if (Array.isArray(ast.body)) {
- ctx.println(ast, `{`);
- ctx.incIndent();
- this.visitAllStatements(ast.body, ctx);
- ctx.decIndent();
- ctx.print(ast, `}`);
- }
- else {
- const isObjectLiteral = ast.body instanceof LiteralMapExpr;
- if (isObjectLiteral) {
- ctx.print(ast, '(');
- }
- ast.body.visitExpression(this, ctx);
- if (isObjectLiteral) {
- ctx.print(ast, ')');
- }
- }
- return null;
- }
- visitDeclareFunctionStmt(stmt, ctx) {
- ctx.print(stmt, `function ${stmt.name}(`);
- this._visitParams(stmt.params, ctx);
- ctx.println(stmt, `) {`);
- ctx.incIndent();
- this.visitAllStatements(stmt.statements, ctx);
- ctx.decIndent();
- ctx.println(stmt, `}`);
- return null;
- }
- visitLocalizedString(ast, ctx) {
- // The following convoluted piece of code is effectively the downlevelled equivalent of
- // ```
- // $localize `...`
- // ```
- // which is effectively like:
- // ```
- // $localize(__makeTemplateObject(cooked, raw), expression1, expression2, ...);
- // ```
- ctx.print(ast, `$localize(${makeTemplateObjectPolyfill}(`);
- const parts = [ast.serializeI18nHead()];
- for (let i = 1; i < ast.messageParts.length; i++) {
- parts.push(ast.serializeI18nTemplatePart(i));
- }
- ctx.print(ast, `[${parts.map((part) => escapeIdentifier(part.cooked, false)).join(', ')}], `);
- ctx.print(ast, `[${parts.map((part) => escapeIdentifier(part.raw, false)).join(', ')}])`);
- ast.expressions.forEach((expression) => {
- ctx.print(ast, ', ');
- expression.visitExpression(this, ctx);
- });
- ctx.print(ast, ')');
- return null;
- }
- _visitParams(params, ctx) {
- this.visitAllObjects((param) => ctx.print(null, param.name), params, ctx, ',');
- }
- }
- /**
- * @fileoverview
- * A module to facilitate use of a Trusted Types policy within the JIT
- * compiler. It lazily constructs the Trusted Types policy, providing helper
- * utilities for promoting strings to Trusted Types. When Trusted Types are not
- * available, strings are used as a fallback.
- * @security All use of this module is security-sensitive and should go through
- * security review.
- */
- /**
- * The Trusted Types policy, or null if Trusted Types are not
- * enabled/supported, or undefined if the policy has not been created yet.
- */
- let policy;
- /**
- * Returns the Trusted Types policy, or null if Trusted Types are not
- * enabled/supported. The first call to this function will create the policy.
- */
- function getPolicy() {
- if (policy === undefined) {
- const trustedTypes = _global['trustedTypes'];
- policy = null;
- if (trustedTypes) {
- try {
- policy = trustedTypes.createPolicy('angular#unsafe-jit', {
- createScript: (s) => s,
- });
- }
- catch {
- // trustedTypes.createPolicy throws if called with a name that is
- // already registered, even in report-only mode. Until the API changes,
- // catch the error not to break the applications functionally. In such
- // cases, the code will fall back to using strings.
- }
- }
- }
- return policy;
- }
- /**
- * Unsafely promote a string to a TrustedScript, falling back to strings when
- * Trusted Types are not available.
- * @security In particular, it must be assured that the provided string will
- * never cause an XSS vulnerability if used in a context that will be
- * interpreted and executed as a script by a browser, e.g. when calling eval.
- */
- function trustedScriptFromString(script) {
- return getPolicy()?.createScript(script) || script;
- }
- /**
- * Unsafely call the Function constructor with the given string arguments.
- * @security This is a security-sensitive function; any use of this function
- * must go through security review. In particular, it must be assured that it
- * is only called from the JIT compiler, as use in other code can lead to XSS
- * vulnerabilities.
- */
- function newTrustedFunctionForJIT(...args) {
- if (!_global['trustedTypes']) {
- // In environments that don't support Trusted Types, fall back to the most
- // straightforward implementation:
- return new Function(...args);
- }
- // Chrome currently does not support passing TrustedScript to the Function
- // constructor. The following implements the workaround proposed on the page
- // below, where the Chromium bug is also referenced:
- // https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
- const fnArgs = args.slice(0, -1).join(',');
- const fnBody = args[args.length - 1];
- const body = `(function anonymous(${fnArgs}
- ) { ${fnBody}
- })`;
- // Using eval directly confuses the compiler and prevents this module from
- // being stripped out of JS binaries even if not used. The global['eval']
- // indirection fixes that.
- const fn = _global['eval'](trustedScriptFromString(body));
- if (fn.bind === undefined) {
- // Workaround for a browser bug that only exists in Chrome 83, where passing
- // a TrustedScript to eval just returns the TrustedScript back without
- // evaluating it. In that case, fall back to the most straightforward
- // implementation:
- return new Function(...args);
- }
- // To completely mimic the behavior of calling "new Function", two more
- // things need to happen:
- // 1. Stringifying the resulting function should return its source code
- fn.toString = () => body;
- // 2. When calling the resulting function, `this` should refer to `global`
- return fn.bind(_global);
- // When Trusted Types support in Function constructors is widely available,
- // the implementation of this function can be simplified to:
- // return new Function(...args.map(a => trustedScriptFromString(a)));
- }
- /**
- * A helper class to manage the evaluation of JIT generated code.
- */
- class JitEvaluator {
- /**
- *
- * @param sourceUrl The URL of the generated code.
- * @param statements An array of Angular statement AST nodes to be evaluated.
- * @param refResolver Resolves `o.ExternalReference`s into values.
- * @param createSourceMaps If true then create a source-map for the generated code and include it
- * inline as a source-map comment.
- * @returns A map of all the variables in the generated code.
- */
- evaluateStatements(sourceUrl, statements, refResolver, createSourceMaps) {
- const converter = new JitEmitterVisitor(refResolver);
- const ctx = EmitterVisitorContext.createRoot();
- // Ensure generated code is in strict mode
- if (statements.length > 0 && !isUseStrictStatement(statements[0])) {
- statements = [literal$1('use strict').toStmt(), ...statements];
- }
- converter.visitAllStatements(statements, ctx);
- converter.createReturnStmt(ctx);
- return this.evaluateCode(sourceUrl, ctx, converter.getArgs(), createSourceMaps);
- }
- /**
- * Evaluate a piece of JIT generated code.
- * @param sourceUrl The URL of this generated code.
- * @param ctx A context object that contains an AST of the code to be evaluated.
- * @param vars A map containing the names and values of variables that the evaluated code might
- * reference.
- * @param createSourceMap If true then create a source-map for the generated code and include it
- * inline as a source-map comment.
- * @returns The result of evaluating the code.
- */
- evaluateCode(sourceUrl, ctx, vars, createSourceMap) {
- let fnBody = `"use strict";${ctx.toSource()}\n//# sourceURL=${sourceUrl}`;
- const fnArgNames = [];
- const fnArgValues = [];
- for (const argName in vars) {
- fnArgValues.push(vars[argName]);
- fnArgNames.push(argName);
- }
- if (createSourceMap) {
- // using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise
- // E.g. ```
- // function anonymous(a,b,c
- // /**/) { ... }```
- // We don't want to hard code this fact, so we auto detect it via an empty function first.
- const emptyFn = newTrustedFunctionForJIT(...fnArgNames.concat('return null;')).toString();
- const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1;
- fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()}`;
- }
- const fn = newTrustedFunctionForJIT(...fnArgNames.concat(fnBody));
- return this.executeFunction(fn, fnArgValues);
- }
- /**
- * Execute a JIT generated function by calling it.
- *
- * This method can be overridden in tests to capture the functions that are generated
- * by this `JitEvaluator` class.
- *
- * @param fn A function to execute.
- * @param args The arguments to pass to the function being executed.
- * @returns The return value of the executed function.
- */
- executeFunction(fn, args) {
- return fn(...args);
- }
- }
- /**
- * An Angular AST visitor that converts AST nodes into executable JavaScript code.
- */
- class JitEmitterVisitor extends AbstractJsEmitterVisitor {
- refResolver;
- _evalArgNames = [];
- _evalArgValues = [];
- _evalExportedVars = [];
- constructor(refResolver) {
- super();
- this.refResolver = refResolver;
- }
- createReturnStmt(ctx) {
- const stmt = new ReturnStatement(new LiteralMapExpr(this._evalExportedVars.map((resultVar) => new LiteralMapEntry(resultVar, variable(resultVar), false))));
- stmt.visitStatement(this, ctx);
- }
- getArgs() {
- const result = {};
- for (let i = 0; i < this._evalArgNames.length; i++) {
- result[this._evalArgNames[i]] = this._evalArgValues[i];
- }
- return result;
- }
- visitExternalExpr(ast, ctx) {
- this._emitReferenceToExternal(ast, this.refResolver.resolveExternalReference(ast.value), ctx);
- return null;
- }
- visitWrappedNodeExpr(ast, ctx) {
- this._emitReferenceToExternal(ast, ast.node, ctx);
- return null;
- }
- visitDeclareVarStmt(stmt, ctx) {
- if (stmt.hasModifier(exports.StmtModifier.Exported)) {
- this._evalExportedVars.push(stmt.name);
- }
- return super.visitDeclareVarStmt(stmt, ctx);
- }
- visitDeclareFunctionStmt(stmt, ctx) {
- if (stmt.hasModifier(exports.StmtModifier.Exported)) {
- this._evalExportedVars.push(stmt.name);
- }
- return super.visitDeclareFunctionStmt(stmt, ctx);
- }
- _emitReferenceToExternal(ast, value, ctx) {
- let id = this._evalArgValues.indexOf(value);
- if (id === -1) {
- id = this._evalArgValues.length;
- this._evalArgValues.push(value);
- const name = identifierName({ reference: value }) || 'val';
- this._evalArgNames.push(`jit_${name}_${id}`);
- }
- ctx.print(ast, this._evalArgNames[id]);
- }
- }
- function isUseStrictStatement(statement) {
- return statement.isEquivalent(literal$1('use strict').toStmt());
- }
- function compileInjector(meta) {
- const definitionMap = new DefinitionMap();
- if (meta.providers !== null) {
- definitionMap.set('providers', meta.providers);
- }
- if (meta.imports.length > 0) {
- definitionMap.set('imports', literalArr(meta.imports));
- }
- const expression = importExpr(Identifiers.defineInjector)
- .callFn([definitionMap.toLiteralMap()], undefined, true);
- const type = createInjectorType(meta);
- return { expression, type, statements: [] };
- }
- function createInjectorType(meta) {
- return new ExpressionType(importExpr(Identifiers.InjectorDeclaration, [new ExpressionType(meta.type.type)]));
- }
- /**
- * Implementation of `CompileReflector` which resolves references to @angular/core
- * symbols at runtime, according to a consumer-provided mapping.
- *
- * Only supports `resolveExternalReference`, all other methods throw.
- */
- class R3JitReflector {
- context;
- constructor(context) {
- this.context = context;
- }
- resolveExternalReference(ref) {
- // This reflector only handles @angular/core imports.
- if (ref.moduleName !== '@angular/core') {
- throw new Error(`Cannot resolve external reference to ${ref.moduleName}, only references to @angular/core are supported.`);
- }
- if (!this.context.hasOwnProperty(ref.name)) {
- throw new Error(`No value provided for @angular/core symbol '${ref.name}'.`);
- }
- return this.context[ref.name];
- }
- }
- /**
- * How the selector scope of an NgModule (its declarations, imports, and exports) should be emitted
- * as a part of the NgModule definition.
- */
- exports.R3SelectorScopeMode = void 0;
- (function (R3SelectorScopeMode) {
- /**
- * Emit the declarations inline into the module definition.
- *
- * This option is useful in certain contexts where it's known that JIT support is required. The
- * tradeoff here is that this emit style prevents directives and pipes from being tree-shaken if
- * they are unused, but the NgModule is used.
- */
- R3SelectorScopeMode[R3SelectorScopeMode["Inline"] = 0] = "Inline";
- /**
- * Emit the declarations using a side effectful function call, `ɵɵsetNgModuleScope`, that is
- * guarded with the `ngJitMode` flag.
- *
- * This form of emit supports JIT and can be optimized away if the `ngJitMode` flag is set to
- * false, which allows unused directives and pipes to be tree-shaken.
- */
- R3SelectorScopeMode[R3SelectorScopeMode["SideEffect"] = 1] = "SideEffect";
- /**
- * Don't generate selector scopes at all.
- *
- * This is useful for contexts where JIT support is known to be unnecessary.
- */
- R3SelectorScopeMode[R3SelectorScopeMode["Omit"] = 2] = "Omit";
- })(exports.R3SelectorScopeMode || (exports.R3SelectorScopeMode = {}));
- /**
- * The type of the NgModule meta data.
- * - Global: Used for full and partial compilation modes which mainly includes R3References.
- * - Local: Used for the local compilation mode which mainly includes the raw expressions as appears
- * in the NgModule decorator.
- */
- exports.R3NgModuleMetadataKind = void 0;
- (function (R3NgModuleMetadataKind) {
- R3NgModuleMetadataKind[R3NgModuleMetadataKind["Global"] = 0] = "Global";
- R3NgModuleMetadataKind[R3NgModuleMetadataKind["Local"] = 1] = "Local";
- })(exports.R3NgModuleMetadataKind || (exports.R3NgModuleMetadataKind = {}));
- /**
- * Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
- */
- function compileNgModule(meta) {
- const statements = [];
- const definitionMap = new DefinitionMap();
- definitionMap.set('type', meta.type.value);
- // Assign bootstrap definition. In local compilation mode (i.e., for
- // `R3NgModuleMetadataKind.LOCAL`) we assign the bootstrap field using the runtime
- // `ɵɵsetNgModuleScope`.
- if (meta.kind === exports.R3NgModuleMetadataKind.Global && meta.bootstrap.length > 0) {
- definitionMap.set('bootstrap', refsToArray(meta.bootstrap, meta.containsForwardDecls));
- }
- if (meta.selectorScopeMode === exports.R3SelectorScopeMode.Inline) {
- // If requested to emit scope information inline, pass the `declarations`, `imports` and
- // `exports` to the `ɵɵdefineNgModule()` call directly.
- if (meta.declarations.length > 0) {
- definitionMap.set('declarations', refsToArray(meta.declarations, meta.containsForwardDecls));
- }
- if (meta.imports.length > 0) {
- definitionMap.set('imports', refsToArray(meta.imports, meta.containsForwardDecls));
- }
- if (meta.exports.length > 0) {
- definitionMap.set('exports', refsToArray(meta.exports, meta.containsForwardDecls));
- }
- }
- else if (meta.selectorScopeMode === exports.R3SelectorScopeMode.SideEffect) {
- // In this mode, scope information is not passed into `ɵɵdefineNgModule` as it
- // would prevent tree-shaking of the declarations, imports and exports references. Instead, it's
- // patched onto the NgModule definition with a `ɵɵsetNgModuleScope` call that's guarded by the
- // `ngJitMode` flag.
- const setNgModuleScopeCall = generateSetNgModuleScopeCall(meta);
- if (setNgModuleScopeCall !== null) {
- statements.push(setNgModuleScopeCall);
- }
- }
- else ;
- if (meta.schemas !== null && meta.schemas.length > 0) {
- definitionMap.set('schemas', literalArr(meta.schemas.map((ref) => ref.value)));
- }
- if (meta.id !== null) {
- definitionMap.set('id', meta.id);
- // Generate a side-effectful call to register this NgModule by its id, as per the semantics of
- // NgModule ids.
- statements.push(importExpr(Identifiers.registerNgModuleType).callFn([meta.type.value, meta.id]).toStmt());
- }
- const expression = importExpr(Identifiers.defineNgModule)
- .callFn([definitionMap.toLiteralMap()], undefined, true);
- const type = createNgModuleType(meta);
- return { expression, type, statements };
- }
- /**
- * This function is used in JIT mode to generate the call to `ɵɵdefineNgModule()` from a call to
- * `ɵɵngDeclareNgModule()`.
- */
- function compileNgModuleDeclarationExpression(meta) {
- const definitionMap = new DefinitionMap();
- definitionMap.set('type', new WrappedNodeExpr(meta.type));
- if (meta.bootstrap !== undefined) {
- definitionMap.set('bootstrap', new WrappedNodeExpr(meta.bootstrap));
- }
- if (meta.declarations !== undefined) {
- definitionMap.set('declarations', new WrappedNodeExpr(meta.declarations));
- }
- if (meta.imports !== undefined) {
- definitionMap.set('imports', new WrappedNodeExpr(meta.imports));
- }
- if (meta.exports !== undefined) {
- definitionMap.set('exports', new WrappedNodeExpr(meta.exports));
- }
- if (meta.schemas !== undefined) {
- definitionMap.set('schemas', new WrappedNodeExpr(meta.schemas));
- }
- if (meta.id !== undefined) {
- definitionMap.set('id', new WrappedNodeExpr(meta.id));
- }
- return importExpr(Identifiers.defineNgModule).callFn([definitionMap.toLiteralMap()]);
- }
- function createNgModuleType(meta) {
- if (meta.kind === exports.R3NgModuleMetadataKind.Local) {
- return new ExpressionType(meta.type.value);
- }
- const { type: moduleType, declarations, exports: exports$1, imports, includeImportTypes, publicDeclarationTypes, } = meta;
- return new ExpressionType(importExpr(Identifiers.NgModuleDeclaration, [
- new ExpressionType(moduleType.type),
- publicDeclarationTypes === null
- ? tupleTypeOf(declarations)
- : tupleOfTypes(publicDeclarationTypes),
- includeImportTypes ? tupleTypeOf(imports) : NONE_TYPE,
- tupleTypeOf(exports$1),
- ]));
- }
- /**
- * Generates a function call to `ɵɵsetNgModuleScope` with all necessary information so that the
- * transitive module scope can be computed during runtime in JIT mode. This call is marked pure
- * such that the references to declarations, imports and exports may be elided causing these
- * symbols to become tree-shakeable.
- */
- function generateSetNgModuleScopeCall(meta) {
- const scopeMap = new DefinitionMap();
- if (meta.kind === exports.R3NgModuleMetadataKind.Global) {
- if (meta.declarations.length > 0) {
- scopeMap.set('declarations', refsToArray(meta.declarations, meta.containsForwardDecls));
- }
- }
- else {
- if (meta.declarationsExpression) {
- scopeMap.set('declarations', meta.declarationsExpression);
- }
- }
- if (meta.kind === exports.R3NgModuleMetadataKind.Global) {
- if (meta.imports.length > 0) {
- scopeMap.set('imports', refsToArray(meta.imports, meta.containsForwardDecls));
- }
- }
- else {
- if (meta.importsExpression) {
- scopeMap.set('imports', meta.importsExpression);
- }
- }
- if (meta.kind === exports.R3NgModuleMetadataKind.Global) {
- if (meta.exports.length > 0) {
- scopeMap.set('exports', refsToArray(meta.exports, meta.containsForwardDecls));
- }
- }
- else {
- if (meta.exportsExpression) {
- scopeMap.set('exports', meta.exportsExpression);
- }
- }
- if (meta.kind === exports.R3NgModuleMetadataKind.Local && meta.bootstrapExpression) {
- scopeMap.set('bootstrap', meta.bootstrapExpression);
- }
- if (Object.keys(scopeMap.values).length === 0) {
- return null;
- }
- // setNgModuleScope(...)
- const fnCall = new InvokeFunctionExpr(
- /* fn */ importExpr(Identifiers.setNgModuleScope),
- /* args */ [meta.type.value, scopeMap.toLiteralMap()]);
- // (ngJitMode guard) && setNgModuleScope(...)
- const guardedCall = jitOnlyGuardedExpression(fnCall);
- // function() { (ngJitMode guard) && setNgModuleScope(...); }
- const iife = new FunctionExpr(/* params */ [], /* statements */ [guardedCall.toStmt()]);
- // (function() { (ngJitMode guard) && setNgModuleScope(...); })()
- const iifeCall = new InvokeFunctionExpr(/* fn */ iife, /* args */ []);
- return iifeCall.toStmt();
- }
- function tupleTypeOf(exp) {
- const types = exp.map((ref) => typeofExpr(ref.type));
- return exp.length > 0 ? expressionType(literalArr(types)) : NONE_TYPE;
- }
- function tupleOfTypes(types) {
- const typeofTypes = types.map((type) => typeofExpr(type));
- return types.length > 0 ? expressionType(literalArr(typeofTypes)) : NONE_TYPE;
- }
- function compilePipeFromMetadata(metadata) {
- const definitionMapValues = [];
- // e.g. `name: 'myPipe'`
- definitionMapValues.push({ key: 'name', value: literal$1(metadata.pipeName), quoted: false });
- // e.g. `type: MyPipe`
- definitionMapValues.push({ key: 'type', value: metadata.type.value, quoted: false });
- // e.g. `pure: true`
- definitionMapValues.push({ key: 'pure', value: literal$1(metadata.pure), quoted: false });
- if (metadata.isStandalone === false) {
- definitionMapValues.push({ key: 'standalone', value: literal$1(false), quoted: false });
- }
- const expression = importExpr(Identifiers.definePipe)
- .callFn([literalMap(definitionMapValues)], undefined, true);
- const type = createPipeType(metadata);
- return { expression, type, statements: [] };
- }
- function createPipeType(metadata) {
- return new ExpressionType(importExpr(Identifiers.PipeDeclaration, [
- typeWithParameters(metadata.type.type, metadata.typeArgumentCount),
- new ExpressionType(new LiteralExpr(metadata.pipeName)),
- new ExpressionType(new LiteralExpr(metadata.isStandalone)),
- ]));
- }
- exports.R3TemplateDependencyKind = void 0;
- (function (R3TemplateDependencyKind) {
- R3TemplateDependencyKind[R3TemplateDependencyKind["Directive"] = 0] = "Directive";
- R3TemplateDependencyKind[R3TemplateDependencyKind["Pipe"] = 1] = "Pipe";
- R3TemplateDependencyKind[R3TemplateDependencyKind["NgModule"] = 2] = "NgModule";
- })(exports.R3TemplateDependencyKind || (exports.R3TemplateDependencyKind = {}));
- /**
- * The following set contains all keywords that can be used in the animation css shorthand
- * property and is used during the scoping of keyframes to make sure such keywords
- * are not modified.
- */
- const animationKeywords = new Set([
- // global values
- 'inherit',
- 'initial',
- 'revert',
- 'unset',
- // animation-direction
- 'alternate',
- 'alternate-reverse',
- 'normal',
- 'reverse',
- // animation-fill-mode
- 'backwards',
- 'both',
- 'forwards',
- 'none',
- // animation-play-state
- 'paused',
- 'running',
- // animation-timing-function
- 'ease',
- 'ease-in',
- 'ease-in-out',
- 'ease-out',
- 'linear',
- 'step-start',
- 'step-end',
- // `steps()` function
- 'end',
- 'jump-both',
- 'jump-end',
- 'jump-none',
- 'jump-start',
- 'start',
- ]);
- /**
- * The following array contains all of the CSS at-rule identifiers which are scoped.
- */
- const scopedAtRuleIdentifiers = [
- '@media',
- '@supports',
- '@document',
- '@layer',
- '@container',
- '@scope',
- '@starting-style',
- ];
- /**
- * The following class has its origin from a port of shadowCSS from webcomponents.js to TypeScript.
- * It has since diverge in many ways to tailor Angular's needs.
- *
- * Source:
- * https://github.com/webcomponents/webcomponentsjs/blob/4efecd7e0e/src/ShadowCSS/ShadowCSS.js
- *
- * The original file level comment is reproduced below
- */
- /*
- This is a limited shim for ShadowDOM css styling.
- https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
- The intention here is to support only the styling features which can be
- relatively simply implemented. The goal is to allow users to avoid the
- most obvious pitfalls and do so without compromising performance significantly.
- For ShadowDOM styling that's not covered here, a set of best practices
- can be provided that should allow users to accomplish more complex styling.
- The following is a list of specific ShadowDOM styling features and a brief
- discussion of the approach used to shim.
- Shimmed features:
- * :host, :host-context: ShadowDOM allows styling of the shadowRoot's host
- element using the :host rule. To shim this feature, the :host styles are
- reformatted and prefixed with a given scope name and promoted to a
- document level stylesheet.
- For example, given a scope name of .foo, a rule like this:
- :host {
- background: red;
- }
- }
- becomes:
- .foo {
- background: red;
- }
- * encapsulation: Styles defined within ShadowDOM, apply only to
- dom inside the ShadowDOM.
- The selectors are scoped by adding an attribute selector suffix to each
- simple selector that contains the host element tag name. Each element
- in the element's ShadowDOM template is also given the scope attribute.
- Thus, these rules match only elements that have the scope attribute.
- For example, given a scope name of x-foo, a rule like this:
- div {
- font-weight: bold;
- }
- becomes:
- div[x-foo] {
- font-weight: bold;
- }
- Note that elements that are dynamically added to a scope must have the scope
- selector added to them manually.
- * upper/lower bound encapsulation: Styles which are defined outside a
- shadowRoot should not cross the ShadowDOM boundary and should not apply
- inside a shadowRoot.
- This styling behavior is not emulated. Some possible ways to do this that
- were rejected due to complexity and/or performance concerns include: (1) reset
- every possible property for every possible selector for a given scope name;
- (2) re-implement css in javascript.
- As an alternative, users should make sure to use selectors
- specific to the scope in which they are working.
- * ::distributed: This behavior is not emulated. It's often not necessary
- to style the contents of a specific insertion point and instead, descendants
- of the host element can be styled selectively. Users can also create an
- extra node around an insertion point and style that node's contents
- via descendent selectors. For example, with a shadowRoot like this:
- <style>
- ::content(div) {
- background: red;
- }
- </style>
- <content></content>
- could become:
- <style>
- / *@polyfill .content-container div * /
- ::content(div) {
- background: red;
- }
- </style>
- <div class="content-container">
- <content></content>
- </div>
- Note the use of @polyfill in the comment above a ShadowDOM specific style
- declaration. This is a directive to the styling shim to use the selector
- in comments in lieu of the next selector when running under polyfill.
- */
- class ShadowCss {
- /*
- * Shim some cssText with the given selector. Returns cssText that can be included in the document
- *
- * The selector is the attribute added to all elements inside the host,
- * The hostSelector is the attribute added to the host itself.
- */
- shimCssText(cssText, selector, hostSelector = '') {
- // **NOTE**: Do not strip comments as this will cause component sourcemaps to break
- // due to shift in lines.
- // Collect comments and replace them with a placeholder, this is done to avoid complicating
- // the rule parsing RegExp and keep it safer.
- const comments = [];
- cssText = cssText.replace(_commentRe, (m) => {
- if (m.match(_commentWithHashRe)) {
- comments.push(m);
- }
- else {
- // Replace non hash comments with empty lines.
- // This is done so that we do not leak any sensitive data in comments.
- const newLinesMatches = m.match(_newLinesRe);
- comments.push((newLinesMatches?.join('') ?? '') + '\n');
- }
- return COMMENT_PLACEHOLDER;
- });
- cssText = this._insertDirectives(cssText);
- const scopedCssText = this._scopeCssText(cssText, selector, hostSelector);
- // Add back comments at the original position.
- let commentIdx = 0;
- return scopedCssText.replace(_commentWithHashPlaceHolderRe, () => comments[commentIdx++]);
- }
- _insertDirectives(cssText) {
- cssText = this._insertPolyfillDirectivesInCssText(cssText);
- return this._insertPolyfillRulesInCssText(cssText);
- }
- /**
- * Process styles to add scope to keyframes.
- *
- * Modify both the names of the keyframes defined in the component styles and also the css
- * animation rules using them.
- *
- * Animation rules using keyframes defined elsewhere are not modified to allow for globally
- * defined keyframes.
- *
- * For example, we convert this css:
- *
- * ```scss
- * .box {
- * animation: box-animation 1s forwards;
- * }
- *
- * @keyframes box-animation {
- * to {
- * background-color: green;
- * }
- * }
- * ```
- *
- * to this:
- *
- * ```scss
- * .box {
- * animation: scopeName_box-animation 1s forwards;
- * }
- *
- * @keyframes scopeName_box-animation {
- * to {
- * background-color: green;
- * }
- * }
- * ```
- *
- * @param cssText the component's css text that needs to be scoped.
- * @param scopeSelector the component's scope selector.
- *
- * @returns the scoped css text.
- */
- _scopeKeyframesRelatedCss(cssText, scopeSelector) {
- const unscopedKeyframesSet = new Set();
- const scopedKeyframesCssText = processRules(cssText, (rule) => this._scopeLocalKeyframeDeclarations(rule, scopeSelector, unscopedKeyframesSet));
- return processRules(scopedKeyframesCssText, (rule) => this._scopeAnimationRule(rule, scopeSelector, unscopedKeyframesSet));
- }
- /**
- * Scopes local keyframes names, returning the updated css rule and it also
- * adds the original keyframe name to a provided set to collect all keyframes names
- * so that it can later be used to scope the animation rules.
- *
- * For example, it takes a rule such as:
- *
- * ```scss
- * @keyframes box-animation {
- * to {
- * background-color: green;
- * }
- * }
- * ```
- *
- * and returns:
- *
- * ```scss
- * @keyframes scopeName_box-animation {
- * to {
- * background-color: green;
- * }
- * }
- * ```
- * and as a side effect it adds "box-animation" to the `unscopedKeyframesSet` set
- *
- * @param cssRule the css rule to process.
- * @param scopeSelector the component's scope selector.
- * @param unscopedKeyframesSet the set of unscoped keyframes names (which can be
- * modified as a side effect)
- *
- * @returns the css rule modified with the scoped keyframes name.
- */
- _scopeLocalKeyframeDeclarations(rule, scopeSelector, unscopedKeyframesSet) {
- return {
- ...rule,
- selector: rule.selector.replace(/(^@(?:-webkit-)?keyframes(?:\s+))(['"]?)(.+)\2(\s*)$/, (_, start, quote, keyframeName, endSpaces) => {
- unscopedKeyframesSet.add(unescapeQuotes(keyframeName, quote));
- return `${start}${quote}${scopeSelector}_${keyframeName}${quote}${endSpaces}`;
- }),
- };
- }
- /**
- * Function used to scope a keyframes name (obtained from an animation declaration)
- * using an existing set of unscopedKeyframes names to discern if the scoping needs to be
- * performed (keyframes names of keyframes not defined in the component's css need not to be
- * scoped).
- *
- * @param keyframe the keyframes name to check.
- * @param scopeSelector the component's scope selector.
- * @param unscopedKeyframesSet the set of unscoped keyframes names.
- *
- * @returns the scoped name of the keyframe, or the original name is the name need not to be
- * scoped.
- */
- _scopeAnimationKeyframe(keyframe, scopeSelector, unscopedKeyframesSet) {
- return keyframe.replace(/^(\s*)(['"]?)(.+?)\2(\s*)$/, (_, spaces1, quote, name, spaces2) => {
- name = `${unscopedKeyframesSet.has(unescapeQuotes(name, quote)) ? scopeSelector + '_' : ''}${name}`;
- return `${spaces1}${quote}${name}${quote}${spaces2}`;
- });
- }
- /**
- * Regular expression used to extrapolate the possible keyframes from an
- * animation declaration (with possibly multiple animation definitions)
- *
- * The regular expression can be divided in three parts
- * - (^|\s+|,)
- * captures how many (if any) leading whitespaces are present or a comma
- * - (?:(?:(['"])((?:\\\\|\\\2|(?!\2).)+)\2)|(-?[A-Za-z][\w\-]*))
- * captures two different possible keyframes, ones which are quoted or ones which are valid css
- * indents (custom properties excluded)
- * - (?=[,\s;]|$)
- * simply matches the end of the possible keyframe, valid endings are: a comma, a space, a
- * semicolon or the end of the string
- */
- _animationDeclarationKeyframesRe = /(^|\s+|,)(?:(?:(['"])((?:\\\\|\\\2|(?!\2).)+)\2)|(-?[A-Za-z][\w\-]*))(?=[,\s]|$)/g;
- /**
- * Scope an animation rule so that the keyframes mentioned in such rule
- * are scoped if defined in the component's css and left untouched otherwise.
- *
- * It can scope values of both the 'animation' and 'animation-name' properties.
- *
- * @param rule css rule to scope.
- * @param scopeSelector the component's scope selector.
- * @param unscopedKeyframesSet the set of unscoped keyframes names.
- *
- * @returns the updated css rule.
- **/
- _scopeAnimationRule(rule, scopeSelector, unscopedKeyframesSet) {
- let content = rule.content.replace(/((?:^|\s+|;)(?:-webkit-)?animation\s*:\s*),*([^;]+)/g, (_, start, animationDeclarations) => start +
- animationDeclarations.replace(this._animationDeclarationKeyframesRe, (original, leadingSpaces, quote = '', quotedName, nonQuotedName) => {
- if (quotedName) {
- return `${leadingSpaces}${this._scopeAnimationKeyframe(`${quote}${quotedName}${quote}`, scopeSelector, unscopedKeyframesSet)}`;
- }
- else {
- return animationKeywords.has(nonQuotedName)
- ? original
- : `${leadingSpaces}${this._scopeAnimationKeyframe(nonQuotedName, scopeSelector, unscopedKeyframesSet)}`;
- }
- }));
- content = content.replace(/((?:^|\s+|;)(?:-webkit-)?animation-name(?:\s*):(?:\s*))([^;]+)/g, (_match, start, commaSeparatedKeyframes) => `${start}${commaSeparatedKeyframes
- .split(',')
- .map((keyframe) => this._scopeAnimationKeyframe(keyframe, scopeSelector, unscopedKeyframesSet))
- .join(',')}`);
- return { ...rule, content };
- }
- /*
- * Process styles to convert native ShadowDOM rules that will trip
- * up the css parser; we rely on decorating the stylesheet with inert rules.
- *
- * For example, we convert this rule:
- *
- * polyfill-next-selector { content: ':host menu-item'; }
- * ::content menu-item {
- *
- * to this:
- *
- * scopeName menu-item {
- *
- **/
- _insertPolyfillDirectivesInCssText(cssText) {
- return cssText.replace(_cssContentNextSelectorRe, function (...m) {
- return m[2] + '{';
- });
- }
- /*
- * Process styles to add rules which will only apply under the polyfill
- *
- * For example, we convert this rule:
- *
- * polyfill-rule {
- * content: ':host menu-item';
- * ...
- * }
- *
- * to this:
- *
- * scopeName menu-item {...}
- *
- **/
- _insertPolyfillRulesInCssText(cssText) {
- return cssText.replace(_cssContentRuleRe, (...m) => {
- const rule = m[0].replace(m[1], '').replace(m[2], '');
- return m[4] + rule;
- });
- }
- /* Ensure styles are scoped. Pseudo-scoping takes a rule like:
- *
- * .foo {... }
- *
- * and converts this to
- *
- * scopeName .foo { ... }
- */
- _scopeCssText(cssText, scopeSelector, hostSelector) {
- const unscopedRules = this._extractUnscopedRulesFromCssText(cssText);
- // replace :host and :host-context with -shadowcsshost and -shadowcsshostcontext respectively
- cssText = this._insertPolyfillHostInCssText(cssText);
- cssText = this._convertColonHost(cssText);
- cssText = this._convertColonHostContext(cssText);
- cssText = this._convertShadowDOMSelectors(cssText);
- if (scopeSelector) {
- cssText = this._scopeKeyframesRelatedCss(cssText, scopeSelector);
- cssText = this._scopeSelectors(cssText, scopeSelector, hostSelector);
- }
- cssText = cssText + '\n' + unscopedRules;
- return cssText.trim();
- }
- /*
- * Process styles to add rules which will only apply under the polyfill
- * and do not process via CSSOM. (CSSOM is destructive to rules on rare
- * occasions, e.g. -webkit-calc on Safari.)
- * For example, we convert this rule:
- *
- * @polyfill-unscoped-rule {
- * content: 'menu-item';
- * ... }
- *
- * to this:
- *
- * menu-item {...}
- *
- **/
- _extractUnscopedRulesFromCssText(cssText) {
- let r = '';
- let m;
- _cssContentUnscopedRuleRe.lastIndex = 0;
- while ((m = _cssContentUnscopedRuleRe.exec(cssText)) !== null) {
- const rule = m[0].replace(m[2], '').replace(m[1], m[4]);
- r += rule + '\n\n';
- }
- return r;
- }
- /*
- * convert a rule like :host(.foo) > .bar { }
- *
- * to
- *
- * .foo<scopeName> > .bar
- */
- _convertColonHost(cssText) {
- return cssText.replace(_cssColonHostRe, (_, hostSelectors, otherSelectors) => {
- if (hostSelectors) {
- const convertedSelectors = [];
- const hostSelectorArray = hostSelectors.split(',').map((p) => p.trim());
- for (const hostSelector of hostSelectorArray) {
- if (!hostSelector)
- break;
- const convertedSelector = _polyfillHostNoCombinator + hostSelector.replace(_polyfillHost, '') + otherSelectors;
- convertedSelectors.push(convertedSelector);
- }
- return convertedSelectors.join(',');
- }
- else {
- return _polyfillHostNoCombinator + otherSelectors;
- }
- });
- }
- /*
- * convert a rule like :host-context(.foo) > .bar { }
- *
- * to
- *
- * .foo<scopeName> > .bar, .foo <scopeName> > .bar { }
- *
- * and
- *
- * :host-context(.foo:host) .bar { ... }
- *
- * to
- *
- * .foo<scopeName> .bar { ... }
- */
- _convertColonHostContext(cssText) {
- const length = cssText.length;
- let parens = 0;
- let prev = 0;
- let result = '';
- // Splits up the selectors on their top-level commas, processes the :host-context in them
- // individually and stitches them back together. This ensures that individual selectors don't
- // affect each other.
- for (let i = 0; i < length; i++) {
- const char = cssText[i];
- // If we hit a comma and there are no open parentheses, take the current chunk and process it.
- if (char === ',' && parens === 0) {
- result += this._convertColonHostContextInSelectorPart(cssText.slice(prev, i)) + ',';
- prev = i + 1;
- continue;
- }
- // We've hit the end. Take everything since the last comma.
- if (i === length - 1) {
- result += this._convertColonHostContextInSelectorPart(cssText.slice(prev));
- break;
- }
- if (char === '(') {
- parens++;
- }
- else if (char === ')') {
- parens--;
- }
- }
- return result;
- }
- _convertColonHostContextInSelectorPart(cssText) {
- return cssText.replace(_cssColonHostContextReGlobal, (selectorText, pseudoPrefix) => {
- // We have captured a selector that contains a `:host-context` rule.
- // For backward compatibility `:host-context` may contain a comma separated list of selectors.
- // Each context selector group will contain a list of host-context selectors that must match
- // an ancestor of the host.
- // (Normally `contextSelectorGroups` will only contain a single array of context selectors.)
- const contextSelectorGroups = [[]];
- // There may be more than `:host-context` in this selector so `selectorText` could look like:
- // `:host-context(.one):host-context(.two)`.
- // Execute `_cssColonHostContextRe` over and over until we have extracted all the
- // `:host-context` selectors from this selector.
- let match;
- while ((match = _cssColonHostContextRe.exec(selectorText))) {
- // `match` = [':host-context(<selectors>)<rest>', <selectors>, <rest>]
- // The `<selectors>` could actually be a comma separated list: `:host-context(.one, .two)`.
- const newContextSelectors = (match[1] ?? '')
- .trim()
- .split(',')
- .map((m) => m.trim())
- .filter((m) => m !== '');
- // We must duplicate the current selector group for each of these new selectors.
- // For example if the current groups are:
- // ```
- // [
- // ['a', 'b', 'c'],
- // ['x', 'y', 'z'],
- // ]
- // ```
- // And we have a new set of comma separated selectors: `:host-context(m,n)` then the new
- // groups are:
- // ```
- // [
- // ['a', 'b', 'c', 'm'],
- // ['x', 'y', 'z', 'm'],
- // ['a', 'b', 'c', 'n'],
- // ['x', 'y', 'z', 'n'],
- // ]
- // ```
- const contextSelectorGroupsLength = contextSelectorGroups.length;
- repeatGroups(contextSelectorGroups, newContextSelectors.length);
- for (let i = 0; i < newContextSelectors.length; i++) {
- for (let j = 0; j < contextSelectorGroupsLength; j++) {
- contextSelectorGroups[j + i * contextSelectorGroupsLength].push(newContextSelectors[i]);
- }
- }
- // Update the `selectorText` and see repeat to see if there are more `:host-context`s.
- selectorText = match[2];
- }
- // The context selectors now must be combined with each other to capture all the possible
- // selectors that `:host-context` can match. See `_combineHostContextSelectors()` for more
- // info about how this is done.
- return contextSelectorGroups
- .map((contextSelectors) => _combineHostContextSelectors(contextSelectors, selectorText, pseudoPrefix))
- .join(', ');
- });
- }
- /*
- * Convert combinators like ::shadow and pseudo-elements like ::content
- * by replacing with space.
- */
- _convertShadowDOMSelectors(cssText) {
- return _shadowDOMSelectorsRe.reduce((result, pattern) => result.replace(pattern, ' '), cssText);
- }
- // change a selector like 'div' to 'name div'
- _scopeSelectors(cssText, scopeSelector, hostSelector) {
- return processRules(cssText, (rule) => {
- let selector = rule.selector;
- let content = rule.content;
- if (rule.selector[0] !== '@') {
- selector = this._scopeSelector({
- selector,
- scopeSelector,
- hostSelector,
- isParentSelector: true,
- });
- }
- else if (scopedAtRuleIdentifiers.some((atRule) => rule.selector.startsWith(atRule))) {
- content = this._scopeSelectors(rule.content, scopeSelector, hostSelector);
- }
- else if (rule.selector.startsWith('@font-face') || rule.selector.startsWith('@page')) {
- content = this._stripScopingSelectors(rule.content);
- }
- return new CssRule(selector, content);
- });
- }
- /**
- * Handle a css text that is within a rule that should not contain scope selectors by simply
- * removing them! An example of such a rule is `@font-face`.
- *
- * `@font-face` rules cannot contain nested selectors. Nor can they be nested under a selector.
- * Normally this would be a syntax error by the author of the styles. But in some rare cases, such
- * as importing styles from a library, and applying `:host ::ng-deep` to the imported styles, we
- * can end up with broken css if the imported styles happen to contain @font-face rules.
- *
- * For example:
- *
- * ```
- * :host ::ng-deep {
- * import 'some/lib/containing/font-face';
- * }
- *
- * Similar logic applies to `@page` rules which can contain a particular set of properties,
- * as well as some specific at-rules. Since they can't be encapsulated, we have to strip
- * any scoping selectors from them. For more information: https://www.w3.org/TR/css-page-3
- * ```
- */
- _stripScopingSelectors(cssText) {
- return processRules(cssText, (rule) => {
- const selector = rule.selector
- .replace(_shadowDeepSelectors, ' ')
- .replace(_polyfillHostNoCombinatorRe, ' ');
- return new CssRule(selector, rule.content);
- });
- }
- _safeSelector;
- _shouldScopeIndicator;
- // `isParentSelector` is used to distinguish the selectors which are coming from
- // the initial selector string and any nested selectors, parsed recursively,
- // for example `selector = 'a:where(.one)'` could be the parent, while recursive call
- // would have `selector = '.one'`.
- _scopeSelector({ selector, scopeSelector, hostSelector, isParentSelector = false, }) {
- // Split the selector into independent parts by `,` (comma) unless
- // comma is within parenthesis, for example `:is(.one, two)`.
- // Negative lookup after comma allows not splitting inside nested parenthesis,
- // up to three levels (((,))).
- const selectorSplitRe = / ?,(?!(?:[^)(]*(?:\([^)(]*(?:\([^)(]*(?:\([^)(]*\)[^)(]*)*\)[^)(]*)*\)[^)(]*)*\))) ?/;
- return selector
- .split(selectorSplitRe)
- .map((part) => part.split(_shadowDeepSelectors))
- .map((deepParts) => {
- const [shallowPart, ...otherParts] = deepParts;
- const applyScope = (shallowPart) => {
- if (this._selectorNeedsScoping(shallowPart, scopeSelector)) {
- return this._applySelectorScope({
- selector: shallowPart,
- scopeSelector,
- hostSelector,
- isParentSelector,
- });
- }
- else {
- return shallowPart;
- }
- };
- return [applyScope(shallowPart), ...otherParts].join(' ');
- })
- .join(', ');
- }
- _selectorNeedsScoping(selector, scopeSelector) {
- const re = this._makeScopeMatcher(scopeSelector);
- return !re.test(selector);
- }
- _makeScopeMatcher(scopeSelector) {
- const lre = /\[/g;
- const rre = /\]/g;
- scopeSelector = scopeSelector.replace(lre, '\\[').replace(rre, '\\]');
- return new RegExp('^(' + scopeSelector + ')' + _selectorReSuffix, 'm');
- }
- // scope via name and [is=name]
- _applySimpleSelectorScope(selector, scopeSelector, hostSelector) {
- // In Android browser, the lastIndex is not reset when the regex is used in String.replace()
- _polyfillHostRe.lastIndex = 0;
- if (_polyfillHostRe.test(selector)) {
- const replaceBy = `[${hostSelector}]`;
- let result = selector;
- while (result.match(_polyfillHostNoCombinatorRe)) {
- result = result.replace(_polyfillHostNoCombinatorRe, (_hnc, selector) => {
- return selector.replace(/([^:\)]*)(:*)(.*)/, (_, before, colon, after) => {
- return before + replaceBy + colon + after;
- });
- });
- }
- return result.replace(_polyfillHostRe, replaceBy);
- }
- return scopeSelector + ' ' + selector;
- }
- // return a selector with [name] suffix on each simple selector
- // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] /** @internal */
- _applySelectorScope({ selector, scopeSelector, hostSelector, isParentSelector, }) {
- const isRe = /\[is=([^\]]*)\]/g;
- scopeSelector = scopeSelector.replace(isRe, (_, ...parts) => parts[0]);
- const attrName = `[${scopeSelector}]`;
- const _scopeSelectorPart = (p) => {
- let scopedP = p.trim();
- if (!scopedP) {
- return p;
- }
- if (p.includes(_polyfillHostNoCombinator)) {
- scopedP = this._applySimpleSelectorScope(p, scopeSelector, hostSelector);
- if (!p.match(_polyfillHostNoCombinatorOutsidePseudoFunction)) {
- const [_, before, colon, after] = scopedP.match(/([^:]*)(:*)([\s\S]*)/);
- scopedP = before + attrName + colon + after;
- }
- }
- else {
- // remove :host since it should be unnecessary
- const t = p.replace(_polyfillHostRe, '');
- if (t.length > 0) {
- const matches = t.match(/([^:]*)(:*)([\s\S]*)/);
- if (matches) {
- scopedP = matches[1] + attrName + matches[2] + matches[3];
- }
- }
- }
- return scopedP;
- };
- // Wraps `_scopeSelectorPart()` to not use it directly on selectors with
- // pseudo selector functions like `:where()`. Selectors within pseudo selector
- // functions are recursively sent to `_scopeSelector()`.
- const _pseudoFunctionAwareScopeSelectorPart = (selectorPart) => {
- let scopedPart = '';
- // Collect all outer `:where()` and `:is()` selectors,
- // counting parenthesis to keep nested selectors intact.
- const pseudoSelectorParts = [];
- let pseudoSelectorMatch;
- while ((pseudoSelectorMatch = _cssPrefixWithPseudoSelectorFunction.exec(selectorPart)) !== null) {
- let openedBrackets = 1;
- let index = _cssPrefixWithPseudoSelectorFunction.lastIndex;
- while (index < selectorPart.length) {
- const currentSymbol = selectorPart[index];
- index++;
- if (currentSymbol === '(') {
- openedBrackets++;
- continue;
- }
- if (currentSymbol === ')') {
- openedBrackets--;
- if (openedBrackets === 0) {
- break;
- }
- continue;
- }
- }
- pseudoSelectorParts.push(`${pseudoSelectorMatch[0]}${selectorPart.slice(_cssPrefixWithPseudoSelectorFunction.lastIndex, index)}`);
- _cssPrefixWithPseudoSelectorFunction.lastIndex = index;
- }
- // If selector consists of only `:where()` and `:is()` on the outer level
- // scope those pseudo-selectors individually, otherwise scope the whole
- // selector.
- if (pseudoSelectorParts.join('') === selectorPart) {
- scopedPart = pseudoSelectorParts
- .map((selectorPart) => {
- const [cssPseudoSelectorFunction] = selectorPart.match(_cssPrefixWithPseudoSelectorFunction) ?? [];
- // Unwrap the pseudo selector to scope its contents.
- // For example,
- // - `:where(selectorToScope)` -> `selectorToScope`;
- // - `:is(.foo, .bar)` -> `.foo, .bar`.
- const selectorToScope = selectorPart.slice(cssPseudoSelectorFunction?.length, -1);
- if (selectorToScope.includes(_polyfillHostNoCombinator)) {
- this._shouldScopeIndicator = true;
- }
- const scopedInnerPart = this._scopeSelector({
- selector: selectorToScope,
- scopeSelector,
- hostSelector,
- });
- // Put the result back into the pseudo selector function.
- return `${cssPseudoSelectorFunction}${scopedInnerPart})`;
- })
- .join('');
- }
- else {
- this._shouldScopeIndicator =
- this._shouldScopeIndicator || selectorPart.includes(_polyfillHostNoCombinator);
- scopedPart = this._shouldScopeIndicator ? _scopeSelectorPart(selectorPart) : selectorPart;
- }
- return scopedPart;
- };
- if (isParentSelector) {
- this._safeSelector = new SafeSelector(selector);
- selector = this._safeSelector.content();
- }
- let scopedSelector = '';
- let startIndex = 0;
- let res;
- // Combinators aren't used as a delimiter if they are within parenthesis,
- // for example `:where(.one .two)` stays intact.
- // Similarly to selector separation by comma initially, negative lookahead
- // is used here to not break selectors within nested parenthesis up to three
- // nested layers.
- const sep = /( |>|\+|~(?!=))(?!([^)(]*(?:\([^)(]*(?:\([^)(]*(?:\([^)(]*\)[^)(]*)*\)[^)(]*)*\)[^)(]*)*\)))\s*/g;
- // If a selector appears before :host it should not be shimmed as it
- // matches on ancestor elements and not on elements in the host's shadow
- // `:host-context(div)` is transformed to
- // `-shadowcsshost-no-combinatordiv, div -shadowcsshost-no-combinator`
- // the `div` is not part of the component in the 2nd selectors and should not be scoped.
- // Historically `component-tag:host` was matching the component so we also want to preserve
- // this behavior to avoid breaking legacy apps (it should not match).
- // The behavior should be:
- // - `tag:host` -> `tag[h]` (this is to avoid breaking legacy apps, should not match anything)
- // - `tag :host` -> `tag [h]` (`tag` is not scoped because it's considered part of a
- // `:host-context(tag)`)
- const hasHost = selector.includes(_polyfillHostNoCombinator);
- // Only scope parts after or on the same level as the first `-shadowcsshost-no-combinator`
- // when it is present. The selector has the same level when it is a part of a pseudo
- // selector, like `:where()`, for example `:where(:host, .foo)` would result in `.foo`
- // being scoped.
- if (isParentSelector || this._shouldScopeIndicator) {
- this._shouldScopeIndicator = !hasHost;
- }
- while ((res = sep.exec(selector)) !== null) {
- const separator = res[1];
- // Do not trim the selector, as otherwise this will break sourcemaps
- // when they are defined on multiple lines
- // Example:
- // div,
- // p { color: red}
- const part = selector.slice(startIndex, res.index);
- // A space following an escaped hex value and followed by another hex character
- // (ie: ".\fc ber" for ".über") is not a separator between 2 selectors
- // also keep in mind that backslashes are replaced by a placeholder by SafeSelector
- // These escaped selectors happen for example when esbuild runs with optimization.minify.
- if (part.match(/__esc-ph-(\d+)__/) && selector[res.index + 1]?.match(/[a-fA-F\d]/)) {
- continue;
- }
- const scopedPart = _pseudoFunctionAwareScopeSelectorPart(part);
- scopedSelector += `${scopedPart} ${separator} `;
- startIndex = sep.lastIndex;
- }
- const part = selector.substring(startIndex);
- scopedSelector += _pseudoFunctionAwareScopeSelectorPart(part);
- // replace the placeholders with their original values
- // using values stored inside the `safeSelector` instance.
- return this._safeSelector.restore(scopedSelector);
- }
- _insertPolyfillHostInCssText(selector) {
- return selector
- .replace(_colonHostContextRe, _polyfillHostContext)
- .replace(_colonHostRe, _polyfillHost);
- }
- }
- class SafeSelector {
- placeholders = [];
- index = 0;
- _content;
- constructor(selector) {
- // Replaces attribute selectors with placeholders.
- // The WS in [attr="va lue"] would otherwise be interpreted as a selector separator.
- selector = this._escapeRegexMatches(selector, /(\[[^\]]*\])/g);
- // CSS allows for certain special characters to be used in selectors if they're escaped.
- // E.g. `.foo:blue` won't match a class called `foo:blue`, because the colon denotes a
- // pseudo-class, but writing `.foo\:blue` will match, because the colon was escaped.
- // Replace all escape sequences (`\` followed by a character) with a placeholder so
- // that our handling of pseudo-selectors doesn't mess with them.
- // Escaped characters have a specific placeholder so they can be detected separately.
- selector = selector.replace(/(\\.)/g, (_, keep) => {
- const replaceBy = `__esc-ph-${this.index}__`;
- this.placeholders.push(keep);
- this.index++;
- return replaceBy;
- });
- // Replaces the expression in `:nth-child(2n + 1)` with a placeholder.
- // WS and "+" would otherwise be interpreted as selector separators.
- this._content = selector.replace(/(:nth-[-\w]+)(\([^)]+\))/g, (_, pseudo, exp) => {
- const replaceBy = `__ph-${this.index}__`;
- this.placeholders.push(exp);
- this.index++;
- return pseudo + replaceBy;
- });
- }
- restore(content) {
- return content.replace(/__(?:ph|esc-ph)-(\d+)__/g, (_ph, index) => this.placeholders[+index]);
- }
- content() {
- return this._content;
- }
- /**
- * Replaces all of the substrings that match a regex within a
- * special string (e.g. `__ph-0__`, `__ph-1__`, etc).
- */
- _escapeRegexMatches(content, pattern) {
- return content.replace(pattern, (_, keep) => {
- const replaceBy = `__ph-${this.index}__`;
- this.placeholders.push(keep);
- this.index++;
- return replaceBy;
- });
- }
- }
- const _cssScopedPseudoFunctionPrefix = '(:(where|is)\\()?';
- const _cssPrefixWithPseudoSelectorFunction = /:(where|is)\(/gi;
- const _cssContentNextSelectorRe = /polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim;
- const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim;
- const _cssContentUnscopedRuleRe = /(polyfill-unscoped-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim;
- const _polyfillHost = '-shadowcsshost';
- // note: :host-context pre-processed to -shadowcsshostcontext.
- const _polyfillHostContext = '-shadowcsscontext';
- const _parenSuffix = '(?:\\((' + '(?:\\([^)(]*\\)|[^)(]*)+?' + ')\\))';
- const _cssColonHostRe = new RegExp(_polyfillHost + _parenSuffix + '?([^,{]*)', 'gim');
- // note: :host-context patterns are terminated with `{`, as opposed to :host which
- // is both `{` and `,` because :host-context handles top-level commas differently.
- const _hostContextPattern = _polyfillHostContext + _parenSuffix + '?([^{]*)';
- const _cssColonHostContextReGlobal = new RegExp(`${_cssScopedPseudoFunctionPrefix}(${_hostContextPattern})`, 'gim');
- const _cssColonHostContextRe = new RegExp(_hostContextPattern, 'im');
- const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
- const _polyfillHostNoCombinatorOutsidePseudoFunction = new RegExp(`${_polyfillHostNoCombinator}(?![^(]*\\))`, 'g');
- const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s,]*)/;
- const _shadowDOMSelectorsRe = [
- /::shadow/g,
- /::content/g,
- // Deprecated selectors
- /\/shadow-deep\//g,
- /\/shadow\//g,
- ];
- // The deep combinator is deprecated in the CSS spec
- // Support for `>>>`, `deep`, `::ng-deep` is then also deprecated and will be removed in the future.
- // see https://github.com/angular/angular/pull/17677
- const _shadowDeepSelectors = /(?:>>>)|(?:\/deep\/)|(?:::ng-deep)/g;
- const _selectorReSuffix = '([>\\s~+[.,{:][\\s\\S]*)?$';
- const _polyfillHostRe = /-shadowcsshost/gim;
- const _colonHostRe = /:host/gim;
- const _colonHostContextRe = /:host-context/gim;
- const _newLinesRe = /\r?\n/g;
- const _commentRe = /\/\*[\s\S]*?\*\//g;
- const _commentWithHashRe = /\/\*\s*#\s*source(Mapping)?URL=/g;
- const COMMENT_PLACEHOLDER = '%COMMENT%';
- const _commentWithHashPlaceHolderRe = new RegExp(COMMENT_PLACEHOLDER, 'g');
- const BLOCK_PLACEHOLDER = '%BLOCK%';
- const _ruleRe = new RegExp(`(\\s*(?:${COMMENT_PLACEHOLDER}\\s*)*)([^;\\{\\}]+?)(\\s*)((?:{%BLOCK%}?\\s*;?)|(?:\\s*;))`, 'g');
- const CONTENT_PAIRS = new Map([['{', '}']]);
- const COMMA_IN_PLACEHOLDER = '%COMMA_IN_PLACEHOLDER%';
- const SEMI_IN_PLACEHOLDER = '%SEMI_IN_PLACEHOLDER%';
- const COLON_IN_PLACEHOLDER = '%COLON_IN_PLACEHOLDER%';
- const _cssCommaInPlaceholderReGlobal = new RegExp(COMMA_IN_PLACEHOLDER, 'g');
- const _cssSemiInPlaceholderReGlobal = new RegExp(SEMI_IN_PLACEHOLDER, 'g');
- const _cssColonInPlaceholderReGlobal = new RegExp(COLON_IN_PLACEHOLDER, 'g');
- class CssRule {
- selector;
- content;
- constructor(selector, content) {
- this.selector = selector;
- this.content = content;
- }
- }
- function processRules(input, ruleCallback) {
- const escaped = escapeInStrings(input);
- const inputWithEscapedBlocks = escapeBlocks(escaped, CONTENT_PAIRS, BLOCK_PLACEHOLDER);
- let nextBlockIndex = 0;
- const escapedResult = inputWithEscapedBlocks.escapedString.replace(_ruleRe, (...m) => {
- const selector = m[2];
- let content = '';
- let suffix = m[4];
- let contentPrefix = '';
- if (suffix && suffix.startsWith('{' + BLOCK_PLACEHOLDER)) {
- content = inputWithEscapedBlocks.blocks[nextBlockIndex++];
- suffix = suffix.substring(BLOCK_PLACEHOLDER.length + 1);
- contentPrefix = '{';
- }
- const rule = ruleCallback(new CssRule(selector, content));
- return `${m[1]}${rule.selector}${m[3]}${contentPrefix}${rule.content}${suffix}`;
- });
- return unescapeInStrings(escapedResult);
- }
- class StringWithEscapedBlocks {
- escapedString;
- blocks;
- constructor(escapedString, blocks) {
- this.escapedString = escapedString;
- this.blocks = blocks;
- }
- }
- function escapeBlocks(input, charPairs, placeholder) {
- const resultParts = [];
- const escapedBlocks = [];
- let openCharCount = 0;
- let nonBlockStartIndex = 0;
- let blockStartIndex = -1;
- let openChar;
- let closeChar;
- for (let i = 0; i < input.length; i++) {
- const char = input[i];
- if (char === '\\') {
- i++;
- }
- else if (char === closeChar) {
- openCharCount--;
- if (openCharCount === 0) {
- escapedBlocks.push(input.substring(blockStartIndex, i));
- resultParts.push(placeholder);
- nonBlockStartIndex = i;
- blockStartIndex = -1;
- openChar = closeChar = undefined;
- }
- }
- else if (char === openChar) {
- openCharCount++;
- }
- else if (openCharCount === 0 && charPairs.has(char)) {
- openChar = char;
- closeChar = charPairs.get(char);
- openCharCount = 1;
- blockStartIndex = i + 1;
- resultParts.push(input.substring(nonBlockStartIndex, blockStartIndex));
- }
- }
- if (blockStartIndex !== -1) {
- escapedBlocks.push(input.substring(blockStartIndex));
- resultParts.push(placeholder);
- }
- else {
- resultParts.push(input.substring(nonBlockStartIndex));
- }
- return new StringWithEscapedBlocks(resultParts.join(''), escapedBlocks);
- }
- /**
- * Object containing as keys characters that should be substituted by placeholders
- * when found in strings during the css text parsing, and as values the respective
- * placeholders
- */
- const ESCAPE_IN_STRING_MAP = {
- ';': SEMI_IN_PLACEHOLDER,
- ',': COMMA_IN_PLACEHOLDER,
- ':': COLON_IN_PLACEHOLDER,
- };
- /**
- * Parse the provided css text and inside strings (meaning, inside pairs of unescaped single or
- * double quotes) replace specific characters with their respective placeholders as indicated
- * by the `ESCAPE_IN_STRING_MAP` map.
- *
- * For example convert the text
- * `animation: "my-anim:at\"ion" 1s;`
- * to
- * `animation: "my-anim%COLON_IN_PLACEHOLDER%at\"ion" 1s;`
- *
- * This is necessary in order to remove the meaning of some characters when found inside strings
- * (for example `;` indicates the end of a css declaration, `,` the sequence of values and `:` the
- * division between property and value during a declaration, none of these meanings apply when such
- * characters are within strings and so in order to prevent parsing issues they need to be replaced
- * with placeholder text for the duration of the css manipulation process).
- *
- * @param input the original css text.
- *
- * @returns the css text with specific characters in strings replaced by placeholders.
- **/
- function escapeInStrings(input) {
- let result = input;
- let currentQuoteChar = null;
- for (let i = 0; i < result.length; i++) {
- const char = result[i];
- if (char === '\\') {
- i++;
- }
- else {
- if (currentQuoteChar !== null) {
- // index i is inside a quoted sub-string
- if (char === currentQuoteChar) {
- currentQuoteChar = null;
- }
- else {
- const placeholder = ESCAPE_IN_STRING_MAP[char];
- if (placeholder) {
- result = `${result.substr(0, i)}${placeholder}${result.substr(i + 1)}`;
- i += placeholder.length - 1;
- }
- }
- }
- else if (char === "'" || char === '"') {
- currentQuoteChar = char;
- }
- }
- }
- return result;
- }
- /**
- * Replace in a string all occurrences of keys in the `ESCAPE_IN_STRING_MAP` map with their
- * original representation, this is simply used to revert the changes applied by the
- * escapeInStrings function.
- *
- * For example it reverts the text:
- * `animation: "my-anim%COLON_IN_PLACEHOLDER%at\"ion" 1s;`
- * to it's original form of:
- * `animation: "my-anim:at\"ion" 1s;`
- *
- * Note: For the sake of simplicity this function does not check that the placeholders are
- * actually inside strings as it would anyway be extremely unlikely to find them outside of strings.
- *
- * @param input the css text containing the placeholders.
- *
- * @returns the css text without the placeholders.
- */
- function unescapeInStrings(input) {
- let result = input.replace(_cssCommaInPlaceholderReGlobal, ',');
- result = result.replace(_cssSemiInPlaceholderReGlobal, ';');
- result = result.replace(_cssColonInPlaceholderReGlobal, ':');
- return result;
- }
- /**
- * Unescape all quotes present in a string, but only if the string was actually already
- * quoted.
- *
- * This generates a "canonical" representation of strings which can be used to match strings
- * which would otherwise only differ because of differently escaped quotes.
- *
- * For example it converts the string (assumed to be quoted):
- * `this \\"is\\" a \\'\\\\'test`
- * to:
- * `this "is" a '\\\\'test`
- * (note that the latter backslashes are not removed as they are not actually escaping the single
- * quote)
- *
- *
- * @param input the string possibly containing escaped quotes.
- * @param isQuoted boolean indicating whether the string was quoted inside a bigger string (if not
- * then it means that it doesn't represent an inner string and thus no unescaping is required)
- *
- * @returns the string in the "canonical" representation without escaped quotes.
- */
- function unescapeQuotes(str, isQuoted) {
- return !isQuoted ? str : str.replace(/((?:^|[^\\])(?:\\\\)*)\\(?=['"])/g, '$1');
- }
- /**
- * Combine the `contextSelectors` with the `hostMarker` and the `otherSelectors`
- * to create a selector that matches the same as `:host-context()`.
- *
- * Given a single context selector `A` we need to output selectors that match on the host and as an
- * ancestor of the host:
- *
- * ```
- * A <hostMarker>, A<hostMarker> {}
- * ```
- *
- * When there is more than one context selector we also have to create combinations of those
- * selectors with each other. For example if there are `A` and `B` selectors the output is:
- *
- * ```
- * AB<hostMarker>, AB <hostMarker>, A B<hostMarker>,
- * B A<hostMarker>, A B <hostMarker>, B A <hostMarker> {}
- * ```
- *
- * And so on...
- *
- * @param contextSelectors an array of context selectors that will be combined.
- * @param otherSelectors the rest of the selectors that are not context selectors.
- */
- function _combineHostContextSelectors(contextSelectors, otherSelectors, pseudoPrefix = '') {
- const hostMarker = _polyfillHostNoCombinator;
- _polyfillHostRe.lastIndex = 0; // reset the regex to ensure we get an accurate test
- const otherSelectorsHasHost = _polyfillHostRe.test(otherSelectors);
- // If there are no context selectors then just output a host marker
- if (contextSelectors.length === 0) {
- return hostMarker + otherSelectors;
- }
- const combined = [contextSelectors.pop() || ''];
- while (contextSelectors.length > 0) {
- const length = combined.length;
- const contextSelector = contextSelectors.pop();
- for (let i = 0; i < length; i++) {
- const previousSelectors = combined[i];
- // Add the new selector as a descendant of the previous selectors
- combined[length * 2 + i] = previousSelectors + ' ' + contextSelector;
- // Add the new selector as an ancestor of the previous selectors
- combined[length + i] = contextSelector + ' ' + previousSelectors;
- // Add the new selector to act on the same element as the previous selectors
- combined[i] = contextSelector + previousSelectors;
- }
- }
- // Finally connect the selector to the `hostMarker`s: either acting directly on the host
- // (A<hostMarker>) or as an ancestor (A <hostMarker>).
- return combined
- .map((s) => otherSelectorsHasHost
- ? `${pseudoPrefix}${s}${otherSelectors}`
- : `${pseudoPrefix}${s}${hostMarker}${otherSelectors}, ${pseudoPrefix}${s} ${hostMarker}${otherSelectors}`)
- .join(',');
- }
- /**
- * Mutate the given `groups` array so that there are `multiples` clones of the original array
- * stored.
- *
- * For example `repeatGroups([a, b], 3)` will result in `[a, b, a, b, a, b]` - but importantly the
- * newly added groups will be clones of the original.
- *
- * @param groups An array of groups of strings that will be repeated. This array is mutated
- * in-place.
- * @param multiples The number of times the current groups should appear.
- */
- function repeatGroups(groups, multiples) {
- const length = groups.length;
- for (let i = 1; i < multiples; i++) {
- for (let j = 0; j < length; j++) {
- groups[j + i * length] = groups[j].slice(0);
- }
- }
- }
- /**
- * Distinguishes different kinds of IR operations.
- *
- * Includes both creation and update operations.
- */
- var OpKind;
- (function (OpKind) {
- /**
- * A special operation type which is used to represent the beginning and end nodes of a linked
- * list of operations.
- */
- OpKind[OpKind["ListEnd"] = 0] = "ListEnd";
- /**
- * An operation which wraps an output AST statement.
- */
- OpKind[OpKind["Statement"] = 1] = "Statement";
- /**
- * An operation which declares and initializes a `SemanticVariable`.
- */
- OpKind[OpKind["Variable"] = 2] = "Variable";
- /**
- * An operation to begin rendering of an element.
- */
- OpKind[OpKind["ElementStart"] = 3] = "ElementStart";
- /**
- * An operation to render an element with no children.
- */
- OpKind[OpKind["Element"] = 4] = "Element";
- /**
- * An operation which declares an embedded view.
- */
- OpKind[OpKind["Template"] = 5] = "Template";
- /**
- * An operation to end rendering of an element previously started with `ElementStart`.
- */
- OpKind[OpKind["ElementEnd"] = 6] = "ElementEnd";
- /**
- * An operation to begin an `ng-container`.
- */
- OpKind[OpKind["ContainerStart"] = 7] = "ContainerStart";
- /**
- * An operation for an `ng-container` with no children.
- */
- OpKind[OpKind["Container"] = 8] = "Container";
- /**
- * An operation to end an `ng-container`.
- */
- OpKind[OpKind["ContainerEnd"] = 9] = "ContainerEnd";
- /**
- * An operation disable binding for subsequent elements, which are descendants of a non-bindable
- * node.
- */
- OpKind[OpKind["DisableBindings"] = 10] = "DisableBindings";
- /**
- * An op to conditionally render a template.
- */
- OpKind[OpKind["Conditional"] = 11] = "Conditional";
- /**
- * An operation to re-enable binding, after it was previously disabled.
- */
- OpKind[OpKind["EnableBindings"] = 12] = "EnableBindings";
- /**
- * An operation to render a text node.
- */
- OpKind[OpKind["Text"] = 13] = "Text";
- /**
- * An operation declaring an event listener for an element.
- */
- OpKind[OpKind["Listener"] = 14] = "Listener";
- /**
- * An operation to interpolate text into a text node.
- */
- OpKind[OpKind["InterpolateText"] = 15] = "InterpolateText";
- /**
- * An intermediate binding op, that has not yet been processed into an individual property,
- * attribute, style, etc.
- */
- OpKind[OpKind["Binding"] = 16] = "Binding";
- /**
- * An operation to bind an expression to a property of an element.
- */
- OpKind[OpKind["Property"] = 17] = "Property";
- /**
- * An operation to bind an expression to a style property of an element.
- */
- OpKind[OpKind["StyleProp"] = 18] = "StyleProp";
- /**
- * An operation to bind an expression to a class property of an element.
- */
- OpKind[OpKind["ClassProp"] = 19] = "ClassProp";
- /**
- * An operation to bind an expression to the styles of an element.
- */
- OpKind[OpKind["StyleMap"] = 20] = "StyleMap";
- /**
- * An operation to bind an expression to the classes of an element.
- */
- OpKind[OpKind["ClassMap"] = 21] = "ClassMap";
- /**
- * An operation to advance the runtime's implicit slot context during the update phase of a view.
- */
- OpKind[OpKind["Advance"] = 22] = "Advance";
- /**
- * An operation to instantiate a pipe.
- */
- OpKind[OpKind["Pipe"] = 23] = "Pipe";
- /**
- * An operation to associate an attribute with an element.
- */
- OpKind[OpKind["Attribute"] = 24] = "Attribute";
- /**
- * An attribute that has been extracted for inclusion in the consts array.
- */
- OpKind[OpKind["ExtractedAttribute"] = 25] = "ExtractedAttribute";
- /**
- * An operation that configures a `@defer` block.
- */
- OpKind[OpKind["Defer"] = 26] = "Defer";
- /**
- * An operation that controls when a `@defer` loads.
- */
- OpKind[OpKind["DeferOn"] = 27] = "DeferOn";
- /**
- * An operation that controls when a `@defer` loads, using a custom expression as the condition.
- */
- OpKind[OpKind["DeferWhen"] = 28] = "DeferWhen";
- /**
- * An i18n message that has been extracted for inclusion in the consts array.
- */
- OpKind[OpKind["I18nMessage"] = 29] = "I18nMessage";
- /**
- * A host binding property.
- */
- OpKind[OpKind["HostProperty"] = 30] = "HostProperty";
- /**
- * A namespace change, which causes the subsequent elements to be processed as either HTML or SVG.
- */
- OpKind[OpKind["Namespace"] = 31] = "Namespace";
- /**
- * Configure a content projeciton definition for the view.
- */
- OpKind[OpKind["ProjectionDef"] = 32] = "ProjectionDef";
- /**
- * Create a content projection slot.
- */
- OpKind[OpKind["Projection"] = 33] = "Projection";
- /**
- * Create a repeater creation instruction op.
- */
- OpKind[OpKind["RepeaterCreate"] = 34] = "RepeaterCreate";
- /**
- * An update up for a repeater.
- */
- OpKind[OpKind["Repeater"] = 35] = "Repeater";
- /**
- * An operation to bind an expression to the property side of a two-way binding.
- */
- OpKind[OpKind["TwoWayProperty"] = 36] = "TwoWayProperty";
- /**
- * An operation declaring the event side of a two-way binding.
- */
- OpKind[OpKind["TwoWayListener"] = 37] = "TwoWayListener";
- /**
- * A creation-time operation that initializes the slot for a `@let` declaration.
- */
- OpKind[OpKind["DeclareLet"] = 38] = "DeclareLet";
- /**
- * An update-time operation that stores the current value of a `@let` declaration.
- */
- OpKind[OpKind["StoreLet"] = 39] = "StoreLet";
- /**
- * The start of an i18n block.
- */
- OpKind[OpKind["I18nStart"] = 40] = "I18nStart";
- /**
- * A self-closing i18n on a single element.
- */
- OpKind[OpKind["I18n"] = 41] = "I18n";
- /**
- * The end of an i18n block.
- */
- OpKind[OpKind["I18nEnd"] = 42] = "I18nEnd";
- /**
- * An expression in an i18n message.
- */
- OpKind[OpKind["I18nExpression"] = 43] = "I18nExpression";
- /**
- * An instruction that applies a set of i18n expressions.
- */
- OpKind[OpKind["I18nApply"] = 44] = "I18nApply";
- /**
- * An instruction to create an ICU expression.
- */
- OpKind[OpKind["IcuStart"] = 45] = "IcuStart";
- /**
- * An instruction to update an ICU expression.
- */
- OpKind[OpKind["IcuEnd"] = 46] = "IcuEnd";
- /**
- * An instruction representing a placeholder in an ICU expression.
- */
- OpKind[OpKind["IcuPlaceholder"] = 47] = "IcuPlaceholder";
- /**
- * An i18n context containing information needed to generate an i18n message.
- */
- OpKind[OpKind["I18nContext"] = 48] = "I18nContext";
- /**
- * A creation op that corresponds to i18n attributes on an element.
- */
- OpKind[OpKind["I18nAttributes"] = 49] = "I18nAttributes";
- /**
- * Creation op that attaches the location at which an element was defined in a template to it.
- */
- OpKind[OpKind["SourceLocation"] = 50] = "SourceLocation";
- })(OpKind || (OpKind = {}));
- /**
- * Distinguishes different kinds of IR expressions.
- */
- var ExpressionKind;
- (function (ExpressionKind) {
- /**
- * Read of a variable in a lexical scope.
- */
- ExpressionKind[ExpressionKind["LexicalRead"] = 0] = "LexicalRead";
- /**
- * A reference to the current view context.
- */
- ExpressionKind[ExpressionKind["Context"] = 1] = "Context";
- /**
- * A reference to the view context, for use inside a track function.
- */
- ExpressionKind[ExpressionKind["TrackContext"] = 2] = "TrackContext";
- /**
- * Read of a variable declared in a `VariableOp`.
- */
- ExpressionKind[ExpressionKind["ReadVariable"] = 3] = "ReadVariable";
- /**
- * Runtime operation to navigate to the next view context in the view hierarchy.
- */
- ExpressionKind[ExpressionKind["NextContext"] = 4] = "NextContext";
- /**
- * Runtime operation to retrieve the value of a local reference.
- */
- ExpressionKind[ExpressionKind["Reference"] = 5] = "Reference";
- /**
- * A call storing the value of a `@let` declaration.
- */
- ExpressionKind[ExpressionKind["StoreLet"] = 6] = "StoreLet";
- /**
- * A reference to a `@let` declaration read from the context view.
- */
- ExpressionKind[ExpressionKind["ContextLetReference"] = 7] = "ContextLetReference";
- /**
- * Runtime operation to snapshot the current view context.
- */
- ExpressionKind[ExpressionKind["GetCurrentView"] = 8] = "GetCurrentView";
- /**
- * Runtime operation to restore a snapshotted view.
- */
- ExpressionKind[ExpressionKind["RestoreView"] = 9] = "RestoreView";
- /**
- * Runtime operation to reset the current view context after `RestoreView`.
- */
- ExpressionKind[ExpressionKind["ResetView"] = 10] = "ResetView";
- /**
- * Defines and calls a function with change-detected arguments.
- */
- ExpressionKind[ExpressionKind["PureFunctionExpr"] = 11] = "PureFunctionExpr";
- /**
- * Indicates a positional parameter to a pure function definition.
- */
- ExpressionKind[ExpressionKind["PureFunctionParameterExpr"] = 12] = "PureFunctionParameterExpr";
- /**
- * Binding to a pipe transformation.
- */
- ExpressionKind[ExpressionKind["PipeBinding"] = 13] = "PipeBinding";
- /**
- * Binding to a pipe transformation with a variable number of arguments.
- */
- ExpressionKind[ExpressionKind["PipeBindingVariadic"] = 14] = "PipeBindingVariadic";
- /*
- * A safe property read requiring expansion into a null check.
- */
- ExpressionKind[ExpressionKind["SafePropertyRead"] = 15] = "SafePropertyRead";
- /**
- * A safe keyed read requiring expansion into a null check.
- */
- ExpressionKind[ExpressionKind["SafeKeyedRead"] = 16] = "SafeKeyedRead";
- /**
- * A safe function call requiring expansion into a null check.
- */
- ExpressionKind[ExpressionKind["SafeInvokeFunction"] = 17] = "SafeInvokeFunction";
- /**
- * An intermediate expression that will be expanded from a safe read into an explicit ternary.
- */
- ExpressionKind[ExpressionKind["SafeTernaryExpr"] = 18] = "SafeTernaryExpr";
- /**
- * An empty expression that will be stipped before generating the final output.
- */
- ExpressionKind[ExpressionKind["EmptyExpr"] = 19] = "EmptyExpr";
- /*
- * An assignment to a temporary variable.
- */
- ExpressionKind[ExpressionKind["AssignTemporaryExpr"] = 20] = "AssignTemporaryExpr";
- /**
- * A reference to a temporary variable.
- */
- ExpressionKind[ExpressionKind["ReadTemporaryExpr"] = 21] = "ReadTemporaryExpr";
- /**
- * An expression that will cause a literal slot index to be emitted.
- */
- ExpressionKind[ExpressionKind["SlotLiteralExpr"] = 22] = "SlotLiteralExpr";
- /**
- * A test expression for a conditional op.
- */
- ExpressionKind[ExpressionKind["ConditionalCase"] = 23] = "ConditionalCase";
- /**
- * An expression that will be automatically extracted to the component const array.
- */
- ExpressionKind[ExpressionKind["ConstCollected"] = 24] = "ConstCollected";
- /**
- * Operation that sets the value of a two-way binding.
- */
- ExpressionKind[ExpressionKind["TwoWayBindingSet"] = 25] = "TwoWayBindingSet";
- })(ExpressionKind || (ExpressionKind = {}));
- var VariableFlags;
- (function (VariableFlags) {
- VariableFlags[VariableFlags["None"] = 0] = "None";
- /**
- * Always inline this variable, regardless of the number of times it's used.
- * An `AlwaysInline` variable may not depend on context, because doing so may cause side effects
- * that are illegal when multi-inlined. (The optimizer will enforce this constraint.)
- */
- VariableFlags[VariableFlags["AlwaysInline"] = 1] = "AlwaysInline";
- })(VariableFlags || (VariableFlags = {}));
- /**
- * Distinguishes between different kinds of `SemanticVariable`s.
- */
- var SemanticVariableKind;
- (function (SemanticVariableKind) {
- /**
- * Represents the context of a particular view.
- */
- SemanticVariableKind[SemanticVariableKind["Context"] = 0] = "Context";
- /**
- * Represents an identifier declared in the lexical scope of a view.
- */
- SemanticVariableKind[SemanticVariableKind["Identifier"] = 1] = "Identifier";
- /**
- * Represents a saved state that can be used to restore a view in a listener handler function.
- */
- SemanticVariableKind[SemanticVariableKind["SavedView"] = 2] = "SavedView";
- /**
- * An alias generated by a special embedded view type (e.g. a `@for` block).
- */
- SemanticVariableKind[SemanticVariableKind["Alias"] = 3] = "Alias";
- })(SemanticVariableKind || (SemanticVariableKind = {}));
- /**
- * Whether to compile in compatibilty mode. In compatibility mode, the template pipeline will
- * attempt to match the output of `TemplateDefinitionBuilder` as exactly as possible, at the cost
- * of producing quirky or larger code in some cases.
- */
- var CompatibilityMode;
- (function (CompatibilityMode) {
- CompatibilityMode[CompatibilityMode["Normal"] = 0] = "Normal";
- CompatibilityMode[CompatibilityMode["TemplateDefinitionBuilder"] = 1] = "TemplateDefinitionBuilder";
- })(CompatibilityMode || (CompatibilityMode = {}));
- /**
- * Enumeration of the types of attributes which can be applied to an element.
- */
- var BindingKind;
- (function (BindingKind) {
- /**
- * Static attributes.
- */
- BindingKind[BindingKind["Attribute"] = 0] = "Attribute";
- /**
- * Class bindings.
- */
- BindingKind[BindingKind["ClassName"] = 1] = "ClassName";
- /**
- * Style bindings.
- */
- BindingKind[BindingKind["StyleProperty"] = 2] = "StyleProperty";
- /**
- * Dynamic property bindings.
- */
- BindingKind[BindingKind["Property"] = 3] = "Property";
- /**
- * Property or attribute bindings on a template.
- */
- BindingKind[BindingKind["Template"] = 4] = "Template";
- /**
- * Internationalized attributes.
- */
- BindingKind[BindingKind["I18n"] = 5] = "I18n";
- /**
- * Animation property bindings.
- */
- BindingKind[BindingKind["Animation"] = 6] = "Animation";
- /**
- * Property side of a two-way binding.
- */
- BindingKind[BindingKind["TwoWayProperty"] = 7] = "TwoWayProperty";
- })(BindingKind || (BindingKind = {}));
- /**
- * Enumeration of possible times i18n params can be resolved.
- */
- var I18nParamResolutionTime;
- (function (I18nParamResolutionTime) {
- /**
- * Param is resolved at message creation time. Most params should be resolved at message creation
- * time. However, ICU params need to be handled in post-processing.
- */
- I18nParamResolutionTime[I18nParamResolutionTime["Creation"] = 0] = "Creation";
- /**
- * Param is resolved during post-processing. This should be used for params whose value comes from
- * an ICU.
- */
- I18nParamResolutionTime[I18nParamResolutionTime["Postproccessing"] = 1] = "Postproccessing";
- })(I18nParamResolutionTime || (I18nParamResolutionTime = {}));
- /**
- * The contexts in which an i18n expression can be used.
- */
- var I18nExpressionFor;
- (function (I18nExpressionFor) {
- /**
- * This expression is used as a value (i.e. inside an i18n block).
- */
- I18nExpressionFor[I18nExpressionFor["I18nText"] = 0] = "I18nText";
- /**
- * This expression is used in a binding.
- */
- I18nExpressionFor[I18nExpressionFor["I18nAttribute"] = 1] = "I18nAttribute";
- })(I18nExpressionFor || (I18nExpressionFor = {}));
- /**
- * Flags that describe what an i18n param value. These determine how the value is serialized into
- * the final map.
- */
- var I18nParamValueFlags;
- (function (I18nParamValueFlags) {
- I18nParamValueFlags[I18nParamValueFlags["None"] = 0] = "None";
- /**
- * This value represents an element tag.
- */
- I18nParamValueFlags[I18nParamValueFlags["ElementTag"] = 1] = "ElementTag";
- /**
- * This value represents a template tag.
- */
- I18nParamValueFlags[I18nParamValueFlags["TemplateTag"] = 2] = "TemplateTag";
- /**
- * This value represents the opening of a tag.
- */
- I18nParamValueFlags[I18nParamValueFlags["OpenTag"] = 4] = "OpenTag";
- /**
- * This value represents the closing of a tag.
- */
- I18nParamValueFlags[I18nParamValueFlags["CloseTag"] = 8] = "CloseTag";
- /**
- * This value represents an i18n expression index.
- */
- I18nParamValueFlags[I18nParamValueFlags["ExpressionIndex"] = 16] = "ExpressionIndex";
- })(I18nParamValueFlags || (I18nParamValueFlags = {}));
- /**
- * Whether the active namespace is HTML, MathML, or SVG mode.
- */
- var Namespace;
- (function (Namespace) {
- Namespace[Namespace["HTML"] = 0] = "HTML";
- Namespace[Namespace["SVG"] = 1] = "SVG";
- Namespace[Namespace["Math"] = 2] = "Math";
- })(Namespace || (Namespace = {}));
- /**
- * The type of a `@defer` trigger, for use in the ir.
- */
- var DeferTriggerKind;
- (function (DeferTriggerKind) {
- DeferTriggerKind[DeferTriggerKind["Idle"] = 0] = "Idle";
- DeferTriggerKind[DeferTriggerKind["Immediate"] = 1] = "Immediate";
- DeferTriggerKind[DeferTriggerKind["Timer"] = 2] = "Timer";
- DeferTriggerKind[DeferTriggerKind["Hover"] = 3] = "Hover";
- DeferTriggerKind[DeferTriggerKind["Interaction"] = 4] = "Interaction";
- DeferTriggerKind[DeferTriggerKind["Viewport"] = 5] = "Viewport";
- DeferTriggerKind[DeferTriggerKind["Never"] = 6] = "Never";
- })(DeferTriggerKind || (DeferTriggerKind = {}));
- /**
- * Kinds of i18n contexts. They can be created because of root i18n blocks, or ICUs.
- */
- var I18nContextKind;
- (function (I18nContextKind) {
- I18nContextKind[I18nContextKind["RootI18n"] = 0] = "RootI18n";
- I18nContextKind[I18nContextKind["Icu"] = 1] = "Icu";
- I18nContextKind[I18nContextKind["Attr"] = 2] = "Attr";
- })(I18nContextKind || (I18nContextKind = {}));
- var TemplateKind;
- (function (TemplateKind) {
- TemplateKind[TemplateKind["NgTemplate"] = 0] = "NgTemplate";
- TemplateKind[TemplateKind["Structural"] = 1] = "Structural";
- TemplateKind[TemplateKind["Block"] = 2] = "Block";
- })(TemplateKind || (TemplateKind = {}));
- /**
- * Marker symbol for `ConsumesSlotOpTrait`.
- */
- const ConsumesSlot = Symbol('ConsumesSlot');
- /**
- * Marker symbol for `DependsOnSlotContextOpTrait`.
- */
- const DependsOnSlotContext = Symbol('DependsOnSlotContext');
- /**
- * Marker symbol for `ConsumesVars` trait.
- */
- const ConsumesVarsTrait = Symbol('ConsumesVars');
- /**
- * Marker symbol for `UsesVarOffset` trait.
- */
- const UsesVarOffset = Symbol('UsesVarOffset');
- /**
- * Default values for most `ConsumesSlotOpTrait` fields (used with the spread operator to initialize
- * implementors of the trait).
- */
- const TRAIT_CONSUMES_SLOT = {
- [ConsumesSlot]: true,
- numSlotsUsed: 1,
- };
- /**
- * Default values for most `DependsOnSlotContextOpTrait` fields (used with the spread operator to
- * initialize implementors of the trait).
- */
- const TRAIT_DEPENDS_ON_SLOT_CONTEXT = {
- [DependsOnSlotContext]: true,
- };
- /**
- * Default values for `UsesVars` fields (used with the spread operator to initialize
- * implementors of the trait).
- */
- const TRAIT_CONSUMES_VARS = {
- [ConsumesVarsTrait]: true,
- };
- /**
- * Test whether an operation implements `ConsumesSlotOpTrait`.
- */
- function hasConsumesSlotTrait(op) {
- return op[ConsumesSlot] === true;
- }
- function hasDependsOnSlotContextTrait(value) {
- return value[DependsOnSlotContext] === true;
- }
- function hasConsumesVarsTrait(value) {
- return value[ConsumesVarsTrait] === true;
- }
- /**
- * Test whether an expression implements `UsesVarOffsetTrait`.
- */
- function hasUsesVarOffsetTrait(expr) {
- return expr[UsesVarOffset] === true;
- }
- /**
- * Create a `StatementOp`.
- */
- function createStatementOp(statement) {
- return {
- kind: OpKind.Statement,
- statement,
- ...NEW_OP,
- };
- }
- /**
- * Create a `VariableOp`.
- */
- function createVariableOp(xref, variable, initializer, flags) {
- return {
- kind: OpKind.Variable,
- xref,
- variable,
- initializer,
- flags,
- ...NEW_OP,
- };
- }
- /**
- * Static structure shared by all operations.
- *
- * Used as a convenience via the spread operator (`...NEW_OP`) when creating new operations, and
- * ensures the fields are always in the same order.
- */
- const NEW_OP = {
- debugListId: null,
- prev: null,
- next: null,
- };
- /**
- * Create an `InterpolationTextOp`.
- */
- function createInterpolateTextOp(xref, interpolation, sourceSpan) {
- return {
- kind: OpKind.InterpolateText,
- target: xref,
- interpolation,
- sourceSpan,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- ...NEW_OP,
- };
- }
- class Interpolation {
- strings;
- expressions;
- i18nPlaceholders;
- constructor(strings, expressions, i18nPlaceholders) {
- this.strings = strings;
- this.expressions = expressions;
- this.i18nPlaceholders = i18nPlaceholders;
- if (i18nPlaceholders.length !== 0 && i18nPlaceholders.length !== expressions.length) {
- throw new Error(`Expected ${expressions.length} placeholders to match interpolation expression count, but got ${i18nPlaceholders.length}`);
- }
- }
- }
- /**
- * Create a `BindingOp`, not yet transformed into a particular type of binding.
- */
- function createBindingOp(target, kind, name, expression, unit, securityContext, isTextAttribute, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
- return {
- kind: OpKind.Binding,
- bindingKind: kind,
- target,
- name,
- expression,
- unit,
- securityContext,
- isTextAttribute,
- isStructuralTemplateAttribute,
- templateKind,
- i18nContext: null,
- i18nMessage,
- sourceSpan,
- ...NEW_OP,
- };
- }
- /**
- * Create a `PropertyOp`.
- */
- function createPropertyOp(target, name, expression, isAnimationTrigger, securityContext, isStructuralTemplateAttribute, templateKind, i18nContext, i18nMessage, sourceSpan) {
- return {
- kind: OpKind.Property,
- target,
- name,
- expression,
- isAnimationTrigger,
- securityContext,
- sanitizer: null,
- isStructuralTemplateAttribute,
- templateKind,
- i18nContext,
- i18nMessage,
- sourceSpan,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- ...NEW_OP,
- };
- }
- /**
- * Create a `TwoWayPropertyOp`.
- */
- function createTwoWayPropertyOp(target, name, expression, securityContext, isStructuralTemplateAttribute, templateKind, i18nContext, i18nMessage, sourceSpan) {
- return {
- kind: OpKind.TwoWayProperty,
- target,
- name,
- expression,
- securityContext,
- sanitizer: null,
- isStructuralTemplateAttribute,
- templateKind,
- i18nContext,
- i18nMessage,
- sourceSpan,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- ...NEW_OP,
- };
- }
- /** Create a `StylePropOp`. */
- function createStylePropOp(xref, name, expression, unit, sourceSpan) {
- return {
- kind: OpKind.StyleProp,
- target: xref,
- name,
- expression,
- unit,
- sourceSpan,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- ...NEW_OP,
- };
- }
- /**
- * Create a `ClassPropOp`.
- */
- function createClassPropOp(xref, name, expression, sourceSpan) {
- return {
- kind: OpKind.ClassProp,
- target: xref,
- name,
- expression,
- sourceSpan,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- ...NEW_OP,
- };
- }
- /** Create a `StyleMapOp`. */
- function createStyleMapOp(xref, expression, sourceSpan) {
- return {
- kind: OpKind.StyleMap,
- target: xref,
- expression,
- sourceSpan,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- ...NEW_OP,
- };
- }
- /**
- * Create a `ClassMapOp`.
- */
- function createClassMapOp(xref, expression, sourceSpan) {
- return {
- kind: OpKind.ClassMap,
- target: xref,
- expression,
- sourceSpan,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- ...NEW_OP,
- };
- }
- /**
- * Create an `AttributeOp`.
- */
- function createAttributeOp(target, namespace, name, expression, securityContext, isTextAttribute, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
- return {
- kind: OpKind.Attribute,
- target,
- namespace,
- name,
- expression,
- securityContext,
- sanitizer: null,
- isTextAttribute,
- isStructuralTemplateAttribute,
- templateKind,
- i18nContext: null,
- i18nMessage,
- sourceSpan,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- ...NEW_OP,
- };
- }
- /**
- * Create an `AdvanceOp`.
- */
- function createAdvanceOp(delta, sourceSpan) {
- return {
- kind: OpKind.Advance,
- delta,
- sourceSpan,
- ...NEW_OP,
- };
- }
- /**
- * Create a conditional op, which will display an embedded view according to a condtion.
- */
- function createConditionalOp(target, test, conditions, sourceSpan) {
- return {
- kind: OpKind.Conditional,
- target,
- test,
- conditions,
- processed: null,
- sourceSpan,
- contextValue: null,
- ...NEW_OP,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- };
- }
- function createRepeaterOp(repeaterCreate, targetSlot, collection, sourceSpan) {
- return {
- kind: OpKind.Repeater,
- target: repeaterCreate,
- targetSlot,
- collection,
- sourceSpan,
- ...NEW_OP,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- };
- }
- function createDeferWhenOp(target, expr, modifier, sourceSpan) {
- return {
- kind: OpKind.DeferWhen,
- target,
- expr,
- modifier,
- sourceSpan,
- ...NEW_OP,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- };
- }
- /**
- * Create an i18n expression op.
- */
- function createI18nExpressionOp(context, target, i18nOwner, handle, expression, icuPlaceholder, i18nPlaceholder, resolutionTime, usage, name, sourceSpan) {
- return {
- kind: OpKind.I18nExpression,
- context,
- target,
- i18nOwner,
- handle,
- expression,
- icuPlaceholder,
- i18nPlaceholder,
- resolutionTime,
- usage,
- name,
- sourceSpan,
- ...NEW_OP,
- ...TRAIT_CONSUMES_VARS,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- };
- }
- /**
- * Creates an op to apply i18n expression ops.
- */
- function createI18nApplyOp(owner, handle, sourceSpan) {
- return {
- kind: OpKind.I18nApply,
- owner,
- handle,
- sourceSpan,
- ...NEW_OP,
- };
- }
- /**
- * Creates a `StoreLetOp`.
- */
- function createStoreLetOp(target, declaredName, value, sourceSpan) {
- return {
- kind: OpKind.StoreLet,
- target,
- declaredName,
- value,
- sourceSpan,
- ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
- ...TRAIT_CONSUMES_VARS,
- ...NEW_OP,
- };
- }
- /**
- * Check whether a given `o.Expression` is a logical IR expression type.
- */
- function isIrExpression(expr) {
- return expr instanceof ExpressionBase;
- }
- /**
- * Base type used for all logical IR expressions.
- */
- class ExpressionBase extends Expression {
- constructor(sourceSpan = null) {
- super(null, sourceSpan);
- }
- }
- /**
- * Logical expression representing a lexical read of a variable name.
- */
- class LexicalReadExpr extends ExpressionBase {
- name;
- kind = ExpressionKind.LexicalRead;
- constructor(name) {
- super();
- this.name = name;
- }
- visitExpression(visitor, context) { }
- isEquivalent(other) {
- // We assume that the lexical reads are in the same context, which must be true for parent
- // expressions to be equivalent.
- // TODO: is this generally safe?
- return this.name === other.name;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions() { }
- clone() {
- return new LexicalReadExpr(this.name);
- }
- }
- /**
- * Runtime operation to retrieve the value of a local reference.
- */
- class ReferenceExpr extends ExpressionBase {
- target;
- targetSlot;
- offset;
- kind = ExpressionKind.Reference;
- constructor(target, targetSlot, offset) {
- super();
- this.target = target;
- this.targetSlot = targetSlot;
- this.offset = offset;
- }
- visitExpression() { }
- isEquivalent(e) {
- return e instanceof ReferenceExpr && e.target === this.target;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions() { }
- clone() {
- return new ReferenceExpr(this.target, this.targetSlot, this.offset);
- }
- }
- class StoreLetExpr extends ExpressionBase {
- target;
- value;
- sourceSpan;
- kind = ExpressionKind.StoreLet;
- [ConsumesVarsTrait] = true;
- [DependsOnSlotContext] = true;
- constructor(target, value, sourceSpan) {
- super();
- this.target = target;
- this.value = value;
- this.sourceSpan = sourceSpan;
- }
- visitExpression() { }
- isEquivalent(e) {
- return (e instanceof StoreLetExpr && e.target === this.target && e.value.isEquivalent(this.value));
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- this.value = transformExpressionsInExpression(this.value, transform, flags);
- }
- clone() {
- return new StoreLetExpr(this.target, this.value, this.sourceSpan);
- }
- }
- class ContextLetReferenceExpr extends ExpressionBase {
- target;
- targetSlot;
- kind = ExpressionKind.ContextLetReference;
- constructor(target, targetSlot) {
- super();
- this.target = target;
- this.targetSlot = targetSlot;
- }
- visitExpression() { }
- isEquivalent(e) {
- return e instanceof ContextLetReferenceExpr && e.target === this.target;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions() { }
- clone() {
- return new ContextLetReferenceExpr(this.target, this.targetSlot);
- }
- }
- /**
- * A reference to the current view context (usually the `ctx` variable in a template function).
- */
- class ContextExpr extends ExpressionBase {
- view;
- kind = ExpressionKind.Context;
- constructor(view) {
- super();
- this.view = view;
- }
- visitExpression() { }
- isEquivalent(e) {
- return e instanceof ContextExpr && e.view === this.view;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions() { }
- clone() {
- return new ContextExpr(this.view);
- }
- }
- /**
- * A reference to the current view context inside a track function.
- */
- class TrackContextExpr extends ExpressionBase {
- view;
- kind = ExpressionKind.TrackContext;
- constructor(view) {
- super();
- this.view = view;
- }
- visitExpression() { }
- isEquivalent(e) {
- return e instanceof TrackContextExpr && e.view === this.view;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions() { }
- clone() {
- return new TrackContextExpr(this.view);
- }
- }
- /**
- * Runtime operation to navigate to the next view context in the view hierarchy.
- */
- class NextContextExpr extends ExpressionBase {
- kind = ExpressionKind.NextContext;
- steps = 1;
- constructor() {
- super();
- }
- visitExpression() { }
- isEquivalent(e) {
- return e instanceof NextContextExpr && e.steps === this.steps;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions() { }
- clone() {
- const expr = new NextContextExpr();
- expr.steps = this.steps;
- return expr;
- }
- }
- /**
- * Runtime operation to snapshot the current view context.
- *
- * The result of this operation can be stored in a variable and later used with the `RestoreView`
- * operation.
- */
- class GetCurrentViewExpr extends ExpressionBase {
- kind = ExpressionKind.GetCurrentView;
- constructor() {
- super();
- }
- visitExpression() { }
- isEquivalent(e) {
- return e instanceof GetCurrentViewExpr;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions() { }
- clone() {
- return new GetCurrentViewExpr();
- }
- }
- /**
- * Runtime operation to restore a snapshotted view.
- */
- class RestoreViewExpr extends ExpressionBase {
- view;
- kind = ExpressionKind.RestoreView;
- constructor(view) {
- super();
- this.view = view;
- }
- visitExpression(visitor, context) {
- if (typeof this.view !== 'number') {
- this.view.visitExpression(visitor, context);
- }
- }
- isEquivalent(e) {
- if (!(e instanceof RestoreViewExpr) || typeof e.view !== typeof this.view) {
- return false;
- }
- if (typeof this.view === 'number') {
- return this.view === e.view;
- }
- else {
- return this.view.isEquivalent(e.view);
- }
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- if (typeof this.view !== 'number') {
- this.view = transformExpressionsInExpression(this.view, transform, flags);
- }
- }
- clone() {
- return new RestoreViewExpr(this.view instanceof Expression ? this.view.clone() : this.view);
- }
- }
- /**
- * Runtime operation to reset the current view context after `RestoreView`.
- */
- class ResetViewExpr extends ExpressionBase {
- expr;
- kind = ExpressionKind.ResetView;
- constructor(expr) {
- super();
- this.expr = expr;
- }
- visitExpression(visitor, context) {
- this.expr.visitExpression(visitor, context);
- }
- isEquivalent(e) {
- return e instanceof ResetViewExpr && this.expr.isEquivalent(e.expr);
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- this.expr = transformExpressionsInExpression(this.expr, transform, flags);
- }
- clone() {
- return new ResetViewExpr(this.expr.clone());
- }
- }
- class TwoWayBindingSetExpr extends ExpressionBase {
- target;
- value;
- kind = ExpressionKind.TwoWayBindingSet;
- constructor(target, value) {
- super();
- this.target = target;
- this.value = value;
- }
- visitExpression(visitor, context) {
- this.target.visitExpression(visitor, context);
- this.value.visitExpression(visitor, context);
- }
- isEquivalent(other) {
- return this.target.isEquivalent(other.target) && this.value.isEquivalent(other.value);
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- this.target = transformExpressionsInExpression(this.target, transform, flags);
- this.value = transformExpressionsInExpression(this.value, transform, flags);
- }
- clone() {
- return new TwoWayBindingSetExpr(this.target, this.value);
- }
- }
- /**
- * Read of a variable declared as an `ir.VariableOp` and referenced through its `ir.XrefId`.
- */
- class ReadVariableExpr extends ExpressionBase {
- xref;
- kind = ExpressionKind.ReadVariable;
- name = null;
- constructor(xref) {
- super();
- this.xref = xref;
- }
- visitExpression() { }
- isEquivalent(other) {
- return other instanceof ReadVariableExpr && other.xref === this.xref;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions() { }
- clone() {
- const expr = new ReadVariableExpr(this.xref);
- expr.name = this.name;
- return expr;
- }
- }
- class PureFunctionExpr extends ExpressionBase {
- kind = ExpressionKind.PureFunctionExpr;
- [ConsumesVarsTrait] = true;
- [UsesVarOffset] = true;
- varOffset = null;
- /**
- * The expression which should be memoized as a pure computation.
- *
- * This expression contains internal `PureFunctionParameterExpr`s, which are placeholders for the
- * positional argument expressions in `args.
- */
- body;
- /**
- * Positional arguments to the pure function which will memoize the `body` expression, which act
- * as memoization keys.
- */
- args;
- /**
- * Once extracted to the `ConstantPool`, a reference to the function which defines the computation
- * of `body`.
- */
- fn = null;
- constructor(expression, args) {
- super();
- this.body = expression;
- this.args = args;
- }
- visitExpression(visitor, context) {
- this.body?.visitExpression(visitor, context);
- for (const arg of this.args) {
- arg.visitExpression(visitor, context);
- }
- }
- isEquivalent(other) {
- if (!(other instanceof PureFunctionExpr) || other.args.length !== this.args.length) {
- return false;
- }
- return (other.body !== null &&
- this.body !== null &&
- other.body.isEquivalent(this.body) &&
- other.args.every((arg, idx) => arg.isEquivalent(this.args[idx])));
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- if (this.body !== null) {
- // TODO: figure out if this is the right flag to pass here.
- this.body = transformExpressionsInExpression(this.body, transform, flags | VisitorContextFlag.InChildOperation);
- }
- else if (this.fn !== null) {
- this.fn = transformExpressionsInExpression(this.fn, transform, flags);
- }
- for (let i = 0; i < this.args.length; i++) {
- this.args[i] = transformExpressionsInExpression(this.args[i], transform, flags);
- }
- }
- clone() {
- const expr = new PureFunctionExpr(this.body?.clone() ?? null, this.args.map((arg) => arg.clone()));
- expr.fn = this.fn?.clone() ?? null;
- expr.varOffset = this.varOffset;
- return expr;
- }
- }
- class PureFunctionParameterExpr extends ExpressionBase {
- index;
- kind = ExpressionKind.PureFunctionParameterExpr;
- constructor(index) {
- super();
- this.index = index;
- }
- visitExpression() { }
- isEquivalent(other) {
- return other instanceof PureFunctionParameterExpr && other.index === this.index;
- }
- isConstant() {
- return true;
- }
- transformInternalExpressions() { }
- clone() {
- return new PureFunctionParameterExpr(this.index);
- }
- }
- class PipeBindingExpr extends ExpressionBase {
- target;
- targetSlot;
- name;
- args;
- kind = ExpressionKind.PipeBinding;
- [ConsumesVarsTrait] = true;
- [UsesVarOffset] = true;
- varOffset = null;
- constructor(target, targetSlot, name, args) {
- super();
- this.target = target;
- this.targetSlot = targetSlot;
- this.name = name;
- this.args = args;
- }
- visitExpression(visitor, context) {
- for (const arg of this.args) {
- arg.visitExpression(visitor, context);
- }
- }
- isEquivalent() {
- return false;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- for (let idx = 0; idx < this.args.length; idx++) {
- this.args[idx] = transformExpressionsInExpression(this.args[idx], transform, flags);
- }
- }
- clone() {
- const r = new PipeBindingExpr(this.target, this.targetSlot, this.name, this.args.map((a) => a.clone()));
- r.varOffset = this.varOffset;
- return r;
- }
- }
- class PipeBindingVariadicExpr extends ExpressionBase {
- target;
- targetSlot;
- name;
- args;
- numArgs;
- kind = ExpressionKind.PipeBindingVariadic;
- [ConsumesVarsTrait] = true;
- [UsesVarOffset] = true;
- varOffset = null;
- constructor(target, targetSlot, name, args, numArgs) {
- super();
- this.target = target;
- this.targetSlot = targetSlot;
- this.name = name;
- this.args = args;
- this.numArgs = numArgs;
- }
- visitExpression(visitor, context) {
- this.args.visitExpression(visitor, context);
- }
- isEquivalent() {
- return false;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- this.args = transformExpressionsInExpression(this.args, transform, flags);
- }
- clone() {
- const r = new PipeBindingVariadicExpr(this.target, this.targetSlot, this.name, this.args.clone(), this.numArgs);
- r.varOffset = this.varOffset;
- return r;
- }
- }
- class SafePropertyReadExpr extends ExpressionBase {
- receiver;
- name;
- kind = ExpressionKind.SafePropertyRead;
- constructor(receiver, name) {
- super();
- this.receiver = receiver;
- this.name = name;
- }
- // An alias for name, which allows other logic to handle property reads and keyed reads together.
- get index() {
- return this.name;
- }
- visitExpression(visitor, context) {
- this.receiver.visitExpression(visitor, context);
- }
- isEquivalent() {
- return false;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- this.receiver = transformExpressionsInExpression(this.receiver, transform, flags);
- }
- clone() {
- return new SafePropertyReadExpr(this.receiver.clone(), this.name);
- }
- }
- class SafeKeyedReadExpr extends ExpressionBase {
- receiver;
- index;
- kind = ExpressionKind.SafeKeyedRead;
- constructor(receiver, index, sourceSpan) {
- super(sourceSpan);
- this.receiver = receiver;
- this.index = index;
- }
- visitExpression(visitor, context) {
- this.receiver.visitExpression(visitor, context);
- this.index.visitExpression(visitor, context);
- }
- isEquivalent() {
- return false;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- this.receiver = transformExpressionsInExpression(this.receiver, transform, flags);
- this.index = transformExpressionsInExpression(this.index, transform, flags);
- }
- clone() {
- return new SafeKeyedReadExpr(this.receiver.clone(), this.index.clone(), this.sourceSpan);
- }
- }
- class SafeInvokeFunctionExpr extends ExpressionBase {
- receiver;
- args;
- kind = ExpressionKind.SafeInvokeFunction;
- constructor(receiver, args) {
- super();
- this.receiver = receiver;
- this.args = args;
- }
- visitExpression(visitor, context) {
- this.receiver.visitExpression(visitor, context);
- for (const a of this.args) {
- a.visitExpression(visitor, context);
- }
- }
- isEquivalent() {
- return false;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- this.receiver = transformExpressionsInExpression(this.receiver, transform, flags);
- for (let i = 0; i < this.args.length; i++) {
- this.args[i] = transformExpressionsInExpression(this.args[i], transform, flags);
- }
- }
- clone() {
- return new SafeInvokeFunctionExpr(this.receiver.clone(), this.args.map((a) => a.clone()));
- }
- }
- class SafeTernaryExpr extends ExpressionBase {
- guard;
- expr;
- kind = ExpressionKind.SafeTernaryExpr;
- constructor(guard, expr) {
- super();
- this.guard = guard;
- this.expr = expr;
- }
- visitExpression(visitor, context) {
- this.guard.visitExpression(visitor, context);
- this.expr.visitExpression(visitor, context);
- }
- isEquivalent() {
- return false;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- this.guard = transformExpressionsInExpression(this.guard, transform, flags);
- this.expr = transformExpressionsInExpression(this.expr, transform, flags);
- }
- clone() {
- return new SafeTernaryExpr(this.guard.clone(), this.expr.clone());
- }
- }
- class EmptyExpr extends ExpressionBase {
- kind = ExpressionKind.EmptyExpr;
- visitExpression(visitor, context) { }
- isEquivalent(e) {
- return e instanceof EmptyExpr;
- }
- isConstant() {
- return true;
- }
- clone() {
- return new EmptyExpr();
- }
- transformInternalExpressions() { }
- }
- class AssignTemporaryExpr extends ExpressionBase {
- expr;
- xref;
- kind = ExpressionKind.AssignTemporaryExpr;
- name = null;
- constructor(expr, xref) {
- super();
- this.expr = expr;
- this.xref = xref;
- }
- visitExpression(visitor, context) {
- this.expr.visitExpression(visitor, context);
- }
- isEquivalent() {
- return false;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) {
- this.expr = transformExpressionsInExpression(this.expr, transform, flags);
- }
- clone() {
- const a = new AssignTemporaryExpr(this.expr.clone(), this.xref);
- a.name = this.name;
- return a;
- }
- }
- class ReadTemporaryExpr extends ExpressionBase {
- xref;
- kind = ExpressionKind.ReadTemporaryExpr;
- name = null;
- constructor(xref) {
- super();
- this.xref = xref;
- }
- visitExpression(visitor, context) { }
- isEquivalent() {
- return this.xref === this.xref;
- }
- isConstant() {
- return false;
- }
- transformInternalExpressions(transform, flags) { }
- clone() {
- const r = new ReadTemporaryExpr(this.xref);
- r.name = this.name;
- return r;
- }
- }
- class SlotLiteralExpr extends ExpressionBase {
- slot;
- kind = ExpressionKind.SlotLiteralExpr;
- constructor(slot) {
- super();
- this.slot = slot;
- }
- visitExpression(visitor, context) { }
- isEquivalent(e) {
- return e instanceof SlotLiteralExpr && e.slot === this.slot;
- }
- isConstant() {
- return true;
- }
- clone() {
- return new SlotLiteralExpr(this.slot);
- }
- transformInternalExpressions() { }
- }
- class ConditionalCaseExpr extends ExpressionBase {
- expr;
- target;
- targetSlot;
- alias;
- kind = ExpressionKind.ConditionalCase;
- /**
- * Create an expression for one branch of a conditional.
- * @param expr The expression to be tested for this case. Might be null, as in an `else` case.
- * @param target The Xref of the view to be displayed if this condition is true.
- */
- constructor(expr, target, targetSlot, alias = null) {
- super();
- this.expr = expr;
- this.target = target;
- this.targetSlot = targetSlot;
- this.alias = alias;
- }
- visitExpression(visitor, context) {
- if (this.expr !== null) {
- this.expr.visitExpression(visitor, context);
- }
- }
- isEquivalent(e) {
- return e instanceof ConditionalCaseExpr && e.expr === this.expr;
- }
- isConstant() {
- return true;
- }
- clone() {
- return new ConditionalCaseExpr(this.expr, this.target, this.targetSlot);
- }
- transformInternalExpressions(transform, flags) {
- if (this.expr !== null) {
- this.expr = transformExpressionsInExpression(this.expr, transform, flags);
- }
- }
- }
- class ConstCollectedExpr extends ExpressionBase {
- expr;
- kind = ExpressionKind.ConstCollected;
- constructor(expr) {
- super();
- this.expr = expr;
- }
- transformInternalExpressions(transform, flags) {
- this.expr = transform(this.expr, flags);
- }
- visitExpression(visitor, context) {
- this.expr.visitExpression(visitor, context);
- }
- isEquivalent(e) {
- if (!(e instanceof ConstCollectedExpr)) {
- return false;
- }
- return this.expr.isEquivalent(e.expr);
- }
- isConstant() {
- return this.expr.isConstant();
- }
- clone() {
- return new ConstCollectedExpr(this.expr);
- }
- }
- /**
- * Visits all `Expression`s in the AST of `op` with the `visitor` function.
- */
- function visitExpressionsInOp(op, visitor) {
- transformExpressionsInOp(op, (expr, flags) => {
- visitor(expr, flags);
- return expr;
- }, VisitorContextFlag.None);
- }
- var VisitorContextFlag;
- (function (VisitorContextFlag) {
- VisitorContextFlag[VisitorContextFlag["None"] = 0] = "None";
- VisitorContextFlag[VisitorContextFlag["InChildOperation"] = 1] = "InChildOperation";
- })(VisitorContextFlag || (VisitorContextFlag = {}));
- function transformExpressionsInInterpolation(interpolation, transform, flags) {
- for (let i = 0; i < interpolation.expressions.length; i++) {
- interpolation.expressions[i] = transformExpressionsInExpression(interpolation.expressions[i], transform, flags);
- }
- }
- /**
- * Transform all `Expression`s in the AST of `op` with the `transform` function.
- *
- * All such operations will be replaced with the result of applying `transform`, which may be an
- * identity transformation.
- */
- function transformExpressionsInOp(op, transform, flags) {
- switch (op.kind) {
- case OpKind.StyleProp:
- case OpKind.StyleMap:
- case OpKind.ClassProp:
- case OpKind.ClassMap:
- case OpKind.Binding:
- if (op.expression instanceof Interpolation) {
- transformExpressionsInInterpolation(op.expression, transform, flags);
- }
- else {
- op.expression = transformExpressionsInExpression(op.expression, transform, flags);
- }
- break;
- case OpKind.Property:
- case OpKind.HostProperty:
- case OpKind.Attribute:
- if (op.expression instanceof Interpolation) {
- transformExpressionsInInterpolation(op.expression, transform, flags);
- }
- else {
- op.expression = transformExpressionsInExpression(op.expression, transform, flags);
- }
- op.sanitizer =
- op.sanitizer && transformExpressionsInExpression(op.sanitizer, transform, flags);
- break;
- case OpKind.TwoWayProperty:
- op.expression = transformExpressionsInExpression(op.expression, transform, flags);
- op.sanitizer =
- op.sanitizer && transformExpressionsInExpression(op.sanitizer, transform, flags);
- break;
- case OpKind.I18nExpression:
- op.expression = transformExpressionsInExpression(op.expression, transform, flags);
- break;
- case OpKind.InterpolateText:
- transformExpressionsInInterpolation(op.interpolation, transform, flags);
- break;
- case OpKind.Statement:
- transformExpressionsInStatement(op.statement, transform, flags);
- break;
- case OpKind.Variable:
- op.initializer = transformExpressionsInExpression(op.initializer, transform, flags);
- break;
- case OpKind.Conditional:
- for (const condition of op.conditions) {
- if (condition.expr === null) {
- // This is a default case.
- continue;
- }
- condition.expr = transformExpressionsInExpression(condition.expr, transform, flags);
- }
- if (op.processed !== null) {
- op.processed = transformExpressionsInExpression(op.processed, transform, flags);
- }
- if (op.contextValue !== null) {
- op.contextValue = transformExpressionsInExpression(op.contextValue, transform, flags);
- }
- break;
- case OpKind.Listener:
- case OpKind.TwoWayListener:
- for (const innerOp of op.handlerOps) {
- transformExpressionsInOp(innerOp, transform, flags | VisitorContextFlag.InChildOperation);
- }
- break;
- case OpKind.ExtractedAttribute:
- op.expression =
- op.expression && transformExpressionsInExpression(op.expression, transform, flags);
- op.trustedValueFn =
- op.trustedValueFn && transformExpressionsInExpression(op.trustedValueFn, transform, flags);
- break;
- case OpKind.RepeaterCreate:
- if (op.trackByOps === null) {
- op.track = transformExpressionsInExpression(op.track, transform, flags);
- }
- else {
- for (const innerOp of op.trackByOps) {
- transformExpressionsInOp(innerOp, transform, flags | VisitorContextFlag.InChildOperation);
- }
- }
- if (op.trackByFn !== null) {
- op.trackByFn = transformExpressionsInExpression(op.trackByFn, transform, flags);
- }
- break;
- case OpKind.Repeater:
- op.collection = transformExpressionsInExpression(op.collection, transform, flags);
- break;
- case OpKind.Defer:
- if (op.loadingConfig !== null) {
- op.loadingConfig = transformExpressionsInExpression(op.loadingConfig, transform, flags);
- }
- if (op.placeholderConfig !== null) {
- op.placeholderConfig = transformExpressionsInExpression(op.placeholderConfig, transform, flags);
- }
- if (op.resolverFn !== null) {
- op.resolverFn = transformExpressionsInExpression(op.resolverFn, transform, flags);
- }
- break;
- case OpKind.I18nMessage:
- for (const [placeholder, expr] of op.params) {
- op.params.set(placeholder, transformExpressionsInExpression(expr, transform, flags));
- }
- for (const [placeholder, expr] of op.postprocessingParams) {
- op.postprocessingParams.set(placeholder, transformExpressionsInExpression(expr, transform, flags));
- }
- break;
- case OpKind.DeferWhen:
- op.expr = transformExpressionsInExpression(op.expr, transform, flags);
- break;
- case OpKind.StoreLet:
- op.value = transformExpressionsInExpression(op.value, transform, flags);
- break;
- case OpKind.Advance:
- case OpKind.Container:
- case OpKind.ContainerEnd:
- case OpKind.ContainerStart:
- case OpKind.DeferOn:
- case OpKind.DisableBindings:
- case OpKind.Element:
- case OpKind.ElementEnd:
- case OpKind.ElementStart:
- case OpKind.EnableBindings:
- case OpKind.I18n:
- case OpKind.I18nApply:
- case OpKind.I18nContext:
- case OpKind.I18nEnd:
- case OpKind.I18nStart:
- case OpKind.IcuEnd:
- case OpKind.IcuStart:
- case OpKind.Namespace:
- case OpKind.Pipe:
- case OpKind.Projection:
- case OpKind.ProjectionDef:
- case OpKind.Template:
- case OpKind.Text:
- case OpKind.I18nAttributes:
- case OpKind.IcuPlaceholder:
- case OpKind.DeclareLet:
- case OpKind.SourceLocation:
- // These operations contain no expressions.
- break;
- default:
- throw new Error(`AssertionError: transformExpressionsInOp doesn't handle ${OpKind[op.kind]}`);
- }
- }
- /**
- * Transform all `Expression`s in the AST of `expr` with the `transform` function.
- *
- * All such operations will be replaced with the result of applying `transform`, which may be an
- * identity transformation.
- */
- function transformExpressionsInExpression(expr, transform, flags) {
- if (expr instanceof ExpressionBase) {
- expr.transformInternalExpressions(transform, flags);
- }
- else if (expr instanceof BinaryOperatorExpr) {
- expr.lhs = transformExpressionsInExpression(expr.lhs, transform, flags);
- expr.rhs = transformExpressionsInExpression(expr.rhs, transform, flags);
- }
- else if (expr instanceof UnaryOperatorExpr) {
- expr.expr = transformExpressionsInExpression(expr.expr, transform, flags);
- }
- else if (expr instanceof ReadPropExpr) {
- expr.receiver = transformExpressionsInExpression(expr.receiver, transform, flags);
- }
- else if (expr instanceof ReadKeyExpr) {
- expr.receiver = transformExpressionsInExpression(expr.receiver, transform, flags);
- expr.index = transformExpressionsInExpression(expr.index, transform, flags);
- }
- else if (expr instanceof WritePropExpr) {
- expr.receiver = transformExpressionsInExpression(expr.receiver, transform, flags);
- expr.value = transformExpressionsInExpression(expr.value, transform, flags);
- }
- else if (expr instanceof WriteKeyExpr) {
- expr.receiver = transformExpressionsInExpression(expr.receiver, transform, flags);
- expr.index = transformExpressionsInExpression(expr.index, transform, flags);
- expr.value = transformExpressionsInExpression(expr.value, transform, flags);
- }
- else if (expr instanceof InvokeFunctionExpr) {
- expr.fn = transformExpressionsInExpression(expr.fn, transform, flags);
- for (let i = 0; i < expr.args.length; i++) {
- expr.args[i] = transformExpressionsInExpression(expr.args[i], transform, flags);
- }
- }
- else if (expr instanceof LiteralArrayExpr) {
- for (let i = 0; i < expr.entries.length; i++) {
- expr.entries[i] = transformExpressionsInExpression(expr.entries[i], transform, flags);
- }
- }
- else if (expr instanceof LiteralMapExpr) {
- for (let i = 0; i < expr.entries.length; i++) {
- expr.entries[i].value = transformExpressionsInExpression(expr.entries[i].value, transform, flags);
- }
- }
- else if (expr instanceof ConditionalExpr) {
- expr.condition = transformExpressionsInExpression(expr.condition, transform, flags);
- expr.trueCase = transformExpressionsInExpression(expr.trueCase, transform, flags);
- if (expr.falseCase !== null) {
- expr.falseCase = transformExpressionsInExpression(expr.falseCase, transform, flags);
- }
- }
- else if (expr instanceof TypeofExpr) {
- expr.expr = transformExpressionsInExpression(expr.expr, transform, flags);
- }
- else if (expr instanceof WriteVarExpr) {
- expr.value = transformExpressionsInExpression(expr.value, transform, flags);
- }
- else if (expr instanceof LocalizedString) {
- for (let i = 0; i < expr.expressions.length; i++) {
- expr.expressions[i] = transformExpressionsInExpression(expr.expressions[i], transform, flags);
- }
- }
- else if (expr instanceof NotExpr) {
- expr.condition = transformExpressionsInExpression(expr.condition, transform, flags);
- }
- else if (expr instanceof TaggedTemplateLiteralExpr) {
- expr.tag = transformExpressionsInExpression(expr.tag, transform, flags);
- expr.template.expressions = expr.template.expressions.map((e) => transformExpressionsInExpression(e, transform, flags));
- }
- else if (expr instanceof ArrowFunctionExpr) {
- if (Array.isArray(expr.body)) {
- for (let i = 0; i < expr.body.length; i++) {
- transformExpressionsInStatement(expr.body[i], transform, flags);
- }
- }
- else {
- expr.body = transformExpressionsInExpression(expr.body, transform, flags);
- }
- }
- else if (expr instanceof WrappedNodeExpr) ;
- else if (expr instanceof TemplateLiteralExpr) {
- for (let i = 0; i < expr.expressions.length; i++) {
- expr.expressions[i] = transformExpressionsInExpression(expr.expressions[i], transform, flags);
- }
- }
- else if (expr instanceof ReadVarExpr ||
- expr instanceof ExternalExpr ||
- expr instanceof LiteralExpr) ;
- else {
- throw new Error(`Unhandled expression kind: ${expr.constructor.name}`);
- }
- return transform(expr, flags);
- }
- /**
- * Transform all `Expression`s in the AST of `stmt` with the `transform` function.
- *
- * All such operations will be replaced with the result of applying `transform`, which may be an
- * identity transformation.
- */
- function transformExpressionsInStatement(stmt, transform, flags) {
- if (stmt instanceof ExpressionStatement) {
- stmt.expr = transformExpressionsInExpression(stmt.expr, transform, flags);
- }
- else if (stmt instanceof ReturnStatement) {
- stmt.value = transformExpressionsInExpression(stmt.value, transform, flags);
- }
- else if (stmt instanceof DeclareVarStmt) {
- if (stmt.value !== undefined) {
- stmt.value = transformExpressionsInExpression(stmt.value, transform, flags);
- }
- }
- else if (stmt instanceof IfStmt) {
- stmt.condition = transformExpressionsInExpression(stmt.condition, transform, flags);
- for (const caseStatement of stmt.trueCase) {
- transformExpressionsInStatement(caseStatement, transform, flags);
- }
- for (const caseStatement of stmt.falseCase) {
- transformExpressionsInStatement(caseStatement, transform, flags);
- }
- }
- else {
- throw new Error(`Unhandled statement kind: ${stmt.constructor.name}`);
- }
- }
- /**
- * Checks whether the given expression is a string literal.
- */
- function isStringLiteral(expr) {
- return expr instanceof LiteralExpr && typeof expr.value === 'string';
- }
- /**
- * A linked list of `Op` nodes of a given subtype.
- *
- * @param OpT specific subtype of `Op` nodes which this list contains.
- */
- class OpList {
- static nextListId = 0;
- /**
- * Debug ID of this `OpList` instance.
- */
- debugListId = OpList.nextListId++;
- // OpList uses static head/tail nodes of a special `ListEnd` type.
- // This avoids the need for special casing of the first and last list
- // elements in all list operations.
- head = {
- kind: OpKind.ListEnd,
- next: null,
- prev: null,
- debugListId: this.debugListId,
- };
- tail = {
- kind: OpKind.ListEnd,
- next: null,
- prev: null,
- debugListId: this.debugListId,
- };
- constructor() {
- // Link `head` and `tail` together at the start (list is empty).
- this.head.next = this.tail;
- this.tail.prev = this.head;
- }
- /**
- * Push a new operation to the tail of the list.
- */
- push(op) {
- if (Array.isArray(op)) {
- for (const o of op) {
- this.push(o);
- }
- return;
- }
- OpList.assertIsNotEnd(op);
- OpList.assertIsUnowned(op);
- op.debugListId = this.debugListId;
- // The old "previous" node (which might be the head, if the list is empty).
- const oldLast = this.tail.prev;
- // Insert `op` following the old last node.
- op.prev = oldLast;
- oldLast.next = op;
- // Connect `op` with the list tail.
- op.next = this.tail;
- this.tail.prev = op;
- }
- /**
- * Prepend one or more nodes to the start of the list.
- */
- prepend(ops) {
- if (ops.length === 0) {
- return;
- }
- for (const op of ops) {
- OpList.assertIsNotEnd(op);
- OpList.assertIsUnowned(op);
- op.debugListId = this.debugListId;
- }
- const first = this.head.next;
- let prev = this.head;
- for (const op of ops) {
- prev.next = op;
- op.prev = prev;
- prev = op;
- }
- prev.next = first;
- first.prev = prev;
- }
- /**
- * `OpList` is iterable via the iteration protocol.
- *
- * It's safe to mutate the part of the list that has already been returned by the iterator, up to
- * and including the last operation returned. Mutations beyond that point _may_ be safe, but may
- * also corrupt the iteration position and should be avoided.
- */
- *[Symbol.iterator]() {
- let current = this.head.next;
- while (current !== this.tail) {
- // Guards against corruption of the iterator state by mutations to the tail of the list during
- // iteration.
- OpList.assertIsOwned(current, this.debugListId);
- const next = current.next;
- yield current;
- current = next;
- }
- }
- *reversed() {
- let current = this.tail.prev;
- while (current !== this.head) {
- OpList.assertIsOwned(current, this.debugListId);
- const prev = current.prev;
- yield current;
- current = prev;
- }
- }
- /**
- * Replace `oldOp` with `newOp` in the list.
- */
- static replace(oldOp, newOp) {
- OpList.assertIsNotEnd(oldOp);
- OpList.assertIsNotEnd(newOp);
- OpList.assertIsOwned(oldOp);
- OpList.assertIsUnowned(newOp);
- newOp.debugListId = oldOp.debugListId;
- if (oldOp.prev !== null) {
- oldOp.prev.next = newOp;
- newOp.prev = oldOp.prev;
- }
- if (oldOp.next !== null) {
- oldOp.next.prev = newOp;
- newOp.next = oldOp.next;
- }
- oldOp.debugListId = null;
- oldOp.prev = null;
- oldOp.next = null;
- }
- /**
- * Replace `oldOp` with some number of new operations in the list (which may include `oldOp`).
- */
- static replaceWithMany(oldOp, newOps) {
- if (newOps.length === 0) {
- // Replacing with an empty list -> pure removal.
- OpList.remove(oldOp);
- return;
- }
- OpList.assertIsNotEnd(oldOp);
- OpList.assertIsOwned(oldOp);
- const listId = oldOp.debugListId;
- oldOp.debugListId = null;
- for (const newOp of newOps) {
- OpList.assertIsNotEnd(newOp);
- // `newOp` might be `oldOp`, but at this point it's been marked as unowned.
- OpList.assertIsUnowned(newOp);
- }
- // It should be safe to reuse `oldOp` in the `newOps` list - maybe you want to sandwich an
- // operation between two new ops.
- const { prev: oldPrev, next: oldNext } = oldOp;
- oldOp.prev = null;
- oldOp.next = null;
- let prev = oldPrev;
- for (const newOp of newOps) {
- this.assertIsUnowned(newOp);
- newOp.debugListId = listId;
- prev.next = newOp;
- newOp.prev = prev;
- // This _should_ be the case, but set it just in case.
- newOp.next = null;
- prev = newOp;
- }
- // At the end of iteration, `prev` holds the last node in the list.
- const first = newOps[0];
- const last = prev;
- // Replace `oldOp` with the chain `first` -> `last`.
- if (oldPrev !== null) {
- oldPrev.next = first;
- first.prev = oldPrev;
- }
- if (oldNext !== null) {
- oldNext.prev = last;
- last.next = oldNext;
- }
- }
- /**
- * Remove the given node from the list which contains it.
- */
- static remove(op) {
- OpList.assertIsNotEnd(op);
- OpList.assertIsOwned(op);
- op.prev.next = op.next;
- op.next.prev = op.prev;
- // Break any link between the node and this list to safeguard against its usage in future
- // operations.
- op.debugListId = null;
- op.prev = null;
- op.next = null;
- }
- /**
- * Insert `op` before `target`.
- */
- static insertBefore(op, target) {
- if (Array.isArray(op)) {
- for (const o of op) {
- this.insertBefore(o, target);
- }
- return;
- }
- OpList.assertIsOwned(target);
- if (target.prev === null) {
- throw new Error(`AssertionError: illegal operation on list start`);
- }
- OpList.assertIsNotEnd(op);
- OpList.assertIsUnowned(op);
- op.debugListId = target.debugListId;
- // Just in case.
- op.prev = null;
- target.prev.next = op;
- op.prev = target.prev;
- op.next = target;
- target.prev = op;
- }
- /**
- * Insert `op` after `target`.
- */
- static insertAfter(op, target) {
- OpList.assertIsOwned(target);
- if (target.next === null) {
- throw new Error(`AssertionError: illegal operation on list end`);
- }
- OpList.assertIsNotEnd(op);
- OpList.assertIsUnowned(op);
- op.debugListId = target.debugListId;
- target.next.prev = op;
- op.next = target.next;
- op.prev = target;
- target.next = op;
- }
- /**
- * Asserts that `op` does not currently belong to a list.
- */
- static assertIsUnowned(op) {
- if (op.debugListId !== null) {
- throw new Error(`AssertionError: illegal operation on owned node: ${OpKind[op.kind]}`);
- }
- }
- /**
- * Asserts that `op` currently belongs to a list. If `byList` is passed, `op` is asserted to
- * specifically belong to that list.
- */
- static assertIsOwned(op, byList) {
- if (op.debugListId === null) {
- throw new Error(`AssertionError: illegal operation on unowned node: ${OpKind[op.kind]}`);
- }
- else if (byList !== undefined && op.debugListId !== byList) {
- throw new Error(`AssertionError: node belongs to the wrong list (expected ${byList}, actual ${op.debugListId})`);
- }
- }
- /**
- * Asserts that `op` is not a special `ListEnd` node.
- */
- static assertIsNotEnd(op) {
- if (op.kind === OpKind.ListEnd) {
- throw new Error(`AssertionError: illegal operation on list head or tail`);
- }
- }
- }
- class SlotHandle {
- slot = null;
- }
- /**
- * The set of OpKinds that represent the creation of an element or container
- */
- const elementContainerOpKinds = new Set([
- OpKind.Element,
- OpKind.ElementStart,
- OpKind.Container,
- OpKind.ContainerStart,
- OpKind.Template,
- OpKind.RepeaterCreate,
- ]);
- /**
- * Checks whether the given operation represents the creation of an element or container.
- */
- function isElementOrContainerOp(op) {
- return elementContainerOpKinds.has(op.kind);
- }
- /**
- * Create an `ElementStartOp`.
- */
- function createElementStartOp(tag, xref, namespace, i18nPlaceholder, startSourceSpan, wholeSourceSpan) {
- return {
- kind: OpKind.ElementStart,
- xref,
- tag,
- handle: new SlotHandle(),
- attributes: null,
- localRefs: [],
- nonBindable: false,
- namespace,
- i18nPlaceholder,
- startSourceSpan,
- wholeSourceSpan,
- ...TRAIT_CONSUMES_SLOT,
- ...NEW_OP,
- };
- }
- /**
- * Create a `TemplateOp`.
- */
- function createTemplateOp(xref, templateKind, tag, functionNameSuffix, namespace, i18nPlaceholder, startSourceSpan, wholeSourceSpan) {
- return {
- kind: OpKind.Template,
- xref,
- templateKind,
- attributes: null,
- tag,
- handle: new SlotHandle(),
- functionNameSuffix,
- decls: null,
- vars: null,
- localRefs: [],
- nonBindable: false,
- namespace,
- i18nPlaceholder,
- startSourceSpan,
- wholeSourceSpan,
- ...TRAIT_CONSUMES_SLOT,
- ...NEW_OP,
- };
- }
- function createRepeaterCreateOp(primaryView, emptyView, tag, track, varNames, emptyTag, i18nPlaceholder, emptyI18nPlaceholder, startSourceSpan, wholeSourceSpan) {
- return {
- kind: OpKind.RepeaterCreate,
- attributes: null,
- xref: primaryView,
- handle: new SlotHandle(),
- emptyView,
- track,
- trackByFn: null,
- trackByOps: null,
- tag,
- emptyTag,
- emptyAttributes: null,
- functionNameSuffix: 'For',
- namespace: Namespace.HTML,
- nonBindable: false,
- localRefs: [],
- decls: null,
- vars: null,
- varNames,
- usesComponentInstance: false,
- i18nPlaceholder,
- emptyI18nPlaceholder,
- startSourceSpan,
- wholeSourceSpan,
- ...TRAIT_CONSUMES_SLOT,
- ...NEW_OP,
- ...TRAIT_CONSUMES_VARS,
- numSlotsUsed: emptyView === null ? 2 : 3,
- };
- }
- /**
- * Create an `ElementEndOp`.
- */
- function createElementEndOp(xref, sourceSpan) {
- return {
- kind: OpKind.ElementEnd,
- xref,
- sourceSpan,
- ...NEW_OP,
- };
- }
- function createDisableBindingsOp(xref) {
- return {
- kind: OpKind.DisableBindings,
- xref,
- ...NEW_OP,
- };
- }
- function createEnableBindingsOp(xref) {
- return {
- kind: OpKind.EnableBindings,
- xref,
- ...NEW_OP,
- };
- }
- /**
- * Create a `TextOp`.
- */
- function createTextOp(xref, initialValue, icuPlaceholder, sourceSpan) {
- return {
- kind: OpKind.Text,
- xref,
- handle: new SlotHandle(),
- initialValue,
- icuPlaceholder,
- sourceSpan,
- ...TRAIT_CONSUMES_SLOT,
- ...NEW_OP,
- };
- }
- /**
- * Create a `ListenerOp`. Host bindings reuse all the listener logic.
- */
- function createListenerOp(target, targetSlot, name, tag, handlerOps, animationPhase, eventTarget, hostListener, sourceSpan) {
- const handlerList = new OpList();
- handlerList.push(handlerOps);
- return {
- kind: OpKind.Listener,
- target,
- targetSlot,
- tag,
- hostListener,
- name,
- handlerOps: handlerList,
- handlerFnName: null,
- consumesDollarEvent: false,
- isAnimationListener: animationPhase !== null,
- animationPhase,
- eventTarget,
- sourceSpan,
- ...NEW_OP,
- };
- }
- /**
- * Create a `TwoWayListenerOp`.
- */
- function createTwoWayListenerOp(target, targetSlot, name, tag, handlerOps, sourceSpan) {
- const handlerList = new OpList();
- handlerList.push(handlerOps);
- return {
- kind: OpKind.TwoWayListener,
- target,
- targetSlot,
- tag,
- name,
- handlerOps: handlerList,
- handlerFnName: null,
- sourceSpan,
- ...NEW_OP,
- };
- }
- function createPipeOp(xref, slot, name) {
- return {
- kind: OpKind.Pipe,
- xref,
- handle: slot,
- name,
- ...NEW_OP,
- ...TRAIT_CONSUMES_SLOT,
- };
- }
- function createNamespaceOp(namespace) {
- return {
- kind: OpKind.Namespace,
- active: namespace,
- ...NEW_OP,
- };
- }
- function createProjectionDefOp(def) {
- return {
- kind: OpKind.ProjectionDef,
- def,
- ...NEW_OP,
- };
- }
- function createProjectionOp(xref, selector, i18nPlaceholder, fallbackView, sourceSpan) {
- return {
- kind: OpKind.Projection,
- xref,
- handle: new SlotHandle(),
- selector,
- i18nPlaceholder,
- fallbackView,
- projectionSlotIndex: 0,
- attributes: null,
- localRefs: [],
- sourceSpan,
- ...NEW_OP,
- ...TRAIT_CONSUMES_SLOT,
- numSlotsUsed: fallbackView === null ? 1 : 2,
- };
- }
- /**
- * Create an `ExtractedAttributeOp`.
- */
- function createExtractedAttributeOp(target, bindingKind, namespace, name, expression, i18nContext, i18nMessage, securityContext) {
- return {
- kind: OpKind.ExtractedAttribute,
- target,
- bindingKind,
- namespace,
- name,
- expression,
- i18nContext,
- i18nMessage,
- securityContext,
- trustedValueFn: null,
- ...NEW_OP,
- };
- }
- function createDeferOp(xref, main, mainSlot, ownResolverFn, resolverFn, sourceSpan) {
- return {
- kind: OpKind.Defer,
- xref,
- handle: new SlotHandle(),
- mainView: main,
- mainSlot,
- loadingView: null,
- loadingSlot: null,
- loadingConfig: null,
- loadingMinimumTime: null,
- loadingAfterTime: null,
- placeholderView: null,
- placeholderSlot: null,
- placeholderConfig: null,
- placeholderMinimumTime: null,
- errorView: null,
- errorSlot: null,
- ownResolverFn,
- resolverFn,
- flags: null,
- sourceSpan,
- ...NEW_OP,
- ...TRAIT_CONSUMES_SLOT,
- numSlotsUsed: 2,
- };
- }
- function createDeferOnOp(defer, trigger, modifier, sourceSpan) {
- return {
- kind: OpKind.DeferOn,
- defer,
- trigger,
- modifier,
- sourceSpan,
- ...NEW_OP,
- };
- }
- /**
- * Creates a `DeclareLetOp`.
- */
- function createDeclareLetOp(xref, declaredName, sourceSpan) {
- return {
- kind: OpKind.DeclareLet,
- xref,
- declaredName,
- sourceSpan,
- handle: new SlotHandle(),
- ...TRAIT_CONSUMES_SLOT,
- ...NEW_OP,
- };
- }
- /**
- * Create an `ExtractedMessageOp`.
- */
- function createI18nMessageOp(xref, i18nContext, i18nBlock, message, messagePlaceholder, params, postprocessingParams, needsPostprocessing) {
- return {
- kind: OpKind.I18nMessage,
- xref,
- i18nContext,
- i18nBlock,
- message,
- messagePlaceholder,
- params,
- postprocessingParams,
- needsPostprocessing,
- subMessages: [],
- ...NEW_OP,
- };
- }
- /**
- * Create an `I18nStartOp`.
- */
- function createI18nStartOp(xref, message, root, sourceSpan) {
- return {
- kind: OpKind.I18nStart,
- xref,
- handle: new SlotHandle(),
- root: root ?? xref,
- message,
- messageIndex: null,
- subTemplateIndex: null,
- context: null,
- sourceSpan,
- ...NEW_OP,
- ...TRAIT_CONSUMES_SLOT,
- };
- }
- /**
- * Create an `I18nEndOp`.
- */
- function createI18nEndOp(xref, sourceSpan) {
- return {
- kind: OpKind.I18nEnd,
- xref,
- sourceSpan,
- ...NEW_OP,
- };
- }
- /**
- * Creates an ICU start op.
- */
- function createIcuStartOp(xref, message, messagePlaceholder, sourceSpan) {
- return {
- kind: OpKind.IcuStart,
- xref,
- message,
- messagePlaceholder,
- context: null,
- sourceSpan,
- ...NEW_OP,
- };
- }
- /**
- * Creates an ICU end op.
- */
- function createIcuEndOp(xref) {
- return {
- kind: OpKind.IcuEnd,
- xref,
- ...NEW_OP,
- };
- }
- /**
- * Creates an ICU placeholder op.
- */
- function createIcuPlaceholderOp(xref, name, strings) {
- return {
- kind: OpKind.IcuPlaceholder,
- xref,
- name,
- strings,
- expressionPlaceholders: [],
- ...NEW_OP,
- };
- }
- function createI18nContextOp(contextKind, xref, i18nBlock, message, sourceSpan) {
- if (i18nBlock === null && contextKind !== I18nContextKind.Attr) {
- throw new Error('AssertionError: i18nBlock must be provided for non-attribute contexts.');
- }
- return {
- kind: OpKind.I18nContext,
- contextKind,
- xref,
- i18nBlock,
- message,
- sourceSpan,
- params: new Map(),
- postprocessingParams: new Map(),
- ...NEW_OP,
- };
- }
- function createI18nAttributesOp(xref, handle, target) {
- return {
- kind: OpKind.I18nAttributes,
- xref,
- handle,
- target,
- i18nAttributesConfig: null,
- ...NEW_OP,
- ...TRAIT_CONSUMES_SLOT,
- };
- }
- /** Create a `SourceLocationOp`. */
- function createSourceLocationOp(templatePath, locations) {
- return {
- kind: OpKind.SourceLocation,
- templatePath,
- locations,
- ...NEW_OP,
- };
- }
- function createHostPropertyOp(name, expression, isAnimationTrigger, i18nContext, securityContext, sourceSpan) {
- return {
- kind: OpKind.HostProperty,
- name,
- expression,
- isAnimationTrigger,
- i18nContext,
- securityContext,
- sanitizer: null,
- sourceSpan,
- ...TRAIT_CONSUMES_VARS,
- ...NEW_OP,
- };
- }
- /**
- * When referenced in the template's context parameters, this indicates a reference to the entire
- * context object, rather than a specific parameter.
- */
- const CTX_REF = 'CTX_REF_MARKER';
- var CompilationJobKind;
- (function (CompilationJobKind) {
- CompilationJobKind[CompilationJobKind["Tmpl"] = 0] = "Tmpl";
- CompilationJobKind[CompilationJobKind["Host"] = 1] = "Host";
- CompilationJobKind[CompilationJobKind["Both"] = 2] = "Both";
- })(CompilationJobKind || (CompilationJobKind = {}));
- /**
- * An entire ongoing compilation, which will result in one or more template functions when complete.
- * Contains one or more corresponding compilation units.
- */
- class CompilationJob {
- componentName;
- pool;
- compatibility;
- constructor(componentName, pool, compatibility) {
- this.componentName = componentName;
- this.pool = pool;
- this.compatibility = compatibility;
- }
- kind = CompilationJobKind.Both;
- /**
- * Generate a new unique `ir.XrefId` in this job.
- */
- allocateXrefId() {
- return this.nextXrefId++;
- }
- /**
- * Tracks the next `ir.XrefId` which can be assigned as template structures are ingested.
- */
- nextXrefId = 0;
- }
- /**
- * Compilation-in-progress of a whole component's template, including the main template and any
- * embedded views or host bindings.
- */
- class ComponentCompilationJob extends CompilationJob {
- relativeContextFilePath;
- i18nUseExternalIds;
- deferMeta;
- allDeferrableDepsFn;
- relativeTemplatePath;
- enableDebugLocations;
- constructor(componentName, pool, compatibility, relativeContextFilePath, i18nUseExternalIds, deferMeta, allDeferrableDepsFn, relativeTemplatePath, enableDebugLocations) {
- super(componentName, pool, compatibility);
- this.relativeContextFilePath = relativeContextFilePath;
- this.i18nUseExternalIds = i18nUseExternalIds;
- this.deferMeta = deferMeta;
- this.allDeferrableDepsFn = allDeferrableDepsFn;
- this.relativeTemplatePath = relativeTemplatePath;
- this.enableDebugLocations = enableDebugLocations;
- this.root = new ViewCompilationUnit(this, this.allocateXrefId(), null);
- this.views.set(this.root.xref, this.root);
- }
- kind = CompilationJobKind.Tmpl;
- fnSuffix = 'Template';
- /**
- * The root view, representing the component's template.
- */
- root;
- views = new Map();
- /**
- * Causes ngContentSelectors to be emitted, for content projection slots in the view. Possibly a
- * reference into the constant pool.
- */
- contentSelectors = null;
- /**
- * Add a `ViewCompilation` for a new embedded view to this compilation.
- */
- allocateView(parent) {
- const view = new ViewCompilationUnit(this, this.allocateXrefId(), parent);
- this.views.set(view.xref, view);
- return view;
- }
- get units() {
- return this.views.values();
- }
- /**
- * Add a constant `o.Expression` to the compilation and return its index in the `consts` array.
- */
- addConst(newConst, initializers) {
- for (let idx = 0; idx < this.consts.length; idx++) {
- if (this.consts[idx].isEquivalent(newConst)) {
- return idx;
- }
- }
- const idx = this.consts.length;
- this.consts.push(newConst);
- if (initializers) {
- this.constsInitializers.push(...initializers);
- }
- return idx;
- }
- /**
- * Constant expressions used by operations within this component's compilation.
- *
- * This will eventually become the `consts` array in the component definition.
- */
- consts = [];
- /**
- * Initialization statements needed to set up the consts.
- */
- constsInitializers = [];
- }
- /**
- * A compilation unit is compiled into a template function. Some example units are views and host
- * bindings.
- */
- class CompilationUnit {
- xref;
- constructor(xref) {
- this.xref = xref;
- }
- /**
- * List of creation operations for this view.
- *
- * Creation operations may internally contain other operations, including update operations.
- */
- create = new OpList();
- /**
- * List of update operations for this view.
- */
- update = new OpList();
- /**
- * Name of the function which will be generated for this unit.
- *
- * May be `null` if not yet determined.
- */
- fnName = null;
- /**
- * Number of variable slots used within this view, or `null` if variables have not yet been
- * counted.
- */
- vars = null;
- /**
- * Iterate over all `ir.Op`s within this view.
- *
- * Some operations may have child operations, which this iterator will visit.
- */
- *ops() {
- for (const op of this.create) {
- yield op;
- if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
- for (const listenerOp of op.handlerOps) {
- yield listenerOp;
- }
- }
- else if (op.kind === OpKind.RepeaterCreate && op.trackByOps !== null) {
- for (const trackOp of op.trackByOps) {
- yield trackOp;
- }
- }
- }
- for (const op of this.update) {
- yield op;
- }
- }
- }
- /**
- * Compilation-in-progress of an individual view within a template.
- */
- class ViewCompilationUnit extends CompilationUnit {
- job;
- parent;
- constructor(job, xref, parent) {
- super(xref);
- this.job = job;
- this.parent = parent;
- }
- /**
- * Map of declared variables available within this view to the property on the context object
- * which they alias.
- */
- contextVariables = new Map();
- /**
- * Set of aliases available within this view. An alias is a variable whose provided expression is
- * inlined at every location it is used. It may also depend on context variables, by name.
- */
- aliases = new Set();
- /**
- * Number of declaration slots used within this view, or `null` if slots have not yet been
- * allocated.
- */
- decls = null;
- }
- /**
- * Compilation-in-progress of a host binding, which contains a single unit for that host binding.
- */
- class HostBindingCompilationJob extends CompilationJob {
- constructor(componentName, pool, compatibility) {
- super(componentName, pool, compatibility);
- this.root = new HostBindingCompilationUnit(this);
- }
- kind = CompilationJobKind.Host;
- fnSuffix = 'HostBindings';
- root;
- get units() {
- return [this.root];
- }
- }
- class HostBindingCompilationUnit extends CompilationUnit {
- job;
- constructor(job) {
- super(0);
- this.job = job;
- }
- /**
- * Much like an element can have attributes, so can a host binding function.
- */
- attributes = null;
- }
- /**
- * Find any function calls to `$any`, excluding `this.$any`, and delete them, since they have no
- * runtime effects.
- */
- function deleteAnyCasts(job) {
- for (const unit of job.units) {
- for (const op of unit.ops()) {
- transformExpressionsInOp(op, removeAnys, VisitorContextFlag.None);
- }
- }
- }
- function removeAnys(e) {
- if (e instanceof InvokeFunctionExpr &&
- e.fn instanceof LexicalReadExpr &&
- e.fn.name === '$any') {
- if (e.args.length !== 1) {
- throw new Error('The $any builtin function expects exactly one argument.');
- }
- return e.args[0];
- }
- return e;
- }
- /**
- * Adds apply operations after i18n expressions.
- */
- function applyI18nExpressions(job) {
- const i18nContexts = new Map();
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.I18nContext) {
- i18nContexts.set(op.xref, op);
- }
- }
- }
- for (const unit of job.units) {
- for (const op of unit.update) {
- // Only add apply after expressions that are not followed by more expressions.
- if (op.kind === OpKind.I18nExpression && needsApplication(i18nContexts, op)) {
- // TODO: what should be the source span for the apply op?
- OpList.insertAfter(createI18nApplyOp(op.i18nOwner, op.handle, null), op);
- }
- }
- }
- }
- /**
- * Checks whether the given expression op needs to be followed with an apply op.
- */
- function needsApplication(i18nContexts, op) {
- // If the next op is not another expression, we need to apply.
- if (op.next?.kind !== OpKind.I18nExpression) {
- return true;
- }
- const context = i18nContexts.get(op.context);
- const nextContext = i18nContexts.get(op.next.context);
- if (context === undefined) {
- throw new Error("AssertionError: expected an I18nContextOp to exist for the I18nExpressionOp's context");
- }
- if (nextContext === undefined) {
- throw new Error("AssertionError: expected an I18nContextOp to exist for the next I18nExpressionOp's context");
- }
- // If the next op is an expression targeting a different i18n block (or different element, in the
- // case of i18n attributes), we need to apply.
- // First, handle the case of i18n blocks.
- if (context.i18nBlock !== null) {
- // This is a block context. Compare the blocks.
- if (context.i18nBlock !== nextContext.i18nBlock) {
- return true;
- }
- return false;
- }
- // Second, handle the case of i18n attributes.
- if (op.i18nOwner !== op.next.i18nOwner) {
- return true;
- }
- return false;
- }
- /**
- * Updates i18n expression ops to target the last slot in their owning i18n block, and moves them
- * after the last update instruction that depends on that slot.
- */
- function assignI18nSlotDependencies(job) {
- for (const unit of job.units) {
- // The first update op.
- let updateOp = unit.update.head;
- // I18n expressions currently being moved during the iteration.
- let i18nExpressionsInProgress = [];
- // Non-null while we are iterating through an i18nStart/i18nEnd pair
- let state = null;
- for (const createOp of unit.create) {
- if (createOp.kind === OpKind.I18nStart) {
- state = {
- blockXref: createOp.xref,
- lastSlotConsumer: createOp.xref,
- };
- }
- else if (createOp.kind === OpKind.I18nEnd) {
- for (const op of i18nExpressionsInProgress) {
- op.target = state.lastSlotConsumer;
- OpList.insertBefore(op, updateOp);
- }
- i18nExpressionsInProgress.length = 0;
- state = null;
- }
- if (hasConsumesSlotTrait(createOp)) {
- if (state !== null) {
- state.lastSlotConsumer = createOp.xref;
- }
- while (true) {
- if (updateOp.next === null) {
- break;
- }
- if (state !== null &&
- updateOp.kind === OpKind.I18nExpression &&
- updateOp.usage === I18nExpressionFor.I18nText &&
- updateOp.i18nOwner === state.blockXref) {
- const opToRemove = updateOp;
- updateOp = updateOp.next;
- OpList.remove(opToRemove);
- i18nExpressionsInProgress.push(opToRemove);
- continue;
- }
- if (hasDependsOnSlotContextTrait(updateOp) && updateOp.target !== createOp.xref) {
- break;
- }
- updateOp = updateOp.next;
- }
- }
- }
- }
- }
- /**
- * Gets a map of all elements in the given view by their xref id.
- */
- function createOpXrefMap(unit) {
- const map = new Map();
- for (const op of unit.create) {
- if (!hasConsumesSlotTrait(op)) {
- continue;
- }
- map.set(op.xref, op);
- // TODO(dylhunn): `@for` loops with `@empty` blocks need to be special-cased here,
- // because the slot consumer trait currently only supports one slot per consumer and we
- // need two. This should be revisited when making the refactors mentioned in:
- // https://github.com/angular/angular/pull/53620#discussion_r1430918822
- if (op.kind === OpKind.RepeaterCreate && op.emptyView !== null) {
- map.set(op.emptyView, op);
- }
- }
- return map;
- }
- /**
- * Find all extractable attribute and binding ops, and create ExtractedAttributeOps for them.
- * In cases where no instruction needs to be generated for the attribute or binding, it is removed.
- */
- function extractAttributes(job) {
- for (const unit of job.units) {
- const elements = createOpXrefMap(unit);
- for (const op of unit.ops()) {
- switch (op.kind) {
- case OpKind.Attribute:
- extractAttributeOp(unit, op, elements);
- break;
- case OpKind.Property:
- if (!op.isAnimationTrigger) {
- let bindingKind;
- if (op.i18nMessage !== null && op.templateKind === null) {
- // If the binding has an i18n context, it is an i18n attribute, and should have that
- // kind in the consts array.
- bindingKind = BindingKind.I18n;
- }
- else if (op.isStructuralTemplateAttribute) {
- bindingKind = BindingKind.Template;
- }
- else {
- bindingKind = BindingKind.Property;
- }
- OpList.insertBefore(
- // Deliberately null i18nMessage value
- createExtractedAttributeOp(op.target, bindingKind, null, op.name,
- /* expression */ null,
- /* i18nContext */ null,
- /* i18nMessage */ null, op.securityContext), lookupElement$2(elements, op.target));
- }
- break;
- case OpKind.TwoWayProperty:
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.TwoWayProperty, null, op.name,
- /* expression */ null,
- /* i18nContext */ null,
- /* i18nMessage */ null, op.securityContext), lookupElement$2(elements, op.target));
- break;
- case OpKind.StyleProp:
- case OpKind.ClassProp:
- // TODO: Can style or class bindings be i18n attributes?
- // The old compiler treated empty style bindings as regular bindings for the purpose of
- // directive matching. That behavior is incorrect, but we emulate it in compatibility
- // mode.
- if (unit.job.compatibility === CompatibilityMode.TemplateDefinitionBuilder &&
- op.expression instanceof EmptyExpr) {
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.Property, null, op.name,
- /* expression */ null,
- /* i18nContext */ null,
- /* i18nMessage */ null, SecurityContext.STYLE), lookupElement$2(elements, op.target));
- }
- break;
- case OpKind.Listener:
- if (!op.isAnimationListener) {
- const extractedAttributeOp = createExtractedAttributeOp(op.target, BindingKind.Property, null, op.name,
- /* expression */ null,
- /* i18nContext */ null,
- /* i18nMessage */ null, SecurityContext.NONE);
- if (job.kind === CompilationJobKind.Host) {
- if (job.compatibility) {
- // TemplateDefinitionBuilder does not extract listener bindings to the const array
- // (which is honestly pretty inconsistent).
- break;
- }
- // This attribute will apply to the enclosing host binding compilation unit, so order
- // doesn't matter.
- unit.create.push(extractedAttributeOp);
- }
- else {
- OpList.insertBefore(extractedAttributeOp, lookupElement$2(elements, op.target));
- }
- }
- break;
- case OpKind.TwoWayListener:
- // Two-way listeners aren't supported in host bindings.
- if (job.kind !== CompilationJobKind.Host) {
- const extractedAttributeOp = createExtractedAttributeOp(op.target, BindingKind.Property, null, op.name,
- /* expression */ null,
- /* i18nContext */ null,
- /* i18nMessage */ null, SecurityContext.NONE);
- OpList.insertBefore(extractedAttributeOp, lookupElement$2(elements, op.target));
- }
- break;
- }
- }
- }
- }
- /**
- * Looks up an element in the given map by xref ID.
- */
- function lookupElement$2(elements, xref) {
- const el = elements.get(xref);
- if (el === undefined) {
- throw new Error('All attributes should have an element-like target.');
- }
- return el;
- }
- /**
- * Extracts an attribute binding.
- */
- function extractAttributeOp(unit, op, elements) {
- if (op.expression instanceof Interpolation) {
- return;
- }
- let extractable = op.isTextAttribute || op.expression.isConstant();
- if (unit.job.compatibility === CompatibilityMode.TemplateDefinitionBuilder) {
- // TemplateDefinitionBuilder only extracts text attributes. It does not extract attriibute
- // bindings, even if they are constants.
- extractable &&= op.isTextAttribute;
- }
- if (extractable) {
- const extractedAttributeOp = createExtractedAttributeOp(op.target, op.isStructuralTemplateAttribute ? BindingKind.Template : BindingKind.Attribute, op.namespace, op.name, op.expression, op.i18nContext, op.i18nMessage, op.securityContext);
- if (unit.job.kind === CompilationJobKind.Host) {
- // This attribute will apply to the enclosing host binding compilation unit, so order doesn't
- // matter.
- unit.create.push(extractedAttributeOp);
- }
- else {
- const ownerOp = lookupElement$2(elements, op.target);
- OpList.insertBefore(extractedAttributeOp, ownerOp);
- }
- OpList.remove(op);
- }
- }
- /**
- * Looks up an element in the given map by xref ID.
- */
- function lookupElement$1(elements, xref) {
- const el = elements.get(xref);
- if (el === undefined) {
- throw new Error('All attributes should have an element-like target.');
- }
- return el;
- }
- function specializeBindings(job) {
- const elements = new Map();
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (!isElementOrContainerOp(op)) {
- continue;
- }
- elements.set(op.xref, op);
- }
- }
- for (const unit of job.units) {
- for (const op of unit.ops()) {
- if (op.kind !== OpKind.Binding) {
- continue;
- }
- switch (op.bindingKind) {
- case BindingKind.Attribute:
- if (op.name === 'ngNonBindable') {
- OpList.remove(op);
- const target = lookupElement$1(elements, op.target);
- target.nonBindable = true;
- }
- else {
- const [namespace, name] = splitNsName(op.name);
- OpList.replace(op, createAttributeOp(op.target, namespace, name, op.expression, op.securityContext, op.isTextAttribute, op.isStructuralTemplateAttribute, op.templateKind, op.i18nMessage, op.sourceSpan));
- }
- break;
- case BindingKind.Property:
- case BindingKind.Animation:
- if (job.kind === CompilationJobKind.Host) {
- OpList.replace(op, createHostPropertyOp(op.name, op.expression, op.bindingKind === BindingKind.Animation, op.i18nContext, op.securityContext, op.sourceSpan));
- }
- else {
- OpList.replace(op, createPropertyOp(op.target, op.name, op.expression, op.bindingKind === BindingKind.Animation, op.securityContext, op.isStructuralTemplateAttribute, op.templateKind, op.i18nContext, op.i18nMessage, op.sourceSpan));
- }
- break;
- case BindingKind.TwoWayProperty:
- if (!(op.expression instanceof Expression)) {
- // We shouldn't be able to hit this code path since interpolations in two-way bindings
- // result in a parser error. We assert here so that downstream we can assume that
- // the value is always an expression.
- throw new Error(`Expected value of two-way property binding "${op.name}" to be an expression`);
- }
- OpList.replace(op, createTwoWayPropertyOp(op.target, op.name, op.expression, op.securityContext, op.isStructuralTemplateAttribute, op.templateKind, op.i18nContext, op.i18nMessage, op.sourceSpan));
- break;
- case BindingKind.I18n:
- case BindingKind.ClassName:
- case BindingKind.StyleProperty:
- throw new Error(`Unhandled binding of kind ${BindingKind[op.bindingKind]}`);
- }
- }
- }
- }
- const CHAINABLE = new Set([
- Identifiers.attribute,
- Identifiers.classProp,
- Identifiers.element,
- Identifiers.elementContainer,
- Identifiers.elementContainerEnd,
- Identifiers.elementContainerStart,
- Identifiers.elementEnd,
- Identifiers.elementStart,
- Identifiers.hostProperty,
- Identifiers.i18nExp,
- Identifiers.listener,
- Identifiers.listener,
- Identifiers.property,
- Identifiers.styleProp,
- Identifiers.stylePropInterpolate1,
- Identifiers.stylePropInterpolate2,
- Identifiers.stylePropInterpolate3,
- Identifiers.stylePropInterpolate4,
- Identifiers.stylePropInterpolate5,
- Identifiers.stylePropInterpolate6,
- Identifiers.stylePropInterpolate7,
- Identifiers.stylePropInterpolate8,
- Identifiers.stylePropInterpolateV,
- Identifiers.syntheticHostListener,
- Identifiers.syntheticHostProperty,
- Identifiers.templateCreate,
- Identifiers.twoWayProperty,
- Identifiers.twoWayListener,
- Identifiers.declareLet,
- ]);
- /**
- * Chaining results in repeated call expressions, causing a deep AST of receiver expressions. To prevent running out of
- * stack depth the maximum number of chained instructions is limited to this threshold, which has been selected
- * arbitrarily.
- */
- const MAX_CHAIN_LENGTH = 256;
- /**
- * Post-process a reified view compilation and convert sequential calls to chainable instructions
- * into chain calls.
- *
- * For example, two `elementStart` operations in sequence:
- *
- * ```ts
- * elementStart(0, 'div');
- * elementStart(1, 'span');
- * ```
- *
- * Can be called as a chain instead:
- *
- * ```ts
- * elementStart(0, 'div')(1, 'span');
- * ```
- */
- function chain(job) {
- for (const unit of job.units) {
- chainOperationsInList(unit.create);
- chainOperationsInList(unit.update);
- }
- }
- function chainOperationsInList(opList) {
- let chain = null;
- for (const op of opList) {
- if (op.kind !== OpKind.Statement || !(op.statement instanceof ExpressionStatement)) {
- // This type of statement isn't chainable.
- chain = null;
- continue;
- }
- if (!(op.statement.expr instanceof InvokeFunctionExpr) ||
- !(op.statement.expr.fn instanceof ExternalExpr)) {
- // This is a statement, but not an instruction-type call, so not chainable.
- chain = null;
- continue;
- }
- const instruction = op.statement.expr.fn.value;
- if (!CHAINABLE.has(instruction)) {
- // This instruction isn't chainable.
- chain = null;
- continue;
- }
- // This instruction can be chained. It can either be added on to the previous chain (if
- // compatible) or it can be the start of a new chain.
- if (chain !== null && chain.instruction === instruction && chain.length < MAX_CHAIN_LENGTH) {
- // This instruction can be added onto the previous chain.
- const expression = chain.expression.callFn(op.statement.expr.args, op.statement.expr.sourceSpan, op.statement.expr.pure);
- chain.expression = expression;
- chain.op.statement = expression.toStmt();
- chain.length++;
- OpList.remove(op);
- }
- else {
- // Leave this instruction alone for now, but consider it the start of a new chain.
- chain = {
- op,
- instruction,
- expression: op.statement.expr,
- length: 1,
- };
- }
- }
- }
- /**
- * Attribute interpolations of the form `[attr.foo]="{{foo}}""` should be "collapsed" into a plain
- * attribute instruction, instead of an `attributeInterpolate` instruction.
- *
- * (We cannot do this for singleton property interpolations, because `propertyInterpolate`
- * stringifies its expression.)
- *
- * The reification step is also capable of performing this transformation, but doing it early in the
- * pipeline allows other phases to accurately know what instruction will be emitted.
- */
- function collapseSingletonInterpolations(job) {
- for (const unit of job.units) {
- for (const op of unit.update) {
- const eligibleOpKind = op.kind === OpKind.Attribute;
- if (eligibleOpKind &&
- op.expression instanceof Interpolation &&
- op.expression.strings.length === 2 &&
- op.expression.strings.every((s) => s === '')) {
- op.expression = op.expression.expressions[0];
- }
- }
- }
- }
- /**
- * Collapse the various conditions of conditional ops (if, switch) into a single test expression.
- */
- function generateConditionalExpressions(job) {
- for (const unit of job.units) {
- for (const op of unit.ops()) {
- if (op.kind !== OpKind.Conditional) {
- continue;
- }
- let test;
- // Any case with a `null` condition is `default`. If one exists, default to it instead.
- const defaultCase = op.conditions.findIndex((cond) => cond.expr === null);
- if (defaultCase >= 0) {
- const slot = op.conditions.splice(defaultCase, 1)[0].targetSlot;
- test = new SlotLiteralExpr(slot);
- }
- else {
- // By default, a switch evaluates to `-1`, causing no template to be displayed.
- test = literal$1(-1);
- }
- // Switch expressions assign their main test to a temporary, to avoid re-executing it.
- let tmp = op.test == null ? null : new AssignTemporaryExpr(op.test, job.allocateXrefId());
- // For each remaining condition, test whether the temporary satifies the check. (If no temp is
- // present, just check each expression directly.)
- for (let i = op.conditions.length - 1; i >= 0; i--) {
- let conditionalCase = op.conditions[i];
- if (conditionalCase.expr === null) {
- continue;
- }
- if (tmp !== null) {
- const useTmp = i === 0 ? tmp : new ReadTemporaryExpr(tmp.xref);
- conditionalCase.expr = new BinaryOperatorExpr(BinaryOperator.Identical, useTmp, conditionalCase.expr);
- }
- else if (conditionalCase.alias !== null) {
- const caseExpressionTemporaryXref = job.allocateXrefId();
- conditionalCase.expr = new AssignTemporaryExpr(conditionalCase.expr, caseExpressionTemporaryXref);
- op.contextValue = new ReadTemporaryExpr(caseExpressionTemporaryXref);
- }
- test = new ConditionalExpr(conditionalCase.expr, new SlotLiteralExpr(conditionalCase.targetSlot), test);
- }
- // Save the resulting aggregate Joost-expression.
- op.processed = test;
- // Clear the original conditions array, since we no longer need it, and don't want it to
- // affect subsequent phases (e.g. pipe creation).
- op.conditions = [];
- }
- }
- }
- const BINARY_OPERATORS$3 = new Map([
- ['&&', BinaryOperator.And],
- ['>', BinaryOperator.Bigger],
- ['>=', BinaryOperator.BiggerEquals],
- ['|', BinaryOperator.BitwiseOr],
- ['&', BinaryOperator.BitwiseAnd],
- ['/', BinaryOperator.Divide],
- ['==', BinaryOperator.Equals],
- ['===', BinaryOperator.Identical],
- ['<', BinaryOperator.Lower],
- ['<=', BinaryOperator.LowerEquals],
- ['-', BinaryOperator.Minus],
- ['%', BinaryOperator.Modulo],
- ['*', BinaryOperator.Multiply],
- ['!=', BinaryOperator.NotEquals],
- ['!==', BinaryOperator.NotIdentical],
- ['??', BinaryOperator.NullishCoalesce],
- ['||', BinaryOperator.Or],
- ['+', BinaryOperator.Plus],
- ]);
- function namespaceForKey(namespacePrefixKey) {
- const NAMESPACES = new Map([
- ['svg', Namespace.SVG],
- ['math', Namespace.Math],
- ]);
- if (namespacePrefixKey === null) {
- return Namespace.HTML;
- }
- return NAMESPACES.get(namespacePrefixKey) ?? Namespace.HTML;
- }
- function keyForNamespace(namespace) {
- const NAMESPACES = new Map([
- ['svg', Namespace.SVG],
- ['math', Namespace.Math],
- ]);
- for (const [k, n] of NAMESPACES.entries()) {
- if (n === namespace) {
- return k;
- }
- }
- return null; // No namespace prefix for HTML
- }
- function prefixWithNamespace(strippedTag, namespace) {
- if (namespace === Namespace.HTML) {
- return strippedTag;
- }
- return `:${keyForNamespace(namespace)}:${strippedTag}`;
- }
- function literalOrArrayLiteral(value) {
- if (Array.isArray(value)) {
- return literalArr(value.map(literalOrArrayLiteral));
- }
- return literal$1(value);
- }
- /**
- * Converts the semantic attributes of element-like operations (elements, templates) into constant
- * array expressions, and lifts them into the overall component `consts`.
- */
- function collectElementConsts(job) {
- // Collect all extracted attributes.
- const allElementAttributes = new Map();
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.ExtractedAttribute) {
- const attributes = allElementAttributes.get(op.target) || new ElementAttributes(job.compatibility);
- allElementAttributes.set(op.target, attributes);
- attributes.add(op.bindingKind, op.name, op.expression, op.namespace, op.trustedValueFn);
- OpList.remove(op);
- }
- }
- }
- // Serialize the extracted attributes into the const array.
- if (job instanceof ComponentCompilationJob) {
- for (const unit of job.units) {
- for (const op of unit.create) {
- // TODO: Simplify and combine these cases.
- if (op.kind == OpKind.Projection) {
- const attributes = allElementAttributes.get(op.xref);
- if (attributes !== undefined) {
- const attrArray = serializeAttributes(attributes);
- if (attrArray.entries.length > 0) {
- op.attributes = attrArray;
- }
- }
- }
- else if (isElementOrContainerOp(op)) {
- op.attributes = getConstIndex(job, allElementAttributes, op.xref);
- // TODO(dylhunn): `@for` loops with `@empty` blocks need to be special-cased here,
- // because the slot consumer trait currently only supports one slot per consumer and we
- // need two. This should be revisited when making the refactors mentioned in:
- // https://github.com/angular/angular/pull/53620#discussion_r1430918822
- if (op.kind === OpKind.RepeaterCreate && op.emptyView !== null) {
- op.emptyAttributes = getConstIndex(job, allElementAttributes, op.emptyView);
- }
- }
- }
- }
- }
- else if (job instanceof HostBindingCompilationJob) {
- // TODO: If the host binding case further diverges, we may want to split it into its own
- // phase.
- for (const [xref, attributes] of allElementAttributes.entries()) {
- if (xref !== job.root.xref) {
- throw new Error(`An attribute would be const collected into the host binding's template function, but is not associated with the root xref.`);
- }
- const attrArray = serializeAttributes(attributes);
- if (attrArray.entries.length > 0) {
- job.root.attributes = attrArray;
- }
- }
- }
- }
- function getConstIndex(job, allElementAttributes, xref) {
- const attributes = allElementAttributes.get(xref);
- if (attributes !== undefined) {
- const attrArray = serializeAttributes(attributes);
- if (attrArray.entries.length > 0) {
- return job.addConst(attrArray);
- }
- }
- return null;
- }
- /**
- * Shared instance of an empty array to avoid unnecessary array allocations.
- */
- const FLYWEIGHT_ARRAY = Object.freeze([]);
- /**
- * Container for all of the various kinds of attributes which are applied on an element.
- */
- class ElementAttributes {
- compatibility;
- known = new Map();
- byKind = new Map();
- propertyBindings = null;
- projectAs = null;
- get attributes() {
- return this.byKind.get(BindingKind.Attribute) ?? FLYWEIGHT_ARRAY;
- }
- get classes() {
- return this.byKind.get(BindingKind.ClassName) ?? FLYWEIGHT_ARRAY;
- }
- get styles() {
- return this.byKind.get(BindingKind.StyleProperty) ?? FLYWEIGHT_ARRAY;
- }
- get bindings() {
- return this.propertyBindings ?? FLYWEIGHT_ARRAY;
- }
- get template() {
- return this.byKind.get(BindingKind.Template) ?? FLYWEIGHT_ARRAY;
- }
- get i18n() {
- return this.byKind.get(BindingKind.I18n) ?? FLYWEIGHT_ARRAY;
- }
- constructor(compatibility) {
- this.compatibility = compatibility;
- }
- isKnown(kind, name) {
- const nameToValue = this.known.get(kind) ?? new Set();
- this.known.set(kind, nameToValue);
- if (nameToValue.has(name)) {
- return true;
- }
- nameToValue.add(name);
- return false;
- }
- add(kind, name, value, namespace, trustedValueFn) {
- // TemplateDefinitionBuilder puts duplicate attribute, class, and style values into the consts
- // array. This seems inefficient, we can probably keep just the first one or the last value
- // (whichever actually gets applied when multiple values are listed for the same attribute).
- const allowDuplicates = this.compatibility === CompatibilityMode.TemplateDefinitionBuilder &&
- (kind === BindingKind.Attribute ||
- kind === BindingKind.ClassName ||
- kind === BindingKind.StyleProperty);
- if (!allowDuplicates && this.isKnown(kind, name)) {
- return;
- }
- // TODO: Can this be its own phase
- if (name === 'ngProjectAs') {
- if (value === null ||
- !(value instanceof LiteralExpr) ||
- value.value == null ||
- typeof value.value?.toString() !== 'string') {
- throw Error('ngProjectAs must have a string literal value');
- }
- this.projectAs = value.value.toString();
- // TODO: TemplateDefinitionBuilder allows `ngProjectAs` to also be assigned as a literal
- // attribute. Is this sane?
- }
- const array = this.arrayFor(kind);
- array.push(...getAttributeNameLiterals(namespace, name));
- if (kind === BindingKind.Attribute || kind === BindingKind.StyleProperty) {
- if (value === null) {
- throw Error('Attribute, i18n attribute, & style element attributes must have a value');
- }
- if (trustedValueFn !== null) {
- if (!isStringLiteral(value)) {
- throw Error('AssertionError: extracted attribute value should be string literal');
- }
- array.push(taggedTemplate(trustedValueFn, new TemplateLiteralExpr([new TemplateLiteralElementExpr(value.value)], []), undefined, value.sourceSpan));
- }
- else {
- array.push(value);
- }
- }
- }
- arrayFor(kind) {
- if (kind === BindingKind.Property || kind === BindingKind.TwoWayProperty) {
- this.propertyBindings ??= [];
- return this.propertyBindings;
- }
- else {
- if (!this.byKind.has(kind)) {
- this.byKind.set(kind, []);
- }
- return this.byKind.get(kind);
- }
- }
- }
- /**
- * Gets an array of literal expressions representing the attribute's namespaced name.
- */
- function getAttributeNameLiterals(namespace, name) {
- const nameLiteral = literal$1(name);
- if (namespace) {
- return [literal$1(0 /* core.AttributeMarker.NamespaceURI */), literal$1(namespace), nameLiteral];
- }
- return [nameLiteral];
- }
- /**
- * Serializes an ElementAttributes object into an array expression.
- */
- function serializeAttributes({ attributes, bindings, classes, i18n, projectAs, styles, template, }) {
- const attrArray = [...attributes];
- if (projectAs !== null) {
- // Parse the attribute value into a CssSelectorList. Note that we only take the
- // first selector, because we don't support multiple selectors in ngProjectAs.
- const parsedR3Selector = parseSelectorToR3Selector(projectAs)[0];
- attrArray.push(literal$1(5 /* core.AttributeMarker.ProjectAs */), literalOrArrayLiteral(parsedR3Selector));
- }
- if (classes.length > 0) {
- attrArray.push(literal$1(1 /* core.AttributeMarker.Classes */), ...classes);
- }
- if (styles.length > 0) {
- attrArray.push(literal$1(2 /* core.AttributeMarker.Styles */), ...styles);
- }
- if (bindings.length > 0) {
- attrArray.push(literal$1(3 /* core.AttributeMarker.Bindings */), ...bindings);
- }
- if (template.length > 0) {
- attrArray.push(literal$1(4 /* core.AttributeMarker.Template */), ...template);
- }
- if (i18n.length > 0) {
- attrArray.push(literal$1(6 /* core.AttributeMarker.I18n */), ...i18n);
- }
- return literalArr(attrArray);
- }
- /**
- * Some binding instructions in the update block may actually correspond to i18n bindings. In that
- * case, they should be replaced with i18nExp instructions for the dynamic portions.
- */
- function convertI18nBindings(job) {
- const i18nAttributesByElem = new Map();
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.I18nAttributes) {
- i18nAttributesByElem.set(op.target, op);
- }
- }
- for (const op of unit.update) {
- switch (op.kind) {
- case OpKind.Property:
- case OpKind.Attribute:
- if (op.i18nContext === null) {
- continue;
- }
- if (!(op.expression instanceof Interpolation)) {
- continue;
- }
- const i18nAttributesForElem = i18nAttributesByElem.get(op.target);
- if (i18nAttributesForElem === undefined) {
- throw new Error('AssertionError: An i18n attribute binding instruction requires the owning element to have an I18nAttributes create instruction');
- }
- if (i18nAttributesForElem.target !== op.target) {
- throw new Error('AssertionError: Expected i18nAttributes target element to match binding target element');
- }
- const ops = [];
- for (let i = 0; i < op.expression.expressions.length; i++) {
- const expr = op.expression.expressions[i];
- if (op.expression.i18nPlaceholders.length !== op.expression.expressions.length) {
- throw new Error(`AssertionError: An i18n attribute binding instruction requires the same number of expressions and placeholders, but found ${op.expression.i18nPlaceholders.length} placeholders and ${op.expression.expressions.length} expressions`);
- }
- ops.push(createI18nExpressionOp(op.i18nContext, i18nAttributesForElem.target, i18nAttributesForElem.xref, i18nAttributesForElem.handle, expr, null, op.expression.i18nPlaceholders[i], I18nParamResolutionTime.Creation, I18nExpressionFor.I18nAttribute, op.name, op.sourceSpan));
- }
- OpList.replaceWithMany(op, ops);
- break;
- }
- }
- }
- }
- /**
- * Resolve the dependency function of a deferred block.
- */
- function resolveDeferDepsFns(job) {
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.Defer) {
- if (op.resolverFn !== null) {
- continue;
- }
- if (op.ownResolverFn !== null) {
- if (op.handle.slot === null) {
- throw new Error('AssertionError: slot must be assigned before extracting defer deps functions');
- }
- const fullPathName = unit.fnName?.replace('_Template', '');
- op.resolverFn = job.pool.getSharedFunctionReference(op.ownResolverFn, `${fullPathName}_Defer_${op.handle.slot}_DepsFn`,
- /* Don't use unique names for TDB compatibility */ false);
- }
- }
- }
- }
- }
- /**
- * Create one helper context op per i18n block (including generate descending blocks).
- *
- * Also, if an ICU exists inside an i18n block that also contains other localizable content (such as
- * string), create an additional helper context op for the ICU.
- *
- * These context ops are later used for generating i18n messages. (Although we generate at least one
- * context op per nested view, we will collect them up the tree later, to generate a top-level
- * message.)
- */
- function createI18nContexts(job) {
- // Create i18n context ops for i18n attrs.
- const attrContextByMessage = new Map();
- for (const unit of job.units) {
- for (const op of unit.ops()) {
- switch (op.kind) {
- case OpKind.Binding:
- case OpKind.Property:
- case OpKind.Attribute:
- case OpKind.ExtractedAttribute:
- if (op.i18nMessage === null) {
- continue;
- }
- if (!attrContextByMessage.has(op.i18nMessage)) {
- const i18nContext = createI18nContextOp(I18nContextKind.Attr, job.allocateXrefId(), null, op.i18nMessage, null);
- unit.create.push(i18nContext);
- attrContextByMessage.set(op.i18nMessage, i18nContext.xref);
- }
- op.i18nContext = attrContextByMessage.get(op.i18nMessage);
- break;
- }
- }
- }
- // Create i18n context ops for root i18n blocks.
- const blockContextByI18nBlock = new Map();
- for (const unit of job.units) {
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nStart:
- if (op.xref === op.root) {
- const contextOp = createI18nContextOp(I18nContextKind.RootI18n, job.allocateXrefId(), op.xref, op.message, null);
- unit.create.push(contextOp);
- op.context = contextOp.xref;
- blockContextByI18nBlock.set(op.xref, contextOp);
- }
- break;
- }
- }
- }
- // Assign i18n contexts for child i18n blocks. These don't need their own conext, instead they
- // should inherit from their root i18n block.
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.I18nStart && op.xref !== op.root) {
- const rootContext = blockContextByI18nBlock.get(op.root);
- if (rootContext === undefined) {
- throw Error('AssertionError: Root i18n block i18n context should have been created.');
- }
- op.context = rootContext.xref;
- blockContextByI18nBlock.set(op.xref, rootContext);
- }
- }
- }
- // Create or assign i18n contexts for ICUs.
- let currentI18nOp = null;
- for (const unit of job.units) {
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nStart:
- currentI18nOp = op;
- break;
- case OpKind.I18nEnd:
- currentI18nOp = null;
- break;
- case OpKind.IcuStart:
- if (currentI18nOp === null) {
- throw Error('AssertionError: Unexpected ICU outside of an i18n block.');
- }
- if (op.message.id !== currentI18nOp.message.id) {
- // This ICU is a sub-message inside its parent i18n block message. We need to give it
- // its own context.
- const contextOp = createI18nContextOp(I18nContextKind.Icu, job.allocateXrefId(), currentI18nOp.root, op.message, null);
- unit.create.push(contextOp);
- op.context = contextOp.xref;
- }
- else {
- // This ICU is the only translatable content in its parent i18n block. We need to
- // convert the parent's context into an ICU context.
- op.context = currentI18nOp.context;
- blockContextByI18nBlock.get(currentI18nOp.xref).contextKind = I18nContextKind.Icu;
- }
- break;
- }
- }
- }
- }
- /**
- * Deduplicate text bindings, e.g. <div class="cls1" class="cls2">
- */
- function deduplicateTextBindings(job) {
- const seen = new Map();
- for (const unit of job.units) {
- for (const op of unit.update.reversed()) {
- if (op.kind === OpKind.Binding && op.isTextAttribute) {
- const seenForElement = seen.get(op.target) || new Set();
- if (seenForElement.has(op.name)) {
- if (job.compatibility === CompatibilityMode.TemplateDefinitionBuilder) {
- // For most duplicated attributes, TemplateDefinitionBuilder lists all of the values in
- // the consts array. However, for style and class attributes it only keeps the last one.
- // We replicate that behavior here since it has actual consequences for apps with
- // duplicate class or style attrs.
- if (op.name === 'style' || op.name === 'class') {
- OpList.remove(op);
- }
- }
- }
- seenForElement.add(op.name);
- seen.set(op.target, seenForElement);
- }
- }
- }
- }
- /**
- * Defer instructions take a configuration array, which should be collected into the component
- * consts. This phase finds the config options, and creates the corresponding const array.
- */
- function configureDeferInstructions(job) {
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind !== OpKind.Defer) {
- continue;
- }
- if (op.placeholderMinimumTime !== null) {
- op.placeholderConfig = new ConstCollectedExpr(literalOrArrayLiteral([op.placeholderMinimumTime]));
- }
- if (op.loadingMinimumTime !== null || op.loadingAfterTime !== null) {
- op.loadingConfig = new ConstCollectedExpr(literalOrArrayLiteral([op.loadingMinimumTime, op.loadingAfterTime]));
- }
- }
- }
- }
- /**
- * Some `defer` conditions can reference other elements in the template, using their local reference
- * names. However, the semantics are quite different from the normal local reference system: in
- * particular, we need to look at local reference names in enclosing views. This phase resolves
- * all such references to actual xrefs.
- */
- function resolveDeferTargetNames(job) {
- const scopes = new Map();
- function getScopeForView(view) {
- if (scopes.has(view.xref)) {
- return scopes.get(view.xref);
- }
- const scope = new Scope$2();
- for (const op of view.create) {
- // add everything that can be referenced.
- if (!isElementOrContainerOp(op) || op.localRefs === null) {
- continue;
- }
- if (!Array.isArray(op.localRefs)) {
- throw new Error('LocalRefs were already processed, but were needed to resolve defer targets.');
- }
- for (const ref of op.localRefs) {
- if (ref.target !== '') {
- continue;
- }
- scope.targets.set(ref.name, { xref: op.xref, slot: op.handle });
- }
- }
- scopes.set(view.xref, scope);
- return scope;
- }
- function resolveTrigger(deferOwnerView, op, placeholderView) {
- switch (op.trigger.kind) {
- case DeferTriggerKind.Idle:
- case DeferTriggerKind.Never:
- case DeferTriggerKind.Immediate:
- case DeferTriggerKind.Timer:
- return;
- case DeferTriggerKind.Hover:
- case DeferTriggerKind.Interaction:
- case DeferTriggerKind.Viewport:
- if (op.trigger.targetName === null) {
- // A `null` target name indicates we should default to the first element in the
- // placeholder block.
- if (placeholderView === null) {
- throw new Error('defer on trigger with no target name must have a placeholder block');
- }
- const placeholder = job.views.get(placeholderView);
- if (placeholder == undefined) {
- throw new Error('AssertionError: could not find placeholder view for defer on trigger');
- }
- for (const placeholderOp of placeholder.create) {
- if (hasConsumesSlotTrait(placeholderOp) &&
- (isElementOrContainerOp(placeholderOp) ||
- placeholderOp.kind === OpKind.Projection)) {
- op.trigger.targetXref = placeholderOp.xref;
- op.trigger.targetView = placeholderView;
- op.trigger.targetSlotViewSteps = -1;
- op.trigger.targetSlot = placeholderOp.handle;
- return;
- }
- }
- return;
- }
- let view = placeholderView !== null ? job.views.get(placeholderView) : deferOwnerView;
- let step = placeholderView !== null ? -1 : 0;
- while (view !== null) {
- const scope = getScopeForView(view);
- if (scope.targets.has(op.trigger.targetName)) {
- const { xref, slot } = scope.targets.get(op.trigger.targetName);
- op.trigger.targetXref = xref;
- op.trigger.targetView = view.xref;
- op.trigger.targetSlotViewSteps = step;
- op.trigger.targetSlot = slot;
- return;
- }
- view = view.parent !== null ? job.views.get(view.parent) : null;
- step++;
- }
- break;
- default:
- throw new Error(`Trigger kind ${op.trigger.kind} not handled`);
- }
- }
- // Find the defer ops, and assign the data about their targets.
- for (const unit of job.units) {
- const defers = new Map();
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.Defer:
- defers.set(op.xref, op);
- break;
- case OpKind.DeferOn:
- const deferOp = defers.get(op.defer);
- resolveTrigger(unit, op, op.modifier === "hydrate" /* ir.DeferOpModifierKind.HYDRATE */
- ? deferOp.mainView
- : deferOp.placeholderView);
- break;
- }
- }
- }
- }
- let Scope$2 = class Scope {
- targets = new Map();
- };
- const REPLACEMENTS = new Map([
- [OpKind.ElementEnd, [OpKind.ElementStart, OpKind.Element]],
- [OpKind.ContainerEnd, [OpKind.ContainerStart, OpKind.Container]],
- [OpKind.I18nEnd, [OpKind.I18nStart, OpKind.I18n]],
- ]);
- /**
- * Op kinds that should not prevent merging of start/end ops.
- */
- const IGNORED_OP_KINDS = new Set([OpKind.Pipe]);
- /**
- * Replace sequences of mergable instructions (e.g. `ElementStart` and `ElementEnd`) with a
- * consolidated instruction (e.g. `Element`).
- */
- function collapseEmptyInstructions(job) {
- for (const unit of job.units) {
- for (const op of unit.create) {
- // Find end ops that may be able to be merged.
- const opReplacements = REPLACEMENTS.get(op.kind);
- if (opReplacements === undefined) {
- continue;
- }
- const [startKind, mergedKind] = opReplacements;
- // Locate the previous (non-ignored) op.
- let prevOp = op.prev;
- while (prevOp !== null && IGNORED_OP_KINDS.has(prevOp.kind)) {
- prevOp = prevOp.prev;
- }
- // If the previous op is the corresponding start op, we can megre.
- if (prevOp !== null && prevOp.kind === startKind) {
- // Transmute the start instruction to the merged version. This is safe as they're designed
- // to be identical apart from the `kind`.
- prevOp.kind = mergedKind;
- // Remove the end instruction.
- OpList.remove(op);
- }
- }
- }
- }
- /**
- * Safe read expressions such as `a?.b` have different semantics in Angular templates as
- * compared to JavaScript. In particular, they default to `null` instead of `undefined`. This phase
- * finds all unresolved safe read expressions, and converts them into the appropriate output AST
- * reads, guarded by null checks. We generate temporaries as needed, to avoid re-evaluating the same
- * sub-expression multiple times.
- */
- function expandSafeReads(job) {
- for (const unit of job.units) {
- for (const op of unit.ops()) {
- transformExpressionsInOp(op, (e) => safeTransform(e, { job }), VisitorContextFlag.None);
- transformExpressionsInOp(op, ternaryTransform, VisitorContextFlag.None);
- }
- }
- }
- // A lookup set of all the expression kinds that require a temporary variable to be generated.
- [
- InvokeFunctionExpr,
- LiteralArrayExpr,
- LiteralMapExpr,
- SafeInvokeFunctionExpr,
- PipeBindingExpr,
- ].map((e) => e.constructor.name);
- function needsTemporaryInSafeAccess(e) {
- // TODO: We probably want to use an expression visitor to recursively visit all descendents.
- // However, that would potentially do a lot of extra work (because it cannot short circuit), so we
- // implement the logic ourselves for now.
- if (e instanceof UnaryOperatorExpr) {
- return needsTemporaryInSafeAccess(e.expr);
- }
- else if (e instanceof BinaryOperatorExpr) {
- return needsTemporaryInSafeAccess(e.lhs) || needsTemporaryInSafeAccess(e.rhs);
- }
- else if (e instanceof ConditionalExpr) {
- if (e.falseCase && needsTemporaryInSafeAccess(e.falseCase))
- return true;
- return needsTemporaryInSafeAccess(e.condition) || needsTemporaryInSafeAccess(e.trueCase);
- }
- else if (e instanceof NotExpr) {
- return needsTemporaryInSafeAccess(e.condition);
- }
- else if (e instanceof AssignTemporaryExpr) {
- return needsTemporaryInSafeAccess(e.expr);
- }
- else if (e instanceof ReadPropExpr) {
- return needsTemporaryInSafeAccess(e.receiver);
- }
- else if (e instanceof ReadKeyExpr) {
- return needsTemporaryInSafeAccess(e.receiver) || needsTemporaryInSafeAccess(e.index);
- }
- // TODO: Switch to a method which is exhaustive of newly added expression subtypes.
- return (e instanceof InvokeFunctionExpr ||
- e instanceof LiteralArrayExpr ||
- e instanceof LiteralMapExpr ||
- e instanceof SafeInvokeFunctionExpr ||
- e instanceof PipeBindingExpr);
- }
- function temporariesIn(e) {
- const temporaries = new Set();
- // TODO: Although it's not currently supported by the transform helper, we should be able to
- // short-circuit exploring the tree to do less work. In particular, we don't have to penetrate
- // into the subexpressions of temporary assignments.
- transformExpressionsInExpression(e, (e) => {
- if (e instanceof AssignTemporaryExpr) {
- temporaries.add(e.xref);
- }
- return e;
- }, VisitorContextFlag.None);
- return temporaries;
- }
- function eliminateTemporaryAssignments(e, tmps, ctx) {
- // TODO: We can be more efficient than the transform helper here. We don't need to visit any
- // descendents of temporary assignments.
- transformExpressionsInExpression(e, (e) => {
- if (e instanceof AssignTemporaryExpr && tmps.has(e.xref)) {
- const read = new ReadTemporaryExpr(e.xref);
- // `TemplateDefinitionBuilder` has the (accidental?) behavior of generating assignments of
- // temporary variables to themselves. This happens because some subexpression that the
- // temporary refers to, possibly through nested temporaries, has a function call. We copy that
- // behavior here.
- return ctx.job.compatibility === CompatibilityMode.TemplateDefinitionBuilder
- ? new AssignTemporaryExpr(read, read.xref)
- : read;
- }
- return e;
- }, VisitorContextFlag.None);
- return e;
- }
- /**
- * Creates a safe ternary guarded by the input expression, and with a body generated by the provided
- * callback on the input expression. Generates a temporary variable assignment if needed, and
- * deduplicates nested temporary assignments if needed.
- */
- function safeTernaryWithTemporary(guard, body, ctx) {
- let result;
- if (needsTemporaryInSafeAccess(guard)) {
- const xref = ctx.job.allocateXrefId();
- result = [new AssignTemporaryExpr(guard, xref), new ReadTemporaryExpr(xref)];
- }
- else {
- result = [guard, guard.clone()];
- // Consider an expression like `a?.[b?.c()]?.d`. The `b?.c()` will be transformed first,
- // introducing a temporary assignment into the key. Then, as part of expanding the `?.d`. That
- // assignment will be duplicated into both the guard and expression sides. We de-duplicate it,
- // by transforming it from an assignment into a read on the expression side.
- eliminateTemporaryAssignments(result[1], temporariesIn(result[0]), ctx);
- }
- return new SafeTernaryExpr(result[0], body(result[1]));
- }
- function isSafeAccessExpression(e) {
- return (e instanceof SafePropertyReadExpr ||
- e instanceof SafeKeyedReadExpr ||
- e instanceof SafeInvokeFunctionExpr);
- }
- function isUnsafeAccessExpression(e) {
- return (e instanceof ReadPropExpr || e instanceof ReadKeyExpr || e instanceof InvokeFunctionExpr);
- }
- function isAccessExpression$1(e) {
- return isSafeAccessExpression(e) || isUnsafeAccessExpression(e);
- }
- function deepestSafeTernary(e) {
- if (isAccessExpression$1(e) && e.receiver instanceof SafeTernaryExpr) {
- let st = e.receiver;
- while (st.expr instanceof SafeTernaryExpr) {
- st = st.expr;
- }
- return st;
- }
- return null;
- }
- // TODO: When strict compatibility with TemplateDefinitionBuilder is not required, we can use `&&`
- // instead to save some code size.
- function safeTransform(e, ctx) {
- if (!isAccessExpression$1(e)) {
- return e;
- }
- const dst = deepestSafeTernary(e);
- if (dst) {
- if (e instanceof InvokeFunctionExpr) {
- dst.expr = dst.expr.callFn(e.args);
- return e.receiver;
- }
- if (e instanceof ReadPropExpr) {
- dst.expr = dst.expr.prop(e.name);
- return e.receiver;
- }
- if (e instanceof ReadKeyExpr) {
- dst.expr = dst.expr.key(e.index);
- return e.receiver;
- }
- if (e instanceof SafeInvokeFunctionExpr) {
- dst.expr = safeTernaryWithTemporary(dst.expr, (r) => r.callFn(e.args), ctx);
- return e.receiver;
- }
- if (e instanceof SafePropertyReadExpr) {
- dst.expr = safeTernaryWithTemporary(dst.expr, (r) => r.prop(e.name), ctx);
- return e.receiver;
- }
- if (e instanceof SafeKeyedReadExpr) {
- dst.expr = safeTernaryWithTemporary(dst.expr, (r) => r.key(e.index), ctx);
- return e.receiver;
- }
- }
- else {
- if (e instanceof SafeInvokeFunctionExpr) {
- return safeTernaryWithTemporary(e.receiver, (r) => r.callFn(e.args), ctx);
- }
- if (e instanceof SafePropertyReadExpr) {
- return safeTernaryWithTemporary(e.receiver, (r) => r.prop(e.name), ctx);
- }
- if (e instanceof SafeKeyedReadExpr) {
- return safeTernaryWithTemporary(e.receiver, (r) => r.key(e.index), ctx);
- }
- }
- return e;
- }
- function ternaryTransform(e) {
- if (!(e instanceof SafeTernaryExpr)) {
- return e;
- }
- return new ConditionalExpr(new BinaryOperatorExpr(BinaryOperator.Equals, e.guard, NULL_EXPR), NULL_EXPR, e.expr);
- }
- /**
- * The escape sequence used indicate message param values.
- */
- const ESCAPE$1 = '\uFFFD';
- /**
- * Marker used to indicate an element tag.
- */
- const ELEMENT_MARKER = '#';
- /**
- * Marker used to indicate a template tag.
- */
- const TEMPLATE_MARKER = '*';
- /**
- * Marker used to indicate closing of an element or template tag.
- */
- const TAG_CLOSE_MARKER = '/';
- /**
- * Marker used to indicate the sub-template context.
- */
- const CONTEXT_MARKER = ':';
- /**
- * Marker used to indicate the start of a list of values.
- */
- const LIST_START_MARKER = '[';
- /**
- * Marker used to indicate the end of a list of values.
- */
- const LIST_END_MARKER = ']';
- /**
- * Delimiter used to separate multiple values in a list.
- */
- const LIST_DELIMITER = '|';
- /**
- * Formats the param maps on extracted message ops into a maps of `Expression` objects that can be
- * used in the final output.
- */
- function extractI18nMessages(job) {
- // Create an i18n message for each context.
- // TODO: Merge the context op with the message op since they're 1:1 anyways.
- const i18nMessagesByContext = new Map();
- const i18nBlocks = new Map();
- const i18nContexts = new Map();
- for (const unit of job.units) {
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nContext:
- const i18nMessageOp = createI18nMessage(job, op);
- unit.create.push(i18nMessageOp);
- i18nMessagesByContext.set(op.xref, i18nMessageOp);
- i18nContexts.set(op.xref, op);
- break;
- case OpKind.I18nStart:
- i18nBlocks.set(op.xref, op);
- break;
- }
- }
- }
- // Associate sub-messages for ICUs with their root message. At this point we can also remove the
- // ICU start/end ops, as they are no longer needed.
- let currentIcu = null;
- for (const unit of job.units) {
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.IcuStart:
- currentIcu = op;
- OpList.remove(op);
- // Skip any contexts not associated with an ICU.
- const icuContext = i18nContexts.get(op.context);
- if (icuContext.contextKind !== I18nContextKind.Icu) {
- continue;
- }
- // Skip ICUs that share a context with their i18n message. These represent root-level
- // ICUs, not sub-messages.
- const i18nBlock = i18nBlocks.get(icuContext.i18nBlock);
- if (i18nBlock.context === icuContext.xref) {
- continue;
- }
- // Find the root message and push this ICUs message as a sub-message.
- const rootI18nBlock = i18nBlocks.get(i18nBlock.root);
- const rootMessage = i18nMessagesByContext.get(rootI18nBlock.context);
- if (rootMessage === undefined) {
- throw Error('AssertionError: ICU sub-message should belong to a root message.');
- }
- const subMessage = i18nMessagesByContext.get(icuContext.xref);
- subMessage.messagePlaceholder = op.messagePlaceholder;
- rootMessage.subMessages.push(subMessage.xref);
- break;
- case OpKind.IcuEnd:
- currentIcu = null;
- OpList.remove(op);
- break;
- case OpKind.IcuPlaceholder:
- // Add ICU placeholders to the message, then remove the ICU placeholder ops.
- if (currentIcu === null || currentIcu.context == null) {
- throw Error('AssertionError: Unexpected ICU placeholder outside of i18n context');
- }
- const msg = i18nMessagesByContext.get(currentIcu.context);
- msg.postprocessingParams.set(op.name, literal$1(formatIcuPlaceholder(op)));
- OpList.remove(op);
- break;
- }
- }
- }
- }
- /**
- * Create an i18n message op from an i18n context op.
- */
- function createI18nMessage(job, context, messagePlaceholder) {
- let formattedParams = formatParams(context.params);
- const formattedPostprocessingParams = formatParams(context.postprocessingParams);
- let needsPostprocessing = [...context.params.values()].some((v) => v.length > 1);
- return createI18nMessageOp(job.allocateXrefId(), context.xref, context.i18nBlock, context.message, null, formattedParams, formattedPostprocessingParams, needsPostprocessing);
- }
- /**
- * Formats an ICU placeholder into a single string with expression placeholders.
- */
- function formatIcuPlaceholder(op) {
- if (op.strings.length !== op.expressionPlaceholders.length + 1) {
- throw Error(`AssertionError: Invalid ICU placeholder with ${op.strings.length} strings and ${op.expressionPlaceholders.length} expressions`);
- }
- const values = op.expressionPlaceholders.map(formatValue);
- return op.strings.flatMap((str, i) => [str, values[i] || '']).join('');
- }
- /**
- * Formats a map of `I18nParamValue[]` values into a map of `Expression` values.
- */
- function formatParams(params) {
- const formattedParams = new Map();
- for (const [placeholder, placeholderValues] of params) {
- const serializedValues = formatParamValues(placeholderValues);
- if (serializedValues !== null) {
- formattedParams.set(placeholder, literal$1(serializedValues));
- }
- }
- return formattedParams;
- }
- /**
- * Formats an `I18nParamValue[]` into a string (or null for empty array).
- */
- function formatParamValues(values) {
- if (values.length === 0) {
- return null;
- }
- const serializedValues = values.map((value) => formatValue(value));
- return serializedValues.length === 1
- ? serializedValues[0]
- : `${LIST_START_MARKER}${serializedValues.join(LIST_DELIMITER)}${LIST_END_MARKER}`;
- }
- /**
- * Formats a single `I18nParamValue` into a string
- */
- function formatValue(value) {
- // Element tags with a structural directive use a special form that concatenates the element and
- // template values.
- if (value.flags & I18nParamValueFlags.ElementTag &&
- value.flags & I18nParamValueFlags.TemplateTag) {
- if (typeof value.value !== 'object') {
- throw Error('AssertionError: Expected i18n param value to have an element and template slot');
- }
- const elementValue = formatValue({
- ...value,
- value: value.value.element,
- flags: value.flags & ~I18nParamValueFlags.TemplateTag,
- });
- const templateValue = formatValue({
- ...value,
- value: value.value.template,
- flags: value.flags & ~I18nParamValueFlags.ElementTag,
- });
- // TODO(mmalerba): This is likely a bug in TemplateDefinitionBuilder, we should not need to
- // record the template value twice. For now I'm re-implementing the behavior here to keep the
- // output consistent with TemplateDefinitionBuilder.
- if (value.flags & I18nParamValueFlags.OpenTag &&
- value.flags & I18nParamValueFlags.CloseTag) {
- return `${templateValue}${elementValue}${templateValue}`;
- }
- // To match the TemplateDefinitionBuilder output, flip the order depending on whether the
- // values represent a closing or opening tag (or both).
- // TODO(mmalerba): Figure out if this makes a difference in terms of either functionality,
- // or the resulting message ID. If not, we can remove the special-casing in the future.
- return value.flags & I18nParamValueFlags.CloseTag
- ? `${elementValue}${templateValue}`
- : `${templateValue}${elementValue}`;
- }
- // Self-closing tags use a special form that concatenates the start and close tag values.
- if (value.flags & I18nParamValueFlags.OpenTag &&
- value.flags & I18nParamValueFlags.CloseTag) {
- return `${formatValue({
- ...value,
- flags: value.flags & ~I18nParamValueFlags.CloseTag,
- })}${formatValue({ ...value, flags: value.flags & ~I18nParamValueFlags.OpenTag })}`;
- }
- // If there are no special flags, just return the raw value.
- if (value.flags === I18nParamValueFlags.None) {
- return `${value.value}`;
- }
- // Encode the remaining flags as part of the value.
- let tagMarker = '';
- let closeMarker = '';
- if (value.flags & I18nParamValueFlags.ElementTag) {
- tagMarker = ELEMENT_MARKER;
- }
- else if (value.flags & I18nParamValueFlags.TemplateTag) {
- tagMarker = TEMPLATE_MARKER;
- }
- if (tagMarker !== '') {
- closeMarker = value.flags & I18nParamValueFlags.CloseTag ? TAG_CLOSE_MARKER : '';
- }
- const context = value.subTemplateIndex === null ? '' : `${CONTEXT_MARKER}${value.subTemplateIndex}`;
- return `${ESCAPE$1}${closeMarker}${tagMarker}${value.value}${context}${ESCAPE$1}`;
- }
- /**
- * Generate `ir.AdvanceOp`s in between `ir.UpdateOp`s that ensure the runtime's implicit slot
- * context will be advanced correctly.
- */
- function generateAdvance(job) {
- for (const unit of job.units) {
- // First build a map of all of the declarations in the view that have assigned slots.
- const slotMap = new Map();
- for (const op of unit.create) {
- if (!hasConsumesSlotTrait(op)) {
- continue;
- }
- else if (op.handle.slot === null) {
- throw new Error(`AssertionError: expected slots to have been allocated before generating advance() calls`);
- }
- slotMap.set(op.xref, op.handle.slot);
- }
- // Next, step through the update operations and generate `ir.AdvanceOp`s as required to ensure
- // the runtime's implicit slot counter will be set to the correct slot before executing each
- // update operation which depends on it.
- //
- // To do that, we track what the runtime's slot counter will be through the update operations.
- let slotContext = 0;
- for (const op of unit.update) {
- let consumer = null;
- if (hasDependsOnSlotContextTrait(op)) {
- consumer = op;
- }
- else {
- visitExpressionsInOp(op, (expr) => {
- if (consumer === null && hasDependsOnSlotContextTrait(expr)) {
- consumer = expr;
- }
- });
- }
- if (consumer === null) {
- continue;
- }
- if (!slotMap.has(consumer.target)) {
- // We expect ops that _do_ depend on the slot counter to point at declarations that exist in
- // the `slotMap`.
- throw new Error(`AssertionError: reference to unknown slot for target ${consumer.target}`);
- }
- const slot = slotMap.get(consumer.target);
- // Does the slot counter need to be adjusted?
- if (slotContext !== slot) {
- // If so, generate an `ir.AdvanceOp` to advance the counter.
- const delta = slot - slotContext;
- if (delta < 0) {
- throw new Error(`AssertionError: slot counter should never need to move backwards`);
- }
- OpList.insertBefore(createAdvanceOp(delta, consumer.sourceSpan), op);
- slotContext = slot;
- }
- }
- }
- }
- /**
- * Locate projection slots, populate the each component's `ngContentSelectors` literal field,
- * populate `project` arguments, and generate the required `projectionDef` instruction for the job's
- * root view.
- */
- function generateProjectionDefs(job) {
- // TODO: Why does TemplateDefinitionBuilder force a shared constant?
- const share = job.compatibility === CompatibilityMode.TemplateDefinitionBuilder;
- // Collect all selectors from this component, and its nested views. Also, assign each projection a
- // unique ascending projection slot index.
- const selectors = [];
- let projectionSlotIndex = 0;
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.Projection) {
- selectors.push(op.selector);
- op.projectionSlotIndex = projectionSlotIndex++;
- }
- }
- }
- if (selectors.length > 0) {
- // Create the projectionDef array. If we only found a single wildcard selector, then we use the
- // default behavior with no arguments instead.
- let defExpr = null;
- if (selectors.length > 1 || selectors[0] !== '*') {
- const def = selectors.map((s) => (s === '*' ? s : parseSelectorToR3Selector(s)));
- defExpr = job.pool.getConstLiteral(literalOrArrayLiteral(def), share);
- }
- // Create the ngContentSelectors constant.
- job.contentSelectors = job.pool.getConstLiteral(literalOrArrayLiteral(selectors), share);
- // The projection def instruction goes at the beginning of the root view, before any
- // `projection` instructions.
- job.root.create.prepend([createProjectionDefOp(defExpr)]);
- }
- }
- /**
- * Generate a preamble sequence for each view creation block and listener function which declares
- * any variables that be referenced in other operations in the block.
- *
- * Variables generated include:
- * * a saved view context to be used to restore the current view in event listeners.
- * * the context of the restored view within event listener handlers.
- * * context variables from the current view as well as all parent views (including the root
- * context if needed).
- * * local references from elements within the current view and any lexical parents.
- *
- * Variables are generated here unconditionally, and may optimized away in future operations if it
- * turns out their values (and any side effects) are unused.
- */
- function generateVariables(job) {
- recursivelyProcessView(job.root, /* there is no parent scope for the root view */ null);
- }
- /**
- * Process the given `ViewCompilation` and generate preambles for it and any listeners that it
- * declares.
- *
- * @param `parentScope` a scope extracted from the parent view which captures any variables which
- * should be inherited by this view. `null` if the current view is the root view.
- */
- function recursivelyProcessView(view, parentScope) {
- // Extract a `Scope` from this view.
- const scope = getScopeForView(view, parentScope);
- for (const op of view.create) {
- switch (op.kind) {
- case OpKind.Template:
- // Descend into child embedded views.
- recursivelyProcessView(view.job.views.get(op.xref), scope);
- break;
- case OpKind.Projection:
- if (op.fallbackView !== null) {
- recursivelyProcessView(view.job.views.get(op.fallbackView), scope);
- }
- break;
- case OpKind.RepeaterCreate:
- // Descend into child embedded views.
- recursivelyProcessView(view.job.views.get(op.xref), scope);
- if (op.emptyView) {
- recursivelyProcessView(view.job.views.get(op.emptyView), scope);
- }
- if (op.trackByOps !== null) {
- op.trackByOps.prepend(generateVariablesInScopeForView(view, scope, false));
- }
- break;
- case OpKind.Listener:
- case OpKind.TwoWayListener:
- // Prepend variables to listener handler functions.
- op.handlerOps.prepend(generateVariablesInScopeForView(view, scope, true));
- break;
- }
- }
- view.update.prepend(generateVariablesInScopeForView(view, scope, false));
- }
- /**
- * Process a view and generate a `Scope` representing the variables available for reference within
- * that view.
- */
- function getScopeForView(view, parent) {
- const scope = {
- view: view.xref,
- viewContextVariable: {
- kind: SemanticVariableKind.Context,
- name: null,
- view: view.xref,
- },
- contextVariables: new Map(),
- aliases: view.aliases,
- references: [],
- letDeclarations: [],
- parent,
- };
- for (const identifier of view.contextVariables.keys()) {
- scope.contextVariables.set(identifier, {
- kind: SemanticVariableKind.Identifier,
- name: null,
- identifier,
- local: false,
- });
- }
- for (const op of view.create) {
- switch (op.kind) {
- case OpKind.ElementStart:
- case OpKind.Template:
- if (!Array.isArray(op.localRefs)) {
- throw new Error(`AssertionError: expected localRefs to be an array`);
- }
- // Record available local references from this element.
- for (let offset = 0; offset < op.localRefs.length; offset++) {
- scope.references.push({
- name: op.localRefs[offset].name,
- targetId: op.xref,
- targetSlot: op.handle,
- offset,
- variable: {
- kind: SemanticVariableKind.Identifier,
- name: null,
- identifier: op.localRefs[offset].name,
- local: false,
- },
- });
- }
- break;
- case OpKind.DeclareLet:
- scope.letDeclarations.push({
- targetId: op.xref,
- targetSlot: op.handle,
- variable: {
- kind: SemanticVariableKind.Identifier,
- name: null,
- identifier: op.declaredName,
- local: false,
- },
- });
- break;
- }
- }
- return scope;
- }
- /**
- * Generate declarations for all variables that are in scope for a given view.
- *
- * This is a recursive process, as views inherit variables available from their parent view, which
- * itself may have inherited variables, etc.
- */
- function generateVariablesInScopeForView(view, scope, isListener) {
- const newOps = [];
- if (scope.view !== view.xref) {
- // Before generating variables for a parent view, we need to switch to the context of the parent
- // view with a `nextContext` expression. This context switching operation itself declares a
- // variable, because the context of the view may be referenced directly.
- newOps.push(createVariableOp(view.job.allocateXrefId(), scope.viewContextVariable, new NextContextExpr(), VariableFlags.None));
- }
- // Add variables for all context variables available in this scope's view.
- const scopeView = view.job.views.get(scope.view);
- for (const [name, value] of scopeView.contextVariables) {
- const context = new ContextExpr(scope.view);
- // We either read the context, or, if the variable is CTX_REF, use the context directly.
- const variable = value === CTX_REF ? context : new ReadPropExpr(context, value);
- // Add the variable declaration.
- newOps.push(createVariableOp(view.job.allocateXrefId(), scope.contextVariables.get(name), variable, VariableFlags.None));
- }
- for (const alias of scopeView.aliases) {
- newOps.push(createVariableOp(view.job.allocateXrefId(), alias, alias.expression.clone(), VariableFlags.AlwaysInline));
- }
- // Add variables for all local references declared for elements in this scope.
- for (const ref of scope.references) {
- newOps.push(createVariableOp(view.job.allocateXrefId(), ref.variable, new ReferenceExpr(ref.targetId, ref.targetSlot, ref.offset), VariableFlags.None));
- }
- if (scope.view !== view.xref || isListener) {
- for (const decl of scope.letDeclarations) {
- newOps.push(createVariableOp(view.job.allocateXrefId(), decl.variable, new ContextLetReferenceExpr(decl.targetId, decl.targetSlot), VariableFlags.None));
- }
- }
- if (scope.parent !== null) {
- // Recursively add variables from the parent scope.
- newOps.push(...generateVariablesInScopeForView(view, scope.parent, false));
- }
- return newOps;
- }
- /**
- * `ir.ConstCollectedExpr` may be present in any IR expression. This means that expression needs to
- * be lifted into the component const array, and replaced with a reference to the const array at its
- *
- * usage site. This phase walks the IR and performs this transformation.
- */
- function collectConstExpressions(job) {
- for (const unit of job.units) {
- for (const op of unit.ops()) {
- transformExpressionsInOp(op, (expr) => {
- if (!(expr instanceof ConstCollectedExpr)) {
- return expr;
- }
- return literal$1(job.addConst(expr.expr));
- }, VisitorContextFlag.None);
- }
- }
- }
- const STYLE_DOT = 'style.';
- const CLASS_DOT = 'class.';
- const STYLE_BANG = 'style!';
- const CLASS_BANG = 'class!';
- const BANG_IMPORTANT = '!important';
- /**
- * Host bindings are compiled using a different parser entrypoint, and are parsed quite differently
- * as a result. Therefore, we need to do some extra parsing for host style properties, as compared
- * to non-host style properties.
- * TODO: Unify host bindings and non-host bindings in the parser.
- */
- function parseHostStyleProperties(job) {
- for (const op of job.root.update) {
- if (!(op.kind === OpKind.Binding && op.bindingKind === BindingKind.Property)) {
- continue;
- }
- if (op.name.endsWith(BANG_IMPORTANT)) {
- // Delete any `!important` suffixes from the binding name.
- op.name = op.name.substring(0, op.name.length - BANG_IMPORTANT.length);
- }
- if (op.name.startsWith(STYLE_DOT)) {
- op.bindingKind = BindingKind.StyleProperty;
- op.name = op.name.substring(STYLE_DOT.length);
- if (!isCssCustomProperty(op.name)) {
- op.name = hyphenate$1(op.name);
- }
- const { property, suffix } = parseProperty(op.name);
- op.name = property;
- op.unit = suffix;
- }
- else if (op.name.startsWith(STYLE_BANG)) {
- op.bindingKind = BindingKind.StyleProperty;
- op.name = 'style';
- }
- else if (op.name.startsWith(CLASS_DOT)) {
- op.bindingKind = BindingKind.ClassName;
- op.name = parseProperty(op.name.substring(CLASS_DOT.length)).property;
- }
- else if (op.name.startsWith(CLASS_BANG)) {
- op.bindingKind = BindingKind.ClassName;
- op.name = parseProperty(op.name.substring(CLASS_BANG.length)).property;
- }
- }
- }
- /**
- * Checks whether property name is a custom CSS property.
- * See: https://www.w3.org/TR/css-variables-1
- */
- function isCssCustomProperty(name) {
- return name.startsWith('--');
- }
- function hyphenate$1(value) {
- return value
- .replace(/[a-z][A-Z]/g, (v) => {
- return v.charAt(0) + '-' + v.charAt(1);
- })
- .toLowerCase();
- }
- function parseProperty(name) {
- const overrideIndex = name.indexOf('!important');
- if (overrideIndex !== -1) {
- name = overrideIndex > 0 ? name.substring(0, overrideIndex) : '';
- }
- let suffix = null;
- let property = name;
- const unitIndex = name.lastIndexOf('.');
- if (unitIndex > 0) {
- suffix = name.slice(unitIndex + 1);
- property = name.substring(0, unitIndex);
- }
- return { property, suffix };
- }
- function mapLiteral(obj, quoted = false) {
- return literalMap(Object.keys(obj).map((key) => ({
- key,
- quoted,
- value: obj[key],
- })));
- }
- class IcuSerializerVisitor {
- visitText(text) {
- return text.value;
- }
- visitContainer(container) {
- return container.children.map((child) => child.visit(this)).join('');
- }
- visitIcu(icu) {
- const strCases = Object.keys(icu.cases).map((k) => `${k} {${icu.cases[k].visit(this)}}`);
- const result = `{${icu.expressionPlaceholder}, ${icu.type}, ${strCases.join(' ')}}`;
- return result;
- }
- visitTagPlaceholder(ph) {
- return ph.isVoid
- ? this.formatPh(ph.startName)
- : `${this.formatPh(ph.startName)}${ph.children
- .map((child) => child.visit(this))
- .join('')}${this.formatPh(ph.closeName)}`;
- }
- visitPlaceholder(ph) {
- return this.formatPh(ph.name);
- }
- visitBlockPlaceholder(ph) {
- return `${this.formatPh(ph.startName)}${ph.children
- .map((child) => child.visit(this))
- .join('')}${this.formatPh(ph.closeName)}`;
- }
- visitIcuPlaceholder(ph, context) {
- return this.formatPh(ph.name);
- }
- formatPh(value) {
- return `{${formatI18nPlaceholderName(value, /* useCamelCase */ false)}}`;
- }
- }
- const serializer = new IcuSerializerVisitor();
- function serializeIcuNode(icu) {
- return icu.visit(serializer);
- }
- class NodeWithI18n {
- sourceSpan;
- i18n;
- constructor(sourceSpan, i18n) {
- this.sourceSpan = sourceSpan;
- this.i18n = i18n;
- }
- }
- class Text extends NodeWithI18n {
- value;
- tokens;
- constructor(value, sourceSpan, tokens, i18n) {
- super(sourceSpan, i18n);
- this.value = value;
- this.tokens = tokens;
- }
- visit(visitor, context) {
- return visitor.visitText(this, context);
- }
- }
- class Expansion extends NodeWithI18n {
- switchValue;
- type;
- cases;
- switchValueSourceSpan;
- constructor(switchValue, type, cases, sourceSpan, switchValueSourceSpan, i18n) {
- super(sourceSpan, i18n);
- this.switchValue = switchValue;
- this.type = type;
- this.cases = cases;
- this.switchValueSourceSpan = switchValueSourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitExpansion(this, context);
- }
- }
- class ExpansionCase {
- value;
- expression;
- sourceSpan;
- valueSourceSpan;
- expSourceSpan;
- constructor(value, expression, sourceSpan, valueSourceSpan, expSourceSpan) {
- this.value = value;
- this.expression = expression;
- this.sourceSpan = sourceSpan;
- this.valueSourceSpan = valueSourceSpan;
- this.expSourceSpan = expSourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitExpansionCase(this, context);
- }
- }
- class Attribute extends NodeWithI18n {
- name;
- value;
- keySpan;
- valueSpan;
- valueTokens;
- constructor(name, value, sourceSpan, keySpan, valueSpan, valueTokens, i18n) {
- super(sourceSpan, i18n);
- this.name = name;
- this.value = value;
- this.keySpan = keySpan;
- this.valueSpan = valueSpan;
- this.valueTokens = valueTokens;
- }
- visit(visitor, context) {
- return visitor.visitAttribute(this, context);
- }
- }
- class Element extends NodeWithI18n {
- name;
- attrs;
- children;
- startSourceSpan;
- endSourceSpan;
- constructor(name, attrs, children, sourceSpan, startSourceSpan, endSourceSpan = null, i18n) {
- super(sourceSpan, i18n);
- this.name = name;
- this.attrs = attrs;
- this.children = children;
- this.startSourceSpan = startSourceSpan;
- this.endSourceSpan = endSourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitElement(this, context);
- }
- }
- class Comment {
- value;
- sourceSpan;
- constructor(value, sourceSpan) {
- this.value = value;
- this.sourceSpan = sourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitComment(this, context);
- }
- }
- class Block extends NodeWithI18n {
- name;
- parameters;
- children;
- nameSpan;
- startSourceSpan;
- endSourceSpan;
- constructor(name, parameters, children, sourceSpan, nameSpan, startSourceSpan, endSourceSpan = null, i18n) {
- super(sourceSpan, i18n);
- this.name = name;
- this.parameters = parameters;
- this.children = children;
- this.nameSpan = nameSpan;
- this.startSourceSpan = startSourceSpan;
- this.endSourceSpan = endSourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitBlock(this, context);
- }
- }
- class BlockParameter {
- expression;
- sourceSpan;
- constructor(expression, sourceSpan) {
- this.expression = expression;
- this.sourceSpan = sourceSpan;
- }
- visit(visitor, context) {
- return visitor.visitBlockParameter(this, context);
- }
- }
- class LetDeclaration {
- name;
- value;
- sourceSpan;
- nameSpan;
- valueSpan;
- constructor(name, value, sourceSpan, nameSpan, valueSpan) {
- this.name = name;
- this.value = value;
- this.sourceSpan = sourceSpan;
- this.nameSpan = nameSpan;
- this.valueSpan = valueSpan;
- }
- visit(visitor, context) {
- return visitor.visitLetDeclaration(this, context);
- }
- }
- function visitAll(visitor, nodes, context = null) {
- const result = [];
- const visit = visitor.visit
- ? (ast) => visitor.visit(ast, context) || ast.visit(visitor, context)
- : (ast) => ast.visit(visitor, context);
- nodes.forEach((ast) => {
- const astResult = visit(ast);
- if (astResult) {
- result.push(astResult);
- }
- });
- return result;
- }
- class RecursiveVisitor {
- constructor() { }
- visitElement(ast, context) {
- this.visitChildren(context, (visit) => {
- visit(ast.attrs);
- visit(ast.children);
- });
- }
- visitAttribute(ast, context) { }
- visitText(ast, context) { }
- visitComment(ast, context) { }
- visitExpansion(ast, context) {
- return this.visitChildren(context, (visit) => {
- visit(ast.cases);
- });
- }
- visitExpansionCase(ast, context) { }
- visitBlock(block, context) {
- this.visitChildren(context, (visit) => {
- visit(block.parameters);
- visit(block.children);
- });
- }
- visitBlockParameter(ast, context) { }
- visitLetDeclaration(decl, context) { }
- visitChildren(context, cb) {
- let results = [];
- let t = this;
- function visit(children) {
- if (children)
- results.push(visitAll(t, children, context));
- }
- cb(visit);
- return Array.prototype.concat.apply([], results);
- }
- }
- // Mapping between all HTML entity names and their unicode representation.
- // Generated from https://html.spec.whatwg.org/multipage/entities.json by stripping
- // the `&` and `;` from the keys and removing the duplicates.
- // see https://www.w3.org/TR/html51/syntax.html#named-character-references
- const NAMED_ENTITIES = {
- 'AElig': '\u00C6',
- 'AMP': '\u0026',
- 'amp': '\u0026',
- 'Aacute': '\u00C1',
- 'Abreve': '\u0102',
- 'Acirc': '\u00C2',
- 'Acy': '\u0410',
- 'Afr': '\uD835\uDD04',
- 'Agrave': '\u00C0',
- 'Alpha': '\u0391',
- 'Amacr': '\u0100',
- 'And': '\u2A53',
- 'Aogon': '\u0104',
- 'Aopf': '\uD835\uDD38',
- 'ApplyFunction': '\u2061',
- 'af': '\u2061',
- 'Aring': '\u00C5',
- 'angst': '\u00C5',
- 'Ascr': '\uD835\uDC9C',
- 'Assign': '\u2254',
- 'colone': '\u2254',
- 'coloneq': '\u2254',
- 'Atilde': '\u00C3',
- 'Auml': '\u00C4',
- 'Backslash': '\u2216',
- 'setminus': '\u2216',
- 'setmn': '\u2216',
- 'smallsetminus': '\u2216',
- 'ssetmn': '\u2216',
- 'Barv': '\u2AE7',
- 'Barwed': '\u2306',
- 'doublebarwedge': '\u2306',
- 'Bcy': '\u0411',
- 'Because': '\u2235',
- 'becaus': '\u2235',
- 'because': '\u2235',
- 'Bernoullis': '\u212C',
- 'Bscr': '\u212C',
- 'bernou': '\u212C',
- 'Beta': '\u0392',
- 'Bfr': '\uD835\uDD05',
- 'Bopf': '\uD835\uDD39',
- 'Breve': '\u02D8',
- 'breve': '\u02D8',
- 'Bumpeq': '\u224E',
- 'HumpDownHump': '\u224E',
- 'bump': '\u224E',
- 'CHcy': '\u0427',
- 'COPY': '\u00A9',
- 'copy': '\u00A9',
- 'Cacute': '\u0106',
- 'Cap': '\u22D2',
- 'CapitalDifferentialD': '\u2145',
- 'DD': '\u2145',
- 'Cayleys': '\u212D',
- 'Cfr': '\u212D',
- 'Ccaron': '\u010C',
- 'Ccedil': '\u00C7',
- 'Ccirc': '\u0108',
- 'Cconint': '\u2230',
- 'Cdot': '\u010A',
- 'Cedilla': '\u00B8',
- 'cedil': '\u00B8',
- 'CenterDot': '\u00B7',
- 'centerdot': '\u00B7',
- 'middot': '\u00B7',
- 'Chi': '\u03A7',
- 'CircleDot': '\u2299',
- 'odot': '\u2299',
- 'CircleMinus': '\u2296',
- 'ominus': '\u2296',
- 'CirclePlus': '\u2295',
- 'oplus': '\u2295',
- 'CircleTimes': '\u2297',
- 'otimes': '\u2297',
- 'ClockwiseContourIntegral': '\u2232',
- 'cwconint': '\u2232',
- 'CloseCurlyDoubleQuote': '\u201D',
- 'rdquo': '\u201D',
- 'rdquor': '\u201D',
- 'CloseCurlyQuote': '\u2019',
- 'rsquo': '\u2019',
- 'rsquor': '\u2019',
- 'Colon': '\u2237',
- 'Proportion': '\u2237',
- 'Colone': '\u2A74',
- 'Congruent': '\u2261',
- 'equiv': '\u2261',
- 'Conint': '\u222F',
- 'DoubleContourIntegral': '\u222F',
- 'ContourIntegral': '\u222E',
- 'conint': '\u222E',
- 'oint': '\u222E',
- 'Copf': '\u2102',
- 'complexes': '\u2102',
- 'Coproduct': '\u2210',
- 'coprod': '\u2210',
- 'CounterClockwiseContourIntegral': '\u2233',
- 'awconint': '\u2233',
- 'Cross': '\u2A2F',
- 'Cscr': '\uD835\uDC9E',
- 'Cup': '\u22D3',
- 'CupCap': '\u224D',
- 'asympeq': '\u224D',
- 'DDotrahd': '\u2911',
- 'DJcy': '\u0402',
- 'DScy': '\u0405',
- 'DZcy': '\u040F',
- 'Dagger': '\u2021',
- 'ddagger': '\u2021',
- 'Darr': '\u21A1',
- 'Dashv': '\u2AE4',
- 'DoubleLeftTee': '\u2AE4',
- 'Dcaron': '\u010E',
- 'Dcy': '\u0414',
- 'Del': '\u2207',
- 'nabla': '\u2207',
- 'Delta': '\u0394',
- 'Dfr': '\uD835\uDD07',
- 'DiacriticalAcute': '\u00B4',
- 'acute': '\u00B4',
- 'DiacriticalDot': '\u02D9',
- 'dot': '\u02D9',
- 'DiacriticalDoubleAcute': '\u02DD',
- 'dblac': '\u02DD',
- 'DiacriticalGrave': '\u0060',
- 'grave': '\u0060',
- 'DiacriticalTilde': '\u02DC',
- 'tilde': '\u02DC',
- 'Diamond': '\u22C4',
- 'diam': '\u22C4',
- 'diamond': '\u22C4',
- 'DifferentialD': '\u2146',
- 'dd': '\u2146',
- 'Dopf': '\uD835\uDD3B',
- 'Dot': '\u00A8',
- 'DoubleDot': '\u00A8',
- 'die': '\u00A8',
- 'uml': '\u00A8',
- 'DotDot': '\u20DC',
- 'DotEqual': '\u2250',
- 'doteq': '\u2250',
- 'esdot': '\u2250',
- 'DoubleDownArrow': '\u21D3',
- 'Downarrow': '\u21D3',
- 'dArr': '\u21D3',
- 'DoubleLeftArrow': '\u21D0',
- 'Leftarrow': '\u21D0',
- 'lArr': '\u21D0',
- 'DoubleLeftRightArrow': '\u21D4',
- 'Leftrightarrow': '\u21D4',
- 'hArr': '\u21D4',
- 'iff': '\u21D4',
- 'DoubleLongLeftArrow': '\u27F8',
- 'Longleftarrow': '\u27F8',
- 'xlArr': '\u27F8',
- 'DoubleLongLeftRightArrow': '\u27FA',
- 'Longleftrightarrow': '\u27FA',
- 'xhArr': '\u27FA',
- 'DoubleLongRightArrow': '\u27F9',
- 'Longrightarrow': '\u27F9',
- 'xrArr': '\u27F9',
- 'DoubleRightArrow': '\u21D2',
- 'Implies': '\u21D2',
- 'Rightarrow': '\u21D2',
- 'rArr': '\u21D2',
- 'DoubleRightTee': '\u22A8',
- 'vDash': '\u22A8',
- 'DoubleUpArrow': '\u21D1',
- 'Uparrow': '\u21D1',
- 'uArr': '\u21D1',
- 'DoubleUpDownArrow': '\u21D5',
- 'Updownarrow': '\u21D5',
- 'vArr': '\u21D5',
- 'DoubleVerticalBar': '\u2225',
- 'par': '\u2225',
- 'parallel': '\u2225',
- 'shortparallel': '\u2225',
- 'spar': '\u2225',
- 'DownArrow': '\u2193',
- 'ShortDownArrow': '\u2193',
- 'darr': '\u2193',
- 'downarrow': '\u2193',
- 'DownArrowBar': '\u2913',
- 'DownArrowUpArrow': '\u21F5',
- 'duarr': '\u21F5',
- 'DownBreve': '\u0311',
- 'DownLeftRightVector': '\u2950',
- 'DownLeftTeeVector': '\u295E',
- 'DownLeftVector': '\u21BD',
- 'leftharpoondown': '\u21BD',
- 'lhard': '\u21BD',
- 'DownLeftVectorBar': '\u2956',
- 'DownRightTeeVector': '\u295F',
- 'DownRightVector': '\u21C1',
- 'rhard': '\u21C1',
- 'rightharpoondown': '\u21C1',
- 'DownRightVectorBar': '\u2957',
- 'DownTee': '\u22A4',
- 'top': '\u22A4',
- 'DownTeeArrow': '\u21A7',
- 'mapstodown': '\u21A7',
- 'Dscr': '\uD835\uDC9F',
- 'Dstrok': '\u0110',
- 'ENG': '\u014A',
- 'ETH': '\u00D0',
- 'Eacute': '\u00C9',
- 'Ecaron': '\u011A',
- 'Ecirc': '\u00CA',
- 'Ecy': '\u042D',
- 'Edot': '\u0116',
- 'Efr': '\uD835\uDD08',
- 'Egrave': '\u00C8',
- 'Element': '\u2208',
- 'in': '\u2208',
- 'isin': '\u2208',
- 'isinv': '\u2208',
- 'Emacr': '\u0112',
- 'EmptySmallSquare': '\u25FB',
- 'EmptyVerySmallSquare': '\u25AB',
- 'Eogon': '\u0118',
- 'Eopf': '\uD835\uDD3C',
- 'Epsilon': '\u0395',
- 'Equal': '\u2A75',
- 'EqualTilde': '\u2242',
- 'eqsim': '\u2242',
- 'esim': '\u2242',
- 'Equilibrium': '\u21CC',
- 'rightleftharpoons': '\u21CC',
- 'rlhar': '\u21CC',
- 'Escr': '\u2130',
- 'expectation': '\u2130',
- 'Esim': '\u2A73',
- 'Eta': '\u0397',
- 'Euml': '\u00CB',
- 'Exists': '\u2203',
- 'exist': '\u2203',
- 'ExponentialE': '\u2147',
- 'ee': '\u2147',
- 'exponentiale': '\u2147',
- 'Fcy': '\u0424',
- 'Ffr': '\uD835\uDD09',
- 'FilledSmallSquare': '\u25FC',
- 'FilledVerySmallSquare': '\u25AA',
- 'blacksquare': '\u25AA',
- 'squarf': '\u25AA',
- 'squf': '\u25AA',
- 'Fopf': '\uD835\uDD3D',
- 'ForAll': '\u2200',
- 'forall': '\u2200',
- 'Fouriertrf': '\u2131',
- 'Fscr': '\u2131',
- 'GJcy': '\u0403',
- 'GT': '\u003E',
- 'gt': '\u003E',
- 'Gamma': '\u0393',
- 'Gammad': '\u03DC',
- 'Gbreve': '\u011E',
- 'Gcedil': '\u0122',
- 'Gcirc': '\u011C',
- 'Gcy': '\u0413',
- 'Gdot': '\u0120',
- 'Gfr': '\uD835\uDD0A',
- 'Gg': '\u22D9',
- 'ggg': '\u22D9',
- 'Gopf': '\uD835\uDD3E',
- 'GreaterEqual': '\u2265',
- 'ge': '\u2265',
- 'geq': '\u2265',
- 'GreaterEqualLess': '\u22DB',
- 'gel': '\u22DB',
- 'gtreqless': '\u22DB',
- 'GreaterFullEqual': '\u2267',
- 'gE': '\u2267',
- 'geqq': '\u2267',
- 'GreaterGreater': '\u2AA2',
- 'GreaterLess': '\u2277',
- 'gl': '\u2277',
- 'gtrless': '\u2277',
- 'GreaterSlantEqual': '\u2A7E',
- 'geqslant': '\u2A7E',
- 'ges': '\u2A7E',
- 'GreaterTilde': '\u2273',
- 'gsim': '\u2273',
- 'gtrsim': '\u2273',
- 'Gscr': '\uD835\uDCA2',
- 'Gt': '\u226B',
- 'NestedGreaterGreater': '\u226B',
- 'gg': '\u226B',
- 'HARDcy': '\u042A',
- 'Hacek': '\u02C7',
- 'caron': '\u02C7',
- 'Hat': '\u005E',
- 'Hcirc': '\u0124',
- 'Hfr': '\u210C',
- 'Poincareplane': '\u210C',
- 'HilbertSpace': '\u210B',
- 'Hscr': '\u210B',
- 'hamilt': '\u210B',
- 'Hopf': '\u210D',
- 'quaternions': '\u210D',
- 'HorizontalLine': '\u2500',
- 'boxh': '\u2500',
- 'Hstrok': '\u0126',
- 'HumpEqual': '\u224F',
- 'bumpe': '\u224F',
- 'bumpeq': '\u224F',
- 'IEcy': '\u0415',
- 'IJlig': '\u0132',
- 'IOcy': '\u0401',
- 'Iacute': '\u00CD',
- 'Icirc': '\u00CE',
- 'Icy': '\u0418',
- 'Idot': '\u0130',
- 'Ifr': '\u2111',
- 'Im': '\u2111',
- 'image': '\u2111',
- 'imagpart': '\u2111',
- 'Igrave': '\u00CC',
- 'Imacr': '\u012A',
- 'ImaginaryI': '\u2148',
- 'ii': '\u2148',
- 'Int': '\u222C',
- 'Integral': '\u222B',
- 'int': '\u222B',
- 'Intersection': '\u22C2',
- 'bigcap': '\u22C2',
- 'xcap': '\u22C2',
- 'InvisibleComma': '\u2063',
- 'ic': '\u2063',
- 'InvisibleTimes': '\u2062',
- 'it': '\u2062',
- 'Iogon': '\u012E',
- 'Iopf': '\uD835\uDD40',
- 'Iota': '\u0399',
- 'Iscr': '\u2110',
- 'imagline': '\u2110',
- 'Itilde': '\u0128',
- 'Iukcy': '\u0406',
- 'Iuml': '\u00CF',
- 'Jcirc': '\u0134',
- 'Jcy': '\u0419',
- 'Jfr': '\uD835\uDD0D',
- 'Jopf': '\uD835\uDD41',
- 'Jscr': '\uD835\uDCA5',
- 'Jsercy': '\u0408',
- 'Jukcy': '\u0404',
- 'KHcy': '\u0425',
- 'KJcy': '\u040C',
- 'Kappa': '\u039A',
- 'Kcedil': '\u0136',
- 'Kcy': '\u041A',
- 'Kfr': '\uD835\uDD0E',
- 'Kopf': '\uD835\uDD42',
- 'Kscr': '\uD835\uDCA6',
- 'LJcy': '\u0409',
- 'LT': '\u003C',
- 'lt': '\u003C',
- 'Lacute': '\u0139',
- 'Lambda': '\u039B',
- 'Lang': '\u27EA',
- 'Laplacetrf': '\u2112',
- 'Lscr': '\u2112',
- 'lagran': '\u2112',
- 'Larr': '\u219E',
- 'twoheadleftarrow': '\u219E',
- 'Lcaron': '\u013D',
- 'Lcedil': '\u013B',
- 'Lcy': '\u041B',
- 'LeftAngleBracket': '\u27E8',
- 'lang': '\u27E8',
- 'langle': '\u27E8',
- 'LeftArrow': '\u2190',
- 'ShortLeftArrow': '\u2190',
- 'larr': '\u2190',
- 'leftarrow': '\u2190',
- 'slarr': '\u2190',
- 'LeftArrowBar': '\u21E4',
- 'larrb': '\u21E4',
- 'LeftArrowRightArrow': '\u21C6',
- 'leftrightarrows': '\u21C6',
- 'lrarr': '\u21C6',
- 'LeftCeiling': '\u2308',
- 'lceil': '\u2308',
- 'LeftDoubleBracket': '\u27E6',
- 'lobrk': '\u27E6',
- 'LeftDownTeeVector': '\u2961',
- 'LeftDownVector': '\u21C3',
- 'dharl': '\u21C3',
- 'downharpoonleft': '\u21C3',
- 'LeftDownVectorBar': '\u2959',
- 'LeftFloor': '\u230A',
- 'lfloor': '\u230A',
- 'LeftRightArrow': '\u2194',
- 'harr': '\u2194',
- 'leftrightarrow': '\u2194',
- 'LeftRightVector': '\u294E',
- 'LeftTee': '\u22A3',
- 'dashv': '\u22A3',
- 'LeftTeeArrow': '\u21A4',
- 'mapstoleft': '\u21A4',
- 'LeftTeeVector': '\u295A',
- 'LeftTriangle': '\u22B2',
- 'vartriangleleft': '\u22B2',
- 'vltri': '\u22B2',
- 'LeftTriangleBar': '\u29CF',
- 'LeftTriangleEqual': '\u22B4',
- 'ltrie': '\u22B4',
- 'trianglelefteq': '\u22B4',
- 'LeftUpDownVector': '\u2951',
- 'LeftUpTeeVector': '\u2960',
- 'LeftUpVector': '\u21BF',
- 'uharl': '\u21BF',
- 'upharpoonleft': '\u21BF',
- 'LeftUpVectorBar': '\u2958',
- 'LeftVector': '\u21BC',
- 'leftharpoonup': '\u21BC',
- 'lharu': '\u21BC',
- 'LeftVectorBar': '\u2952',
- 'LessEqualGreater': '\u22DA',
- 'leg': '\u22DA',
- 'lesseqgtr': '\u22DA',
- 'LessFullEqual': '\u2266',
- 'lE': '\u2266',
- 'leqq': '\u2266',
- 'LessGreater': '\u2276',
- 'lessgtr': '\u2276',
- 'lg': '\u2276',
- 'LessLess': '\u2AA1',
- 'LessSlantEqual': '\u2A7D',
- 'leqslant': '\u2A7D',
- 'les': '\u2A7D',
- 'LessTilde': '\u2272',
- 'lesssim': '\u2272',
- 'lsim': '\u2272',
- 'Lfr': '\uD835\uDD0F',
- 'Ll': '\u22D8',
- 'Lleftarrow': '\u21DA',
- 'lAarr': '\u21DA',
- 'Lmidot': '\u013F',
- 'LongLeftArrow': '\u27F5',
- 'longleftarrow': '\u27F5',
- 'xlarr': '\u27F5',
- 'LongLeftRightArrow': '\u27F7',
- 'longleftrightarrow': '\u27F7',
- 'xharr': '\u27F7',
- 'LongRightArrow': '\u27F6',
- 'longrightarrow': '\u27F6',
- 'xrarr': '\u27F6',
- 'Lopf': '\uD835\uDD43',
- 'LowerLeftArrow': '\u2199',
- 'swarr': '\u2199',
- 'swarrow': '\u2199',
- 'LowerRightArrow': '\u2198',
- 'searr': '\u2198',
- 'searrow': '\u2198',
- 'Lsh': '\u21B0',
- 'lsh': '\u21B0',
- 'Lstrok': '\u0141',
- 'Lt': '\u226A',
- 'NestedLessLess': '\u226A',
- 'll': '\u226A',
- 'Map': '\u2905',
- 'Mcy': '\u041C',
- 'MediumSpace': '\u205F',
- 'Mellintrf': '\u2133',
- 'Mscr': '\u2133',
- 'phmmat': '\u2133',
- 'Mfr': '\uD835\uDD10',
- 'MinusPlus': '\u2213',
- 'mnplus': '\u2213',
- 'mp': '\u2213',
- 'Mopf': '\uD835\uDD44',
- 'Mu': '\u039C',
- 'NJcy': '\u040A',
- 'Nacute': '\u0143',
- 'Ncaron': '\u0147',
- 'Ncedil': '\u0145',
- 'Ncy': '\u041D',
- 'NegativeMediumSpace': '\u200B',
- 'NegativeThickSpace': '\u200B',
- 'NegativeThinSpace': '\u200B',
- 'NegativeVeryThinSpace': '\u200B',
- 'ZeroWidthSpace': '\u200B',
- 'NewLine': '\u000A',
- 'Nfr': '\uD835\uDD11',
- 'NoBreak': '\u2060',
- 'NonBreakingSpace': '\u00A0',
- 'nbsp': '\u00A0',
- 'Nopf': '\u2115',
- 'naturals': '\u2115',
- 'Not': '\u2AEC',
- 'NotCongruent': '\u2262',
- 'nequiv': '\u2262',
- 'NotCupCap': '\u226D',
- 'NotDoubleVerticalBar': '\u2226',
- 'npar': '\u2226',
- 'nparallel': '\u2226',
- 'nshortparallel': '\u2226',
- 'nspar': '\u2226',
- 'NotElement': '\u2209',
- 'notin': '\u2209',
- 'notinva': '\u2209',
- 'NotEqual': '\u2260',
- 'ne': '\u2260',
- 'NotEqualTilde': '\u2242\u0338',
- 'nesim': '\u2242\u0338',
- 'NotExists': '\u2204',
- 'nexist': '\u2204',
- 'nexists': '\u2204',
- 'NotGreater': '\u226F',
- 'ngt': '\u226F',
- 'ngtr': '\u226F',
- 'NotGreaterEqual': '\u2271',
- 'nge': '\u2271',
- 'ngeq': '\u2271',
- 'NotGreaterFullEqual': '\u2267\u0338',
- 'ngE': '\u2267\u0338',
- 'ngeqq': '\u2267\u0338',
- 'NotGreaterGreater': '\u226B\u0338',
- 'nGtv': '\u226B\u0338',
- 'NotGreaterLess': '\u2279',
- 'ntgl': '\u2279',
- 'NotGreaterSlantEqual': '\u2A7E\u0338',
- 'ngeqslant': '\u2A7E\u0338',
- 'nges': '\u2A7E\u0338',
- 'NotGreaterTilde': '\u2275',
- 'ngsim': '\u2275',
- 'NotHumpDownHump': '\u224E\u0338',
- 'nbump': '\u224E\u0338',
- 'NotHumpEqual': '\u224F\u0338',
- 'nbumpe': '\u224F\u0338',
- 'NotLeftTriangle': '\u22EA',
- 'nltri': '\u22EA',
- 'ntriangleleft': '\u22EA',
- 'NotLeftTriangleBar': '\u29CF\u0338',
- 'NotLeftTriangleEqual': '\u22EC',
- 'nltrie': '\u22EC',
- 'ntrianglelefteq': '\u22EC',
- 'NotLess': '\u226E',
- 'nless': '\u226E',
- 'nlt': '\u226E',
- 'NotLessEqual': '\u2270',
- 'nle': '\u2270',
- 'nleq': '\u2270',
- 'NotLessGreater': '\u2278',
- 'ntlg': '\u2278',
- 'NotLessLess': '\u226A\u0338',
- 'nLtv': '\u226A\u0338',
- 'NotLessSlantEqual': '\u2A7D\u0338',
- 'nleqslant': '\u2A7D\u0338',
- 'nles': '\u2A7D\u0338',
- 'NotLessTilde': '\u2274',
- 'nlsim': '\u2274',
- 'NotNestedGreaterGreater': '\u2AA2\u0338',
- 'NotNestedLessLess': '\u2AA1\u0338',
- 'NotPrecedes': '\u2280',
- 'npr': '\u2280',
- 'nprec': '\u2280',
- 'NotPrecedesEqual': '\u2AAF\u0338',
- 'npre': '\u2AAF\u0338',
- 'npreceq': '\u2AAF\u0338',
- 'NotPrecedesSlantEqual': '\u22E0',
- 'nprcue': '\u22E0',
- 'NotReverseElement': '\u220C',
- 'notni': '\u220C',
- 'notniva': '\u220C',
- 'NotRightTriangle': '\u22EB',
- 'nrtri': '\u22EB',
- 'ntriangleright': '\u22EB',
- 'NotRightTriangleBar': '\u29D0\u0338',
- 'NotRightTriangleEqual': '\u22ED',
- 'nrtrie': '\u22ED',
- 'ntrianglerighteq': '\u22ED',
- 'NotSquareSubset': '\u228F\u0338',
- 'NotSquareSubsetEqual': '\u22E2',
- 'nsqsube': '\u22E2',
- 'NotSquareSuperset': '\u2290\u0338',
- 'NotSquareSupersetEqual': '\u22E3',
- 'nsqsupe': '\u22E3',
- 'NotSubset': '\u2282\u20D2',
- 'nsubset': '\u2282\u20D2',
- 'vnsub': '\u2282\u20D2',
- 'NotSubsetEqual': '\u2288',
- 'nsube': '\u2288',
- 'nsubseteq': '\u2288',
- 'NotSucceeds': '\u2281',
- 'nsc': '\u2281',
- 'nsucc': '\u2281',
- 'NotSucceedsEqual': '\u2AB0\u0338',
- 'nsce': '\u2AB0\u0338',
- 'nsucceq': '\u2AB0\u0338',
- 'NotSucceedsSlantEqual': '\u22E1',
- 'nsccue': '\u22E1',
- 'NotSucceedsTilde': '\u227F\u0338',
- 'NotSuperset': '\u2283\u20D2',
- 'nsupset': '\u2283\u20D2',
- 'vnsup': '\u2283\u20D2',
- 'NotSupersetEqual': '\u2289',
- 'nsupe': '\u2289',
- 'nsupseteq': '\u2289',
- 'NotTilde': '\u2241',
- 'nsim': '\u2241',
- 'NotTildeEqual': '\u2244',
- 'nsime': '\u2244',
- 'nsimeq': '\u2244',
- 'NotTildeFullEqual': '\u2247',
- 'ncong': '\u2247',
- 'NotTildeTilde': '\u2249',
- 'nap': '\u2249',
- 'napprox': '\u2249',
- 'NotVerticalBar': '\u2224',
- 'nmid': '\u2224',
- 'nshortmid': '\u2224',
- 'nsmid': '\u2224',
- 'Nscr': '\uD835\uDCA9',
- 'Ntilde': '\u00D1',
- 'Nu': '\u039D',
- 'OElig': '\u0152',
- 'Oacute': '\u00D3',
- 'Ocirc': '\u00D4',
- 'Ocy': '\u041E',
- 'Odblac': '\u0150',
- 'Ofr': '\uD835\uDD12',
- 'Ograve': '\u00D2',
- 'Omacr': '\u014C',
- 'Omega': '\u03A9',
- 'ohm': '\u03A9',
- 'Omicron': '\u039F',
- 'Oopf': '\uD835\uDD46',
- 'OpenCurlyDoubleQuote': '\u201C',
- 'ldquo': '\u201C',
- 'OpenCurlyQuote': '\u2018',
- 'lsquo': '\u2018',
- 'Or': '\u2A54',
- 'Oscr': '\uD835\uDCAA',
- 'Oslash': '\u00D8',
- 'Otilde': '\u00D5',
- 'Otimes': '\u2A37',
- 'Ouml': '\u00D6',
- 'OverBar': '\u203E',
- 'oline': '\u203E',
- 'OverBrace': '\u23DE',
- 'OverBracket': '\u23B4',
- 'tbrk': '\u23B4',
- 'OverParenthesis': '\u23DC',
- 'PartialD': '\u2202',
- 'part': '\u2202',
- 'Pcy': '\u041F',
- 'Pfr': '\uD835\uDD13',
- 'Phi': '\u03A6',
- 'Pi': '\u03A0',
- 'PlusMinus': '\u00B1',
- 'plusmn': '\u00B1',
- 'pm': '\u00B1',
- 'Popf': '\u2119',
- 'primes': '\u2119',
- 'Pr': '\u2ABB',
- 'Precedes': '\u227A',
- 'pr': '\u227A',
- 'prec': '\u227A',
- 'PrecedesEqual': '\u2AAF',
- 'pre': '\u2AAF',
- 'preceq': '\u2AAF',
- 'PrecedesSlantEqual': '\u227C',
- 'prcue': '\u227C',
- 'preccurlyeq': '\u227C',
- 'PrecedesTilde': '\u227E',
- 'precsim': '\u227E',
- 'prsim': '\u227E',
- 'Prime': '\u2033',
- 'Product': '\u220F',
- 'prod': '\u220F',
- 'Proportional': '\u221D',
- 'prop': '\u221D',
- 'propto': '\u221D',
- 'varpropto': '\u221D',
- 'vprop': '\u221D',
- 'Pscr': '\uD835\uDCAB',
- 'Psi': '\u03A8',
- 'QUOT': '\u0022',
- 'quot': '\u0022',
- 'Qfr': '\uD835\uDD14',
- 'Qopf': '\u211A',
- 'rationals': '\u211A',
- 'Qscr': '\uD835\uDCAC',
- 'RBarr': '\u2910',
- 'drbkarow': '\u2910',
- 'REG': '\u00AE',
- 'circledR': '\u00AE',
- 'reg': '\u00AE',
- 'Racute': '\u0154',
- 'Rang': '\u27EB',
- 'Rarr': '\u21A0',
- 'twoheadrightarrow': '\u21A0',
- 'Rarrtl': '\u2916',
- 'Rcaron': '\u0158',
- 'Rcedil': '\u0156',
- 'Rcy': '\u0420',
- 'Re': '\u211C',
- 'Rfr': '\u211C',
- 'real': '\u211C',
- 'realpart': '\u211C',
- 'ReverseElement': '\u220B',
- 'SuchThat': '\u220B',
- 'ni': '\u220B',
- 'niv': '\u220B',
- 'ReverseEquilibrium': '\u21CB',
- 'leftrightharpoons': '\u21CB',
- 'lrhar': '\u21CB',
- 'ReverseUpEquilibrium': '\u296F',
- 'duhar': '\u296F',
- 'Rho': '\u03A1',
- 'RightAngleBracket': '\u27E9',
- 'rang': '\u27E9',
- 'rangle': '\u27E9',
- 'RightArrow': '\u2192',
- 'ShortRightArrow': '\u2192',
- 'rarr': '\u2192',
- 'rightarrow': '\u2192',
- 'srarr': '\u2192',
- 'RightArrowBar': '\u21E5',
- 'rarrb': '\u21E5',
- 'RightArrowLeftArrow': '\u21C4',
- 'rightleftarrows': '\u21C4',
- 'rlarr': '\u21C4',
- 'RightCeiling': '\u2309',
- 'rceil': '\u2309',
- 'RightDoubleBracket': '\u27E7',
- 'robrk': '\u27E7',
- 'RightDownTeeVector': '\u295D',
- 'RightDownVector': '\u21C2',
- 'dharr': '\u21C2',
- 'downharpoonright': '\u21C2',
- 'RightDownVectorBar': '\u2955',
- 'RightFloor': '\u230B',
- 'rfloor': '\u230B',
- 'RightTee': '\u22A2',
- 'vdash': '\u22A2',
- 'RightTeeArrow': '\u21A6',
- 'map': '\u21A6',
- 'mapsto': '\u21A6',
- 'RightTeeVector': '\u295B',
- 'RightTriangle': '\u22B3',
- 'vartriangleright': '\u22B3',
- 'vrtri': '\u22B3',
- 'RightTriangleBar': '\u29D0',
- 'RightTriangleEqual': '\u22B5',
- 'rtrie': '\u22B5',
- 'trianglerighteq': '\u22B5',
- 'RightUpDownVector': '\u294F',
- 'RightUpTeeVector': '\u295C',
- 'RightUpVector': '\u21BE',
- 'uharr': '\u21BE',
- 'upharpoonright': '\u21BE',
- 'RightUpVectorBar': '\u2954',
- 'RightVector': '\u21C0',
- 'rharu': '\u21C0',
- 'rightharpoonup': '\u21C0',
- 'RightVectorBar': '\u2953',
- 'Ropf': '\u211D',
- 'reals': '\u211D',
- 'RoundImplies': '\u2970',
- 'Rrightarrow': '\u21DB',
- 'rAarr': '\u21DB',
- 'Rscr': '\u211B',
- 'realine': '\u211B',
- 'Rsh': '\u21B1',
- 'rsh': '\u21B1',
- 'RuleDelayed': '\u29F4',
- 'SHCHcy': '\u0429',
- 'SHcy': '\u0428',
- 'SOFTcy': '\u042C',
- 'Sacute': '\u015A',
- 'Sc': '\u2ABC',
- 'Scaron': '\u0160',
- 'Scedil': '\u015E',
- 'Scirc': '\u015C',
- 'Scy': '\u0421',
- 'Sfr': '\uD835\uDD16',
- 'ShortUpArrow': '\u2191',
- 'UpArrow': '\u2191',
- 'uarr': '\u2191',
- 'uparrow': '\u2191',
- 'Sigma': '\u03A3',
- 'SmallCircle': '\u2218',
- 'compfn': '\u2218',
- 'Sopf': '\uD835\uDD4A',
- 'Sqrt': '\u221A',
- 'radic': '\u221A',
- 'Square': '\u25A1',
- 'squ': '\u25A1',
- 'square': '\u25A1',
- 'SquareIntersection': '\u2293',
- 'sqcap': '\u2293',
- 'SquareSubset': '\u228F',
- 'sqsub': '\u228F',
- 'sqsubset': '\u228F',
- 'SquareSubsetEqual': '\u2291',
- 'sqsube': '\u2291',
- 'sqsubseteq': '\u2291',
- 'SquareSuperset': '\u2290',
- 'sqsup': '\u2290',
- 'sqsupset': '\u2290',
- 'SquareSupersetEqual': '\u2292',
- 'sqsupe': '\u2292',
- 'sqsupseteq': '\u2292',
- 'SquareUnion': '\u2294',
- 'sqcup': '\u2294',
- 'Sscr': '\uD835\uDCAE',
- 'Star': '\u22C6',
- 'sstarf': '\u22C6',
- 'Sub': '\u22D0',
- 'Subset': '\u22D0',
- 'SubsetEqual': '\u2286',
- 'sube': '\u2286',
- 'subseteq': '\u2286',
- 'Succeeds': '\u227B',
- 'sc': '\u227B',
- 'succ': '\u227B',
- 'SucceedsEqual': '\u2AB0',
- 'sce': '\u2AB0',
- 'succeq': '\u2AB0',
- 'SucceedsSlantEqual': '\u227D',
- 'sccue': '\u227D',
- 'succcurlyeq': '\u227D',
- 'SucceedsTilde': '\u227F',
- 'scsim': '\u227F',
- 'succsim': '\u227F',
- 'Sum': '\u2211',
- 'sum': '\u2211',
- 'Sup': '\u22D1',
- 'Supset': '\u22D1',
- 'Superset': '\u2283',
- 'sup': '\u2283',
- 'supset': '\u2283',
- 'SupersetEqual': '\u2287',
- 'supe': '\u2287',
- 'supseteq': '\u2287',
- 'THORN': '\u00DE',
- 'TRADE': '\u2122',
- 'trade': '\u2122',
- 'TSHcy': '\u040B',
- 'TScy': '\u0426',
- 'Tab': '\u0009',
- 'Tau': '\u03A4',
- 'Tcaron': '\u0164',
- 'Tcedil': '\u0162',
- 'Tcy': '\u0422',
- 'Tfr': '\uD835\uDD17',
- 'Therefore': '\u2234',
- 'there4': '\u2234',
- 'therefore': '\u2234',
- 'Theta': '\u0398',
- 'ThickSpace': '\u205F\u200A',
- 'ThinSpace': '\u2009',
- 'thinsp': '\u2009',
- 'Tilde': '\u223C',
- 'sim': '\u223C',
- 'thicksim': '\u223C',
- 'thksim': '\u223C',
- 'TildeEqual': '\u2243',
- 'sime': '\u2243',
- 'simeq': '\u2243',
- 'TildeFullEqual': '\u2245',
- 'cong': '\u2245',
- 'TildeTilde': '\u2248',
- 'ap': '\u2248',
- 'approx': '\u2248',
- 'asymp': '\u2248',
- 'thickapprox': '\u2248',
- 'thkap': '\u2248',
- 'Topf': '\uD835\uDD4B',
- 'TripleDot': '\u20DB',
- 'tdot': '\u20DB',
- 'Tscr': '\uD835\uDCAF',
- 'Tstrok': '\u0166',
- 'Uacute': '\u00DA',
- 'Uarr': '\u219F',
- 'Uarrocir': '\u2949',
- 'Ubrcy': '\u040E',
- 'Ubreve': '\u016C',
- 'Ucirc': '\u00DB',
- 'Ucy': '\u0423',
- 'Udblac': '\u0170',
- 'Ufr': '\uD835\uDD18',
- 'Ugrave': '\u00D9',
- 'Umacr': '\u016A',
- 'UnderBar': '\u005F',
- 'lowbar': '\u005F',
- 'UnderBrace': '\u23DF',
- 'UnderBracket': '\u23B5',
- 'bbrk': '\u23B5',
- 'UnderParenthesis': '\u23DD',
- 'Union': '\u22C3',
- 'bigcup': '\u22C3',
- 'xcup': '\u22C3',
- 'UnionPlus': '\u228E',
- 'uplus': '\u228E',
- 'Uogon': '\u0172',
- 'Uopf': '\uD835\uDD4C',
- 'UpArrowBar': '\u2912',
- 'UpArrowDownArrow': '\u21C5',
- 'udarr': '\u21C5',
- 'UpDownArrow': '\u2195',
- 'updownarrow': '\u2195',
- 'varr': '\u2195',
- 'UpEquilibrium': '\u296E',
- 'udhar': '\u296E',
- 'UpTee': '\u22A5',
- 'bot': '\u22A5',
- 'bottom': '\u22A5',
- 'perp': '\u22A5',
- 'UpTeeArrow': '\u21A5',
- 'mapstoup': '\u21A5',
- 'UpperLeftArrow': '\u2196',
- 'nwarr': '\u2196',
- 'nwarrow': '\u2196',
- 'UpperRightArrow': '\u2197',
- 'nearr': '\u2197',
- 'nearrow': '\u2197',
- 'Upsi': '\u03D2',
- 'upsih': '\u03D2',
- 'Upsilon': '\u03A5',
- 'Uring': '\u016E',
- 'Uscr': '\uD835\uDCB0',
- 'Utilde': '\u0168',
- 'Uuml': '\u00DC',
- 'VDash': '\u22AB',
- 'Vbar': '\u2AEB',
- 'Vcy': '\u0412',
- 'Vdash': '\u22A9',
- 'Vdashl': '\u2AE6',
- 'Vee': '\u22C1',
- 'bigvee': '\u22C1',
- 'xvee': '\u22C1',
- 'Verbar': '\u2016',
- 'Vert': '\u2016',
- 'VerticalBar': '\u2223',
- 'mid': '\u2223',
- 'shortmid': '\u2223',
- 'smid': '\u2223',
- 'VerticalLine': '\u007C',
- 'verbar': '\u007C',
- 'vert': '\u007C',
- 'VerticalSeparator': '\u2758',
- 'VerticalTilde': '\u2240',
- 'wr': '\u2240',
- 'wreath': '\u2240',
- 'VeryThinSpace': '\u200A',
- 'hairsp': '\u200A',
- 'Vfr': '\uD835\uDD19',
- 'Vopf': '\uD835\uDD4D',
- 'Vscr': '\uD835\uDCB1',
- 'Vvdash': '\u22AA',
- 'Wcirc': '\u0174',
- 'Wedge': '\u22C0',
- 'bigwedge': '\u22C0',
- 'xwedge': '\u22C0',
- 'Wfr': '\uD835\uDD1A',
- 'Wopf': '\uD835\uDD4E',
- 'Wscr': '\uD835\uDCB2',
- 'Xfr': '\uD835\uDD1B',
- 'Xi': '\u039E',
- 'Xopf': '\uD835\uDD4F',
- 'Xscr': '\uD835\uDCB3',
- 'YAcy': '\u042F',
- 'YIcy': '\u0407',
- 'YUcy': '\u042E',
- 'Yacute': '\u00DD',
- 'Ycirc': '\u0176',
- 'Ycy': '\u042B',
- 'Yfr': '\uD835\uDD1C',
- 'Yopf': '\uD835\uDD50',
- 'Yscr': '\uD835\uDCB4',
- 'Yuml': '\u0178',
- 'ZHcy': '\u0416',
- 'Zacute': '\u0179',
- 'Zcaron': '\u017D',
- 'Zcy': '\u0417',
- 'Zdot': '\u017B',
- 'Zeta': '\u0396',
- 'Zfr': '\u2128',
- 'zeetrf': '\u2128',
- 'Zopf': '\u2124',
- 'integers': '\u2124',
- 'Zscr': '\uD835\uDCB5',
- 'aacute': '\u00E1',
- 'abreve': '\u0103',
- 'ac': '\u223E',
- 'mstpos': '\u223E',
- 'acE': '\u223E\u0333',
- 'acd': '\u223F',
- 'acirc': '\u00E2',
- 'acy': '\u0430',
- 'aelig': '\u00E6',
- 'afr': '\uD835\uDD1E',
- 'agrave': '\u00E0',
- 'alefsym': '\u2135',
- 'aleph': '\u2135',
- 'alpha': '\u03B1',
- 'amacr': '\u0101',
- 'amalg': '\u2A3F',
- 'and': '\u2227',
- 'wedge': '\u2227',
- 'andand': '\u2A55',
- 'andd': '\u2A5C',
- 'andslope': '\u2A58',
- 'andv': '\u2A5A',
- 'ang': '\u2220',
- 'angle': '\u2220',
- 'ange': '\u29A4',
- 'angmsd': '\u2221',
- 'measuredangle': '\u2221',
- 'angmsdaa': '\u29A8',
- 'angmsdab': '\u29A9',
- 'angmsdac': '\u29AA',
- 'angmsdad': '\u29AB',
- 'angmsdae': '\u29AC',
- 'angmsdaf': '\u29AD',
- 'angmsdag': '\u29AE',
- 'angmsdah': '\u29AF',
- 'angrt': '\u221F',
- 'angrtvb': '\u22BE',
- 'angrtvbd': '\u299D',
- 'angsph': '\u2222',
- 'angzarr': '\u237C',
- 'aogon': '\u0105',
- 'aopf': '\uD835\uDD52',
- 'apE': '\u2A70',
- 'apacir': '\u2A6F',
- 'ape': '\u224A',
- 'approxeq': '\u224A',
- 'apid': '\u224B',
- 'apos': '\u0027',
- 'aring': '\u00E5',
- 'ascr': '\uD835\uDCB6',
- 'ast': '\u002A',
- 'midast': '\u002A',
- 'atilde': '\u00E3',
- 'auml': '\u00E4',
- 'awint': '\u2A11',
- 'bNot': '\u2AED',
- 'backcong': '\u224C',
- 'bcong': '\u224C',
- 'backepsilon': '\u03F6',
- 'bepsi': '\u03F6',
- 'backprime': '\u2035',
- 'bprime': '\u2035',
- 'backsim': '\u223D',
- 'bsim': '\u223D',
- 'backsimeq': '\u22CD',
- 'bsime': '\u22CD',
- 'barvee': '\u22BD',
- 'barwed': '\u2305',
- 'barwedge': '\u2305',
- 'bbrktbrk': '\u23B6',
- 'bcy': '\u0431',
- 'bdquo': '\u201E',
- 'ldquor': '\u201E',
- 'bemptyv': '\u29B0',
- 'beta': '\u03B2',
- 'beth': '\u2136',
- 'between': '\u226C',
- 'twixt': '\u226C',
- 'bfr': '\uD835\uDD1F',
- 'bigcirc': '\u25EF',
- 'xcirc': '\u25EF',
- 'bigodot': '\u2A00',
- 'xodot': '\u2A00',
- 'bigoplus': '\u2A01',
- 'xoplus': '\u2A01',
- 'bigotimes': '\u2A02',
- 'xotime': '\u2A02',
- 'bigsqcup': '\u2A06',
- 'xsqcup': '\u2A06',
- 'bigstar': '\u2605',
- 'starf': '\u2605',
- 'bigtriangledown': '\u25BD',
- 'xdtri': '\u25BD',
- 'bigtriangleup': '\u25B3',
- 'xutri': '\u25B3',
- 'biguplus': '\u2A04',
- 'xuplus': '\u2A04',
- 'bkarow': '\u290D',
- 'rbarr': '\u290D',
- 'blacklozenge': '\u29EB',
- 'lozf': '\u29EB',
- 'blacktriangle': '\u25B4',
- 'utrif': '\u25B4',
- 'blacktriangledown': '\u25BE',
- 'dtrif': '\u25BE',
- 'blacktriangleleft': '\u25C2',
- 'ltrif': '\u25C2',
- 'blacktriangleright': '\u25B8',
- 'rtrif': '\u25B8',
- 'blank': '\u2423',
- 'blk12': '\u2592',
- 'blk14': '\u2591',
- 'blk34': '\u2593',
- 'block': '\u2588',
- 'bne': '\u003D\u20E5',
- 'bnequiv': '\u2261\u20E5',
- 'bnot': '\u2310',
- 'bopf': '\uD835\uDD53',
- 'bowtie': '\u22C8',
- 'boxDL': '\u2557',
- 'boxDR': '\u2554',
- 'boxDl': '\u2556',
- 'boxDr': '\u2553',
- 'boxH': '\u2550',
- 'boxHD': '\u2566',
- 'boxHU': '\u2569',
- 'boxHd': '\u2564',
- 'boxHu': '\u2567',
- 'boxUL': '\u255D',
- 'boxUR': '\u255A',
- 'boxUl': '\u255C',
- 'boxUr': '\u2559',
- 'boxV': '\u2551',
- 'boxVH': '\u256C',
- 'boxVL': '\u2563',
- 'boxVR': '\u2560',
- 'boxVh': '\u256B',
- 'boxVl': '\u2562',
- 'boxVr': '\u255F',
- 'boxbox': '\u29C9',
- 'boxdL': '\u2555',
- 'boxdR': '\u2552',
- 'boxdl': '\u2510',
- 'boxdr': '\u250C',
- 'boxhD': '\u2565',
- 'boxhU': '\u2568',
- 'boxhd': '\u252C',
- 'boxhu': '\u2534',
- 'boxminus': '\u229F',
- 'minusb': '\u229F',
- 'boxplus': '\u229E',
- 'plusb': '\u229E',
- 'boxtimes': '\u22A0',
- 'timesb': '\u22A0',
- 'boxuL': '\u255B',
- 'boxuR': '\u2558',
- 'boxul': '\u2518',
- 'boxur': '\u2514',
- 'boxv': '\u2502',
- 'boxvH': '\u256A',
- 'boxvL': '\u2561',
- 'boxvR': '\u255E',
- 'boxvh': '\u253C',
- 'boxvl': '\u2524',
- 'boxvr': '\u251C',
- 'brvbar': '\u00A6',
- 'bscr': '\uD835\uDCB7',
- 'bsemi': '\u204F',
- 'bsol': '\u005C',
- 'bsolb': '\u29C5',
- 'bsolhsub': '\u27C8',
- 'bull': '\u2022',
- 'bullet': '\u2022',
- 'bumpE': '\u2AAE',
- 'cacute': '\u0107',
- 'cap': '\u2229',
- 'capand': '\u2A44',
- 'capbrcup': '\u2A49',
- 'capcap': '\u2A4B',
- 'capcup': '\u2A47',
- 'capdot': '\u2A40',
- 'caps': '\u2229\uFE00',
- 'caret': '\u2041',
- 'ccaps': '\u2A4D',
- 'ccaron': '\u010D',
- 'ccedil': '\u00E7',
- 'ccirc': '\u0109',
- 'ccups': '\u2A4C',
- 'ccupssm': '\u2A50',
- 'cdot': '\u010B',
- 'cemptyv': '\u29B2',
- 'cent': '\u00A2',
- 'cfr': '\uD835\uDD20',
- 'chcy': '\u0447',
- 'check': '\u2713',
- 'checkmark': '\u2713',
- 'chi': '\u03C7',
- 'cir': '\u25CB',
- 'cirE': '\u29C3',
- 'circ': '\u02C6',
- 'circeq': '\u2257',
- 'cire': '\u2257',
- 'circlearrowleft': '\u21BA',
- 'olarr': '\u21BA',
- 'circlearrowright': '\u21BB',
- 'orarr': '\u21BB',
- 'circledS': '\u24C8',
- 'oS': '\u24C8',
- 'circledast': '\u229B',
- 'oast': '\u229B',
- 'circledcirc': '\u229A',
- 'ocir': '\u229A',
- 'circleddash': '\u229D',
- 'odash': '\u229D',
- 'cirfnint': '\u2A10',
- 'cirmid': '\u2AEF',
- 'cirscir': '\u29C2',
- 'clubs': '\u2663',
- 'clubsuit': '\u2663',
- 'colon': '\u003A',
- 'comma': '\u002C',
- 'commat': '\u0040',
- 'comp': '\u2201',
- 'complement': '\u2201',
- 'congdot': '\u2A6D',
- 'copf': '\uD835\uDD54',
- 'copysr': '\u2117',
- 'crarr': '\u21B5',
- 'cross': '\u2717',
- 'cscr': '\uD835\uDCB8',
- 'csub': '\u2ACF',
- 'csube': '\u2AD1',
- 'csup': '\u2AD0',
- 'csupe': '\u2AD2',
- 'ctdot': '\u22EF',
- 'cudarrl': '\u2938',
- 'cudarrr': '\u2935',
- 'cuepr': '\u22DE',
- 'curlyeqprec': '\u22DE',
- 'cuesc': '\u22DF',
- 'curlyeqsucc': '\u22DF',
- 'cularr': '\u21B6',
- 'curvearrowleft': '\u21B6',
- 'cularrp': '\u293D',
- 'cup': '\u222A',
- 'cupbrcap': '\u2A48',
- 'cupcap': '\u2A46',
- 'cupcup': '\u2A4A',
- 'cupdot': '\u228D',
- 'cupor': '\u2A45',
- 'cups': '\u222A\uFE00',
- 'curarr': '\u21B7',
- 'curvearrowright': '\u21B7',
- 'curarrm': '\u293C',
- 'curlyvee': '\u22CE',
- 'cuvee': '\u22CE',
- 'curlywedge': '\u22CF',
- 'cuwed': '\u22CF',
- 'curren': '\u00A4',
- 'cwint': '\u2231',
- 'cylcty': '\u232D',
- 'dHar': '\u2965',
- 'dagger': '\u2020',
- 'daleth': '\u2138',
- 'dash': '\u2010',
- 'hyphen': '\u2010',
- 'dbkarow': '\u290F',
- 'rBarr': '\u290F',
- 'dcaron': '\u010F',
- 'dcy': '\u0434',
- 'ddarr': '\u21CA',
- 'downdownarrows': '\u21CA',
- 'ddotseq': '\u2A77',
- 'eDDot': '\u2A77',
- 'deg': '\u00B0',
- 'delta': '\u03B4',
- 'demptyv': '\u29B1',
- 'dfisht': '\u297F',
- 'dfr': '\uD835\uDD21',
- 'diamondsuit': '\u2666',
- 'diams': '\u2666',
- 'digamma': '\u03DD',
- 'gammad': '\u03DD',
- 'disin': '\u22F2',
- 'div': '\u00F7',
- 'divide': '\u00F7',
- 'divideontimes': '\u22C7',
- 'divonx': '\u22C7',
- 'djcy': '\u0452',
- 'dlcorn': '\u231E',
- 'llcorner': '\u231E',
- 'dlcrop': '\u230D',
- 'dollar': '\u0024',
- 'dopf': '\uD835\uDD55',
- 'doteqdot': '\u2251',
- 'eDot': '\u2251',
- 'dotminus': '\u2238',
- 'minusd': '\u2238',
- 'dotplus': '\u2214',
- 'plusdo': '\u2214',
- 'dotsquare': '\u22A1',
- 'sdotb': '\u22A1',
- 'drcorn': '\u231F',
- 'lrcorner': '\u231F',
- 'drcrop': '\u230C',
- 'dscr': '\uD835\uDCB9',
- 'dscy': '\u0455',
- 'dsol': '\u29F6',
- 'dstrok': '\u0111',
- 'dtdot': '\u22F1',
- 'dtri': '\u25BF',
- 'triangledown': '\u25BF',
- 'dwangle': '\u29A6',
- 'dzcy': '\u045F',
- 'dzigrarr': '\u27FF',
- 'eacute': '\u00E9',
- 'easter': '\u2A6E',
- 'ecaron': '\u011B',
- 'ecir': '\u2256',
- 'eqcirc': '\u2256',
- 'ecirc': '\u00EA',
- 'ecolon': '\u2255',
- 'eqcolon': '\u2255',
- 'ecy': '\u044D',
- 'edot': '\u0117',
- 'efDot': '\u2252',
- 'fallingdotseq': '\u2252',
- 'efr': '\uD835\uDD22',
- 'eg': '\u2A9A',
- 'egrave': '\u00E8',
- 'egs': '\u2A96',
- 'eqslantgtr': '\u2A96',
- 'egsdot': '\u2A98',
- 'el': '\u2A99',
- 'elinters': '\u23E7',
- 'ell': '\u2113',
- 'els': '\u2A95',
- 'eqslantless': '\u2A95',
- 'elsdot': '\u2A97',
- 'emacr': '\u0113',
- 'empty': '\u2205',
- 'emptyset': '\u2205',
- 'emptyv': '\u2205',
- 'varnothing': '\u2205',
- 'emsp13': '\u2004',
- 'emsp14': '\u2005',
- 'emsp': '\u2003',
- 'eng': '\u014B',
- 'ensp': '\u2002',
- 'eogon': '\u0119',
- 'eopf': '\uD835\uDD56',
- 'epar': '\u22D5',
- 'eparsl': '\u29E3',
- 'eplus': '\u2A71',
- 'epsi': '\u03B5',
- 'epsilon': '\u03B5',
- 'epsiv': '\u03F5',
- 'straightepsilon': '\u03F5',
- 'varepsilon': '\u03F5',
- 'equals': '\u003D',
- 'equest': '\u225F',
- 'questeq': '\u225F',
- 'equivDD': '\u2A78',
- 'eqvparsl': '\u29E5',
- 'erDot': '\u2253',
- 'risingdotseq': '\u2253',
- 'erarr': '\u2971',
- 'escr': '\u212F',
- 'eta': '\u03B7',
- 'eth': '\u00F0',
- 'euml': '\u00EB',
- 'euro': '\u20AC',
- 'excl': '\u0021',
- 'fcy': '\u0444',
- 'female': '\u2640',
- 'ffilig': '\uFB03',
- 'fflig': '\uFB00',
- 'ffllig': '\uFB04',
- 'ffr': '\uD835\uDD23',
- 'filig': '\uFB01',
- 'fjlig': '\u0066\u006A',
- 'flat': '\u266D',
- 'fllig': '\uFB02',
- 'fltns': '\u25B1',
- 'fnof': '\u0192',
- 'fopf': '\uD835\uDD57',
- 'fork': '\u22D4',
- 'pitchfork': '\u22D4',
- 'forkv': '\u2AD9',
- 'fpartint': '\u2A0D',
- 'frac12': '\u00BD',
- 'half': '\u00BD',
- 'frac13': '\u2153',
- 'frac14': '\u00BC',
- 'frac15': '\u2155',
- 'frac16': '\u2159',
- 'frac18': '\u215B',
- 'frac23': '\u2154',
- 'frac25': '\u2156',
- 'frac34': '\u00BE',
- 'frac35': '\u2157',
- 'frac38': '\u215C',
- 'frac45': '\u2158',
- 'frac56': '\u215A',
- 'frac58': '\u215D',
- 'frac78': '\u215E',
- 'frasl': '\u2044',
- 'frown': '\u2322',
- 'sfrown': '\u2322',
- 'fscr': '\uD835\uDCBB',
- 'gEl': '\u2A8C',
- 'gtreqqless': '\u2A8C',
- 'gacute': '\u01F5',
- 'gamma': '\u03B3',
- 'gap': '\u2A86',
- 'gtrapprox': '\u2A86',
- 'gbreve': '\u011F',
- 'gcirc': '\u011D',
- 'gcy': '\u0433',
- 'gdot': '\u0121',
- 'gescc': '\u2AA9',
- 'gesdot': '\u2A80',
- 'gesdoto': '\u2A82',
- 'gesdotol': '\u2A84',
- 'gesl': '\u22DB\uFE00',
- 'gesles': '\u2A94',
- 'gfr': '\uD835\uDD24',
- 'gimel': '\u2137',
- 'gjcy': '\u0453',
- 'glE': '\u2A92',
- 'gla': '\u2AA5',
- 'glj': '\u2AA4',
- 'gnE': '\u2269',
- 'gneqq': '\u2269',
- 'gnap': '\u2A8A',
- 'gnapprox': '\u2A8A',
- 'gne': '\u2A88',
- 'gneq': '\u2A88',
- 'gnsim': '\u22E7',
- 'gopf': '\uD835\uDD58',
- 'gscr': '\u210A',
- 'gsime': '\u2A8E',
- 'gsiml': '\u2A90',
- 'gtcc': '\u2AA7',
- 'gtcir': '\u2A7A',
- 'gtdot': '\u22D7',
- 'gtrdot': '\u22D7',
- 'gtlPar': '\u2995',
- 'gtquest': '\u2A7C',
- 'gtrarr': '\u2978',
- 'gvertneqq': '\u2269\uFE00',
- 'gvnE': '\u2269\uFE00',
- 'hardcy': '\u044A',
- 'harrcir': '\u2948',
- 'harrw': '\u21AD',
- 'leftrightsquigarrow': '\u21AD',
- 'hbar': '\u210F',
- 'hslash': '\u210F',
- 'planck': '\u210F',
- 'plankv': '\u210F',
- 'hcirc': '\u0125',
- 'hearts': '\u2665',
- 'heartsuit': '\u2665',
- 'hellip': '\u2026',
- 'mldr': '\u2026',
- 'hercon': '\u22B9',
- 'hfr': '\uD835\uDD25',
- 'hksearow': '\u2925',
- 'searhk': '\u2925',
- 'hkswarow': '\u2926',
- 'swarhk': '\u2926',
- 'hoarr': '\u21FF',
- 'homtht': '\u223B',
- 'hookleftarrow': '\u21A9',
- 'larrhk': '\u21A9',
- 'hookrightarrow': '\u21AA',
- 'rarrhk': '\u21AA',
- 'hopf': '\uD835\uDD59',
- 'horbar': '\u2015',
- 'hscr': '\uD835\uDCBD',
- 'hstrok': '\u0127',
- 'hybull': '\u2043',
- 'iacute': '\u00ED',
- 'icirc': '\u00EE',
- 'icy': '\u0438',
- 'iecy': '\u0435',
- 'iexcl': '\u00A1',
- 'ifr': '\uD835\uDD26',
- 'igrave': '\u00EC',
- 'iiiint': '\u2A0C',
- 'qint': '\u2A0C',
- 'iiint': '\u222D',
- 'tint': '\u222D',
- 'iinfin': '\u29DC',
- 'iiota': '\u2129',
- 'ijlig': '\u0133',
- 'imacr': '\u012B',
- 'imath': '\u0131',
- 'inodot': '\u0131',
- 'imof': '\u22B7',
- 'imped': '\u01B5',
- 'incare': '\u2105',
- 'infin': '\u221E',
- 'infintie': '\u29DD',
- 'intcal': '\u22BA',
- 'intercal': '\u22BA',
- 'intlarhk': '\u2A17',
- 'intprod': '\u2A3C',
- 'iprod': '\u2A3C',
- 'iocy': '\u0451',
- 'iogon': '\u012F',
- 'iopf': '\uD835\uDD5A',
- 'iota': '\u03B9',
- 'iquest': '\u00BF',
- 'iscr': '\uD835\uDCBE',
- 'isinE': '\u22F9',
- 'isindot': '\u22F5',
- 'isins': '\u22F4',
- 'isinsv': '\u22F3',
- 'itilde': '\u0129',
- 'iukcy': '\u0456',
- 'iuml': '\u00EF',
- 'jcirc': '\u0135',
- 'jcy': '\u0439',
- 'jfr': '\uD835\uDD27',
- 'jmath': '\u0237',
- 'jopf': '\uD835\uDD5B',
- 'jscr': '\uD835\uDCBF',
- 'jsercy': '\u0458',
- 'jukcy': '\u0454',
- 'kappa': '\u03BA',
- 'kappav': '\u03F0',
- 'varkappa': '\u03F0',
- 'kcedil': '\u0137',
- 'kcy': '\u043A',
- 'kfr': '\uD835\uDD28',
- 'kgreen': '\u0138',
- 'khcy': '\u0445',
- 'kjcy': '\u045C',
- 'kopf': '\uD835\uDD5C',
- 'kscr': '\uD835\uDCC0',
- 'lAtail': '\u291B',
- 'lBarr': '\u290E',
- 'lEg': '\u2A8B',
- 'lesseqqgtr': '\u2A8B',
- 'lHar': '\u2962',
- 'lacute': '\u013A',
- 'laemptyv': '\u29B4',
- 'lambda': '\u03BB',
- 'langd': '\u2991',
- 'lap': '\u2A85',
- 'lessapprox': '\u2A85',
- 'laquo': '\u00AB',
- 'larrbfs': '\u291F',
- 'larrfs': '\u291D',
- 'larrlp': '\u21AB',
- 'looparrowleft': '\u21AB',
- 'larrpl': '\u2939',
- 'larrsim': '\u2973',
- 'larrtl': '\u21A2',
- 'leftarrowtail': '\u21A2',
- 'lat': '\u2AAB',
- 'latail': '\u2919',
- 'late': '\u2AAD',
- 'lates': '\u2AAD\uFE00',
- 'lbarr': '\u290C',
- 'lbbrk': '\u2772',
- 'lbrace': '\u007B',
- 'lcub': '\u007B',
- 'lbrack': '\u005B',
- 'lsqb': '\u005B',
- 'lbrke': '\u298B',
- 'lbrksld': '\u298F',
- 'lbrkslu': '\u298D',
- 'lcaron': '\u013E',
- 'lcedil': '\u013C',
- 'lcy': '\u043B',
- 'ldca': '\u2936',
- 'ldrdhar': '\u2967',
- 'ldrushar': '\u294B',
- 'ldsh': '\u21B2',
- 'le': '\u2264',
- 'leq': '\u2264',
- 'leftleftarrows': '\u21C7',
- 'llarr': '\u21C7',
- 'leftthreetimes': '\u22CB',
- 'lthree': '\u22CB',
- 'lescc': '\u2AA8',
- 'lesdot': '\u2A7F',
- 'lesdoto': '\u2A81',
- 'lesdotor': '\u2A83',
- 'lesg': '\u22DA\uFE00',
- 'lesges': '\u2A93',
- 'lessdot': '\u22D6',
- 'ltdot': '\u22D6',
- 'lfisht': '\u297C',
- 'lfr': '\uD835\uDD29',
- 'lgE': '\u2A91',
- 'lharul': '\u296A',
- 'lhblk': '\u2584',
- 'ljcy': '\u0459',
- 'llhard': '\u296B',
- 'lltri': '\u25FA',
- 'lmidot': '\u0140',
- 'lmoust': '\u23B0',
- 'lmoustache': '\u23B0',
- 'lnE': '\u2268',
- 'lneqq': '\u2268',
- 'lnap': '\u2A89',
- 'lnapprox': '\u2A89',
- 'lne': '\u2A87',
- 'lneq': '\u2A87',
- 'lnsim': '\u22E6',
- 'loang': '\u27EC',
- 'loarr': '\u21FD',
- 'longmapsto': '\u27FC',
- 'xmap': '\u27FC',
- 'looparrowright': '\u21AC',
- 'rarrlp': '\u21AC',
- 'lopar': '\u2985',
- 'lopf': '\uD835\uDD5D',
- 'loplus': '\u2A2D',
- 'lotimes': '\u2A34',
- 'lowast': '\u2217',
- 'loz': '\u25CA',
- 'lozenge': '\u25CA',
- 'lpar': '\u0028',
- 'lparlt': '\u2993',
- 'lrhard': '\u296D',
- 'lrm': '\u200E',
- 'lrtri': '\u22BF',
- 'lsaquo': '\u2039',
- 'lscr': '\uD835\uDCC1',
- 'lsime': '\u2A8D',
- 'lsimg': '\u2A8F',
- 'lsquor': '\u201A',
- 'sbquo': '\u201A',
- 'lstrok': '\u0142',
- 'ltcc': '\u2AA6',
- 'ltcir': '\u2A79',
- 'ltimes': '\u22C9',
- 'ltlarr': '\u2976',
- 'ltquest': '\u2A7B',
- 'ltrPar': '\u2996',
- 'ltri': '\u25C3',
- 'triangleleft': '\u25C3',
- 'lurdshar': '\u294A',
- 'luruhar': '\u2966',
- 'lvertneqq': '\u2268\uFE00',
- 'lvnE': '\u2268\uFE00',
- 'mDDot': '\u223A',
- 'macr': '\u00AF',
- 'strns': '\u00AF',
- 'male': '\u2642',
- 'malt': '\u2720',
- 'maltese': '\u2720',
- 'marker': '\u25AE',
- 'mcomma': '\u2A29',
- 'mcy': '\u043C',
- 'mdash': '\u2014',
- 'mfr': '\uD835\uDD2A',
- 'mho': '\u2127',
- 'micro': '\u00B5',
- 'midcir': '\u2AF0',
- 'minus': '\u2212',
- 'minusdu': '\u2A2A',
- 'mlcp': '\u2ADB',
- 'models': '\u22A7',
- 'mopf': '\uD835\uDD5E',
- 'mscr': '\uD835\uDCC2',
- 'mu': '\u03BC',
- 'multimap': '\u22B8',
- 'mumap': '\u22B8',
- 'nGg': '\u22D9\u0338',
- 'nGt': '\u226B\u20D2',
- 'nLeftarrow': '\u21CD',
- 'nlArr': '\u21CD',
- 'nLeftrightarrow': '\u21CE',
- 'nhArr': '\u21CE',
- 'nLl': '\u22D8\u0338',
- 'nLt': '\u226A\u20D2',
- 'nRightarrow': '\u21CF',
- 'nrArr': '\u21CF',
- 'nVDash': '\u22AF',
- 'nVdash': '\u22AE',
- 'nacute': '\u0144',
- 'nang': '\u2220\u20D2',
- 'napE': '\u2A70\u0338',
- 'napid': '\u224B\u0338',
- 'napos': '\u0149',
- 'natur': '\u266E',
- 'natural': '\u266E',
- 'ncap': '\u2A43',
- 'ncaron': '\u0148',
- 'ncedil': '\u0146',
- 'ncongdot': '\u2A6D\u0338',
- 'ncup': '\u2A42',
- 'ncy': '\u043D',
- 'ndash': '\u2013',
- 'neArr': '\u21D7',
- 'nearhk': '\u2924',
- 'nedot': '\u2250\u0338',
- 'nesear': '\u2928',
- 'toea': '\u2928',
- 'nfr': '\uD835\uDD2B',
- 'nharr': '\u21AE',
- 'nleftrightarrow': '\u21AE',
- 'nhpar': '\u2AF2',
- 'nis': '\u22FC',
- 'nisd': '\u22FA',
- 'njcy': '\u045A',
- 'nlE': '\u2266\u0338',
- 'nleqq': '\u2266\u0338',
- 'nlarr': '\u219A',
- 'nleftarrow': '\u219A',
- 'nldr': '\u2025',
- 'nopf': '\uD835\uDD5F',
- 'not': '\u00AC',
- 'notinE': '\u22F9\u0338',
- 'notindot': '\u22F5\u0338',
- 'notinvb': '\u22F7',
- 'notinvc': '\u22F6',
- 'notnivb': '\u22FE',
- 'notnivc': '\u22FD',
- 'nparsl': '\u2AFD\u20E5',
- 'npart': '\u2202\u0338',
- 'npolint': '\u2A14',
- 'nrarr': '\u219B',
- 'nrightarrow': '\u219B',
- 'nrarrc': '\u2933\u0338',
- 'nrarrw': '\u219D\u0338',
- 'nscr': '\uD835\uDCC3',
- 'nsub': '\u2284',
- 'nsubE': '\u2AC5\u0338',
- 'nsubseteqq': '\u2AC5\u0338',
- 'nsup': '\u2285',
- 'nsupE': '\u2AC6\u0338',
- 'nsupseteqq': '\u2AC6\u0338',
- 'ntilde': '\u00F1',
- 'nu': '\u03BD',
- 'num': '\u0023',
- 'numero': '\u2116',
- 'numsp': '\u2007',
- 'nvDash': '\u22AD',
- 'nvHarr': '\u2904',
- 'nvap': '\u224D\u20D2',
- 'nvdash': '\u22AC',
- 'nvge': '\u2265\u20D2',
- 'nvgt': '\u003E\u20D2',
- 'nvinfin': '\u29DE',
- 'nvlArr': '\u2902',
- 'nvle': '\u2264\u20D2',
- 'nvlt': '\u003C\u20D2',
- 'nvltrie': '\u22B4\u20D2',
- 'nvrArr': '\u2903',
- 'nvrtrie': '\u22B5\u20D2',
- 'nvsim': '\u223C\u20D2',
- 'nwArr': '\u21D6',
- 'nwarhk': '\u2923',
- 'nwnear': '\u2927',
- 'oacute': '\u00F3',
- 'ocirc': '\u00F4',
- 'ocy': '\u043E',
- 'odblac': '\u0151',
- 'odiv': '\u2A38',
- 'odsold': '\u29BC',
- 'oelig': '\u0153',
- 'ofcir': '\u29BF',
- 'ofr': '\uD835\uDD2C',
- 'ogon': '\u02DB',
- 'ograve': '\u00F2',
- 'ogt': '\u29C1',
- 'ohbar': '\u29B5',
- 'olcir': '\u29BE',
- 'olcross': '\u29BB',
- 'olt': '\u29C0',
- 'omacr': '\u014D',
- 'omega': '\u03C9',
- 'omicron': '\u03BF',
- 'omid': '\u29B6',
- 'oopf': '\uD835\uDD60',
- 'opar': '\u29B7',
- 'operp': '\u29B9',
- 'or': '\u2228',
- 'vee': '\u2228',
- 'ord': '\u2A5D',
- 'order': '\u2134',
- 'orderof': '\u2134',
- 'oscr': '\u2134',
- 'ordf': '\u00AA',
- 'ordm': '\u00BA',
- 'origof': '\u22B6',
- 'oror': '\u2A56',
- 'orslope': '\u2A57',
- 'orv': '\u2A5B',
- 'oslash': '\u00F8',
- 'osol': '\u2298',
- 'otilde': '\u00F5',
- 'otimesas': '\u2A36',
- 'ouml': '\u00F6',
- 'ovbar': '\u233D',
- 'para': '\u00B6',
- 'parsim': '\u2AF3',
- 'parsl': '\u2AFD',
- 'pcy': '\u043F',
- 'percnt': '\u0025',
- 'period': '\u002E',
- 'permil': '\u2030',
- 'pertenk': '\u2031',
- 'pfr': '\uD835\uDD2D',
- 'phi': '\u03C6',
- 'phiv': '\u03D5',
- 'straightphi': '\u03D5',
- 'varphi': '\u03D5',
- 'phone': '\u260E',
- 'pi': '\u03C0',
- 'piv': '\u03D6',
- 'varpi': '\u03D6',
- 'planckh': '\u210E',
- 'plus': '\u002B',
- 'plusacir': '\u2A23',
- 'pluscir': '\u2A22',
- 'plusdu': '\u2A25',
- 'pluse': '\u2A72',
- 'plussim': '\u2A26',
- 'plustwo': '\u2A27',
- 'pointint': '\u2A15',
- 'popf': '\uD835\uDD61',
- 'pound': '\u00A3',
- 'prE': '\u2AB3',
- 'prap': '\u2AB7',
- 'precapprox': '\u2AB7',
- 'precnapprox': '\u2AB9',
- 'prnap': '\u2AB9',
- 'precneqq': '\u2AB5',
- 'prnE': '\u2AB5',
- 'precnsim': '\u22E8',
- 'prnsim': '\u22E8',
- 'prime': '\u2032',
- 'profalar': '\u232E',
- 'profline': '\u2312',
- 'profsurf': '\u2313',
- 'prurel': '\u22B0',
- 'pscr': '\uD835\uDCC5',
- 'psi': '\u03C8',
- 'puncsp': '\u2008',
- 'qfr': '\uD835\uDD2E',
- 'qopf': '\uD835\uDD62',
- 'qprime': '\u2057',
- 'qscr': '\uD835\uDCC6',
- 'quatint': '\u2A16',
- 'quest': '\u003F',
- 'rAtail': '\u291C',
- 'rHar': '\u2964',
- 'race': '\u223D\u0331',
- 'racute': '\u0155',
- 'raemptyv': '\u29B3',
- 'rangd': '\u2992',
- 'range': '\u29A5',
- 'raquo': '\u00BB',
- 'rarrap': '\u2975',
- 'rarrbfs': '\u2920',
- 'rarrc': '\u2933',
- 'rarrfs': '\u291E',
- 'rarrpl': '\u2945',
- 'rarrsim': '\u2974',
- 'rarrtl': '\u21A3',
- 'rightarrowtail': '\u21A3',
- 'rarrw': '\u219D',
- 'rightsquigarrow': '\u219D',
- 'ratail': '\u291A',
- 'ratio': '\u2236',
- 'rbbrk': '\u2773',
- 'rbrace': '\u007D',
- 'rcub': '\u007D',
- 'rbrack': '\u005D',
- 'rsqb': '\u005D',
- 'rbrke': '\u298C',
- 'rbrksld': '\u298E',
- 'rbrkslu': '\u2990',
- 'rcaron': '\u0159',
- 'rcedil': '\u0157',
- 'rcy': '\u0440',
- 'rdca': '\u2937',
- 'rdldhar': '\u2969',
- 'rdsh': '\u21B3',
- 'rect': '\u25AD',
- 'rfisht': '\u297D',
- 'rfr': '\uD835\uDD2F',
- 'rharul': '\u296C',
- 'rho': '\u03C1',
- 'rhov': '\u03F1',
- 'varrho': '\u03F1',
- 'rightrightarrows': '\u21C9',
- 'rrarr': '\u21C9',
- 'rightthreetimes': '\u22CC',
- 'rthree': '\u22CC',
- 'ring': '\u02DA',
- 'rlm': '\u200F',
- 'rmoust': '\u23B1',
- 'rmoustache': '\u23B1',
- 'rnmid': '\u2AEE',
- 'roang': '\u27ED',
- 'roarr': '\u21FE',
- 'ropar': '\u2986',
- 'ropf': '\uD835\uDD63',
- 'roplus': '\u2A2E',
- 'rotimes': '\u2A35',
- 'rpar': '\u0029',
- 'rpargt': '\u2994',
- 'rppolint': '\u2A12',
- 'rsaquo': '\u203A',
- 'rscr': '\uD835\uDCC7',
- 'rtimes': '\u22CA',
- 'rtri': '\u25B9',
- 'triangleright': '\u25B9',
- 'rtriltri': '\u29CE',
- 'ruluhar': '\u2968',
- 'rx': '\u211E',
- 'sacute': '\u015B',
- 'scE': '\u2AB4',
- 'scap': '\u2AB8',
- 'succapprox': '\u2AB8',
- 'scaron': '\u0161',
- 'scedil': '\u015F',
- 'scirc': '\u015D',
- 'scnE': '\u2AB6',
- 'succneqq': '\u2AB6',
- 'scnap': '\u2ABA',
- 'succnapprox': '\u2ABA',
- 'scnsim': '\u22E9',
- 'succnsim': '\u22E9',
- 'scpolint': '\u2A13',
- 'scy': '\u0441',
- 'sdot': '\u22C5',
- 'sdote': '\u2A66',
- 'seArr': '\u21D8',
- 'sect': '\u00A7',
- 'semi': '\u003B',
- 'seswar': '\u2929',
- 'tosa': '\u2929',
- 'sext': '\u2736',
- 'sfr': '\uD835\uDD30',
- 'sharp': '\u266F',
- 'shchcy': '\u0449',
- 'shcy': '\u0448',
- 'shy': '\u00AD',
- 'sigma': '\u03C3',
- 'sigmaf': '\u03C2',
- 'sigmav': '\u03C2',
- 'varsigma': '\u03C2',
- 'simdot': '\u2A6A',
- 'simg': '\u2A9E',
- 'simgE': '\u2AA0',
- 'siml': '\u2A9D',
- 'simlE': '\u2A9F',
- 'simne': '\u2246',
- 'simplus': '\u2A24',
- 'simrarr': '\u2972',
- 'smashp': '\u2A33',
- 'smeparsl': '\u29E4',
- 'smile': '\u2323',
- 'ssmile': '\u2323',
- 'smt': '\u2AAA',
- 'smte': '\u2AAC',
- 'smtes': '\u2AAC\uFE00',
- 'softcy': '\u044C',
- 'sol': '\u002F',
- 'solb': '\u29C4',
- 'solbar': '\u233F',
- 'sopf': '\uD835\uDD64',
- 'spades': '\u2660',
- 'spadesuit': '\u2660',
- 'sqcaps': '\u2293\uFE00',
- 'sqcups': '\u2294\uFE00',
- 'sscr': '\uD835\uDCC8',
- 'star': '\u2606',
- 'sub': '\u2282',
- 'subset': '\u2282',
- 'subE': '\u2AC5',
- 'subseteqq': '\u2AC5',
- 'subdot': '\u2ABD',
- 'subedot': '\u2AC3',
- 'submult': '\u2AC1',
- 'subnE': '\u2ACB',
- 'subsetneqq': '\u2ACB',
- 'subne': '\u228A',
- 'subsetneq': '\u228A',
- 'subplus': '\u2ABF',
- 'subrarr': '\u2979',
- 'subsim': '\u2AC7',
- 'subsub': '\u2AD5',
- 'subsup': '\u2AD3',
- 'sung': '\u266A',
- 'sup1': '\u00B9',
- 'sup2': '\u00B2',
- 'sup3': '\u00B3',
- 'supE': '\u2AC6',
- 'supseteqq': '\u2AC6',
- 'supdot': '\u2ABE',
- 'supdsub': '\u2AD8',
- 'supedot': '\u2AC4',
- 'suphsol': '\u27C9',
- 'suphsub': '\u2AD7',
- 'suplarr': '\u297B',
- 'supmult': '\u2AC2',
- 'supnE': '\u2ACC',
- 'supsetneqq': '\u2ACC',
- 'supne': '\u228B',
- 'supsetneq': '\u228B',
- 'supplus': '\u2AC0',
- 'supsim': '\u2AC8',
- 'supsub': '\u2AD4',
- 'supsup': '\u2AD6',
- 'swArr': '\u21D9',
- 'swnwar': '\u292A',
- 'szlig': '\u00DF',
- 'target': '\u2316',
- 'tau': '\u03C4',
- 'tcaron': '\u0165',
- 'tcedil': '\u0163',
- 'tcy': '\u0442',
- 'telrec': '\u2315',
- 'tfr': '\uD835\uDD31',
- 'theta': '\u03B8',
- 'thetasym': '\u03D1',
- 'thetav': '\u03D1',
- 'vartheta': '\u03D1',
- 'thorn': '\u00FE',
- 'times': '\u00D7',
- 'timesbar': '\u2A31',
- 'timesd': '\u2A30',
- 'topbot': '\u2336',
- 'topcir': '\u2AF1',
- 'topf': '\uD835\uDD65',
- 'topfork': '\u2ADA',
- 'tprime': '\u2034',
- 'triangle': '\u25B5',
- 'utri': '\u25B5',
- 'triangleq': '\u225C',
- 'trie': '\u225C',
- 'tridot': '\u25EC',
- 'triminus': '\u2A3A',
- 'triplus': '\u2A39',
- 'trisb': '\u29CD',
- 'tritime': '\u2A3B',
- 'trpezium': '\u23E2',
- 'tscr': '\uD835\uDCC9',
- 'tscy': '\u0446',
- 'tshcy': '\u045B',
- 'tstrok': '\u0167',
- 'uHar': '\u2963',
- 'uacute': '\u00FA',
- 'ubrcy': '\u045E',
- 'ubreve': '\u016D',
- 'ucirc': '\u00FB',
- 'ucy': '\u0443',
- 'udblac': '\u0171',
- 'ufisht': '\u297E',
- 'ufr': '\uD835\uDD32',
- 'ugrave': '\u00F9',
- 'uhblk': '\u2580',
- 'ulcorn': '\u231C',
- 'ulcorner': '\u231C',
- 'ulcrop': '\u230F',
- 'ultri': '\u25F8',
- 'umacr': '\u016B',
- 'uogon': '\u0173',
- 'uopf': '\uD835\uDD66',
- 'upsi': '\u03C5',
- 'upsilon': '\u03C5',
- 'upuparrows': '\u21C8',
- 'uuarr': '\u21C8',
- 'urcorn': '\u231D',
- 'urcorner': '\u231D',
- 'urcrop': '\u230E',
- 'uring': '\u016F',
- 'urtri': '\u25F9',
- 'uscr': '\uD835\uDCCA',
- 'utdot': '\u22F0',
- 'utilde': '\u0169',
- 'uuml': '\u00FC',
- 'uwangle': '\u29A7',
- 'vBar': '\u2AE8',
- 'vBarv': '\u2AE9',
- 'vangrt': '\u299C',
- 'varsubsetneq': '\u228A\uFE00',
- 'vsubne': '\u228A\uFE00',
- 'varsubsetneqq': '\u2ACB\uFE00',
- 'vsubnE': '\u2ACB\uFE00',
- 'varsupsetneq': '\u228B\uFE00',
- 'vsupne': '\u228B\uFE00',
- 'varsupsetneqq': '\u2ACC\uFE00',
- 'vsupnE': '\u2ACC\uFE00',
- 'vcy': '\u0432',
- 'veebar': '\u22BB',
- 'veeeq': '\u225A',
- 'vellip': '\u22EE',
- 'vfr': '\uD835\uDD33',
- 'vopf': '\uD835\uDD67',
- 'vscr': '\uD835\uDCCB',
- 'vzigzag': '\u299A',
- 'wcirc': '\u0175',
- 'wedbar': '\u2A5F',
- 'wedgeq': '\u2259',
- 'weierp': '\u2118',
- 'wp': '\u2118',
- 'wfr': '\uD835\uDD34',
- 'wopf': '\uD835\uDD68',
- 'wscr': '\uD835\uDCCC',
- 'xfr': '\uD835\uDD35',
- 'xi': '\u03BE',
- 'xnis': '\u22FB',
- 'xopf': '\uD835\uDD69',
- 'xscr': '\uD835\uDCCD',
- 'yacute': '\u00FD',
- 'yacy': '\u044F',
- 'ycirc': '\u0177',
- 'ycy': '\u044B',
- 'yen': '\u00A5',
- 'yfr': '\uD835\uDD36',
- 'yicy': '\u0457',
- 'yopf': '\uD835\uDD6A',
- 'yscr': '\uD835\uDCCE',
- 'yucy': '\u044E',
- 'yuml': '\u00FF',
- 'zacute': '\u017A',
- 'zcaron': '\u017E',
- 'zcy': '\u0437',
- 'zdot': '\u017C',
- 'zeta': '\u03B6',
- 'zfr': '\uD835\uDD37',
- 'zhcy': '\u0436',
- 'zigrarr': '\u21DD',
- 'zopf': '\uD835\uDD6B',
- 'zscr': '\uD835\uDCCF',
- 'zwj': '\u200D',
- 'zwnj': '\u200C',
- };
- // The &ngsp; pseudo-entity is denoting a space.
- // 0xE500 is a PUA (Private Use Areas) unicode character
- // This is inspired by the Angular Dart implementation.
- const NGSP_UNICODE = '\uE500';
- NAMED_ENTITIES['ngsp'] = NGSP_UNICODE;
- class TokenError extends ParseError {
- tokenType;
- constructor(errorMsg, tokenType, span) {
- super(span, errorMsg);
- this.tokenType = tokenType;
- }
- }
- class TokenizeResult {
- tokens;
- errors;
- nonNormalizedIcuExpressions;
- constructor(tokens, errors, nonNormalizedIcuExpressions) {
- this.tokens = tokens;
- this.errors = errors;
- this.nonNormalizedIcuExpressions = nonNormalizedIcuExpressions;
- }
- }
- function tokenize(source, url, getTagDefinition, options = {}) {
- const tokenizer = new _Tokenizer(new ParseSourceFile(source, url), getTagDefinition, options);
- tokenizer.tokenize();
- return new TokenizeResult(mergeTextTokens(tokenizer.tokens), tokenizer.errors, tokenizer.nonNormalizedIcuExpressions);
- }
- const _CR_OR_CRLF_REGEXP = /\r\n?/g;
- function _unexpectedCharacterErrorMsg(charCode) {
- const char = charCode === $EOF ? 'EOF' : String.fromCharCode(charCode);
- return `Unexpected character "${char}"`;
- }
- function _unknownEntityErrorMsg(entitySrc) {
- return `Unknown entity "${entitySrc}" - use the "&#<decimal>;" or "&#x<hex>;" syntax`;
- }
- function _unparsableEntityErrorMsg(type, entityStr) {
- return `Unable to parse entity "${entityStr}" - ${type} character reference entities must end with ";"`;
- }
- var CharacterReferenceType;
- (function (CharacterReferenceType) {
- CharacterReferenceType["HEX"] = "hexadecimal";
- CharacterReferenceType["DEC"] = "decimal";
- })(CharacterReferenceType || (CharacterReferenceType = {}));
- class _ControlFlowError {
- error;
- constructor(error) {
- this.error = error;
- }
- }
- // See https://www.w3.org/TR/html51/syntax.html#writing-html-documents
- class _Tokenizer {
- _getTagDefinition;
- _cursor;
- _tokenizeIcu;
- _interpolationConfig;
- _leadingTriviaCodePoints;
- _currentTokenStart = null;
- _currentTokenType = null;
- _expansionCaseStack = [];
- _inInterpolation = false;
- _preserveLineEndings;
- _i18nNormalizeLineEndingsInICUs;
- _tokenizeBlocks;
- _tokenizeLet;
- tokens = [];
- errors = [];
- nonNormalizedIcuExpressions = [];
- /**
- * @param _file The html source file being tokenized.
- * @param _getTagDefinition A function that will retrieve a tag definition for a given tag name.
- * @param options Configuration of the tokenization.
- */
- constructor(_file, _getTagDefinition, options) {
- this._getTagDefinition = _getTagDefinition;
- this._tokenizeIcu = options.tokenizeExpansionForms || false;
- this._interpolationConfig = options.interpolationConfig || DEFAULT_INTERPOLATION_CONFIG;
- this._leadingTriviaCodePoints =
- options.leadingTriviaChars && options.leadingTriviaChars.map((c) => c.codePointAt(0) || 0);
- const range = options.range || {
- endPos: _file.content.length,
- startPos: 0,
- startLine: 0,
- startCol: 0,
- };
- this._cursor = options.escapedString
- ? new EscapedCharacterCursor(_file, range)
- : new PlainCharacterCursor(_file, range);
- this._preserveLineEndings = options.preserveLineEndings || false;
- this._i18nNormalizeLineEndingsInICUs = options.i18nNormalizeLineEndingsInICUs || false;
- this._tokenizeBlocks = options.tokenizeBlocks ?? true;
- this._tokenizeLet = options.tokenizeLet ?? true;
- try {
- this._cursor.init();
- }
- catch (e) {
- this.handleError(e);
- }
- }
- _processCarriageReturns(content) {
- if (this._preserveLineEndings) {
- return content;
- }
- // https://www.w3.org/TR/html51/syntax.html#preprocessing-the-input-stream
- // In order to keep the original position in the source, we can not
- // pre-process it.
- // Instead CRs are processed right before instantiating the tokens.
- return content.replace(_CR_OR_CRLF_REGEXP, '\n');
- }
- tokenize() {
- while (this._cursor.peek() !== $EOF) {
- const start = this._cursor.clone();
- try {
- if (this._attemptCharCode($LT)) {
- if (this._attemptCharCode($BANG)) {
- if (this._attemptCharCode($LBRACKET)) {
- this._consumeCdata(start);
- }
- else if (this._attemptCharCode($MINUS)) {
- this._consumeComment(start);
- }
- else {
- this._consumeDocType(start);
- }
- }
- else if (this._attemptCharCode($SLASH)) {
- this._consumeTagClose(start);
- }
- else {
- this._consumeTagOpen(start);
- }
- }
- else if (this._tokenizeLet &&
- // Use `peek` instead of `attempCharCode` since we
- // don't want to advance in case it's not `@let`.
- this._cursor.peek() === $AT &&
- !this._inInterpolation &&
- this._attemptStr('@let')) {
- this._consumeLetDeclaration(start);
- }
- else if (this._tokenizeBlocks && this._attemptCharCode($AT)) {
- this._consumeBlockStart(start);
- }
- else if (this._tokenizeBlocks &&
- !this._inInterpolation &&
- !this._isInExpansionCase() &&
- !this._isInExpansionForm() &&
- this._attemptCharCode($RBRACE)) {
- this._consumeBlockEnd(start);
- }
- else if (!(this._tokenizeIcu && this._tokenizeExpansionForm())) {
- // In (possibly interpolated) text the end of the text is given by `isTextEnd()`, while
- // the premature end of an interpolation is given by the start of a new HTML element.
- this._consumeWithInterpolation(5 /* TokenType.TEXT */, 8 /* TokenType.INTERPOLATION */, () => this._isTextEnd(), () => this._isTagStart());
- }
- }
- catch (e) {
- this.handleError(e);
- }
- }
- this._beginToken(33 /* TokenType.EOF */);
- this._endToken([]);
- }
- _getBlockName() {
- // This allows us to capture up something like `@else if`, but not `@ if`.
- let spacesInNameAllowed = false;
- const nameCursor = this._cursor.clone();
- this._attemptCharCodeUntilFn((code) => {
- if (isWhitespace(code)) {
- return !spacesInNameAllowed;
- }
- if (isBlockNameChar(code)) {
- spacesInNameAllowed = true;
- return false;
- }
- return true;
- });
- return this._cursor.getChars(nameCursor).trim();
- }
- _consumeBlockStart(start) {
- this._beginToken(24 /* TokenType.BLOCK_OPEN_START */, start);
- const startToken = this._endToken([this._getBlockName()]);
- if (this._cursor.peek() === $LPAREN) {
- // Advance past the opening paren.
- this._cursor.advance();
- // Capture the parameters.
- this._consumeBlockParameters();
- // Allow spaces before the closing paren.
- this._attemptCharCodeUntilFn(isNotWhitespace);
- if (this._attemptCharCode($RPAREN)) {
- // Allow spaces after the paren.
- this._attemptCharCodeUntilFn(isNotWhitespace);
- }
- else {
- startToken.type = 28 /* TokenType.INCOMPLETE_BLOCK_OPEN */;
- return;
- }
- }
- if (this._attemptCharCode($LBRACE)) {
- this._beginToken(25 /* TokenType.BLOCK_OPEN_END */);
- this._endToken([]);
- }
- else {
- startToken.type = 28 /* TokenType.INCOMPLETE_BLOCK_OPEN */;
- }
- }
- _consumeBlockEnd(start) {
- this._beginToken(26 /* TokenType.BLOCK_CLOSE */, start);
- this._endToken([]);
- }
- _consumeBlockParameters() {
- // Trim the whitespace until the first parameter.
- this._attemptCharCodeUntilFn(isBlockParameterChar);
- while (this._cursor.peek() !== $RPAREN && this._cursor.peek() !== $EOF) {
- this._beginToken(27 /* TokenType.BLOCK_PARAMETER */);
- const start = this._cursor.clone();
- let inQuote = null;
- let openParens = 0;
- // Consume the parameter until the next semicolon or brace.
- // Note that we skip over semicolons/braces inside of strings.
- while ((this._cursor.peek() !== $SEMICOLON && this._cursor.peek() !== $EOF) ||
- inQuote !== null) {
- const char = this._cursor.peek();
- // Skip to the next character if it was escaped.
- if (char === $BACKSLASH) {
- this._cursor.advance();
- }
- else if (char === inQuote) {
- inQuote = null;
- }
- else if (inQuote === null && isQuote(char)) {
- inQuote = char;
- }
- else if (char === $LPAREN && inQuote === null) {
- openParens++;
- }
- else if (char === $RPAREN && inQuote === null) {
- if (openParens === 0) {
- break;
- }
- else if (openParens > 0) {
- openParens--;
- }
- }
- this._cursor.advance();
- }
- this._endToken([this._cursor.getChars(start)]);
- // Skip to the next parameter.
- this._attemptCharCodeUntilFn(isBlockParameterChar);
- }
- }
- _consumeLetDeclaration(start) {
- this._beginToken(29 /* TokenType.LET_START */, start);
- // Require at least one white space after the `@let`.
- if (isWhitespace(this._cursor.peek())) {
- this._attemptCharCodeUntilFn(isNotWhitespace);
- }
- else {
- const token = this._endToken([this._cursor.getChars(start)]);
- token.type = 32 /* TokenType.INCOMPLETE_LET */;
- return;
- }
- const startToken = this._endToken([this._getLetDeclarationName()]);
- // Skip over white space before the equals character.
- this._attemptCharCodeUntilFn(isNotWhitespace);
- // Expect an equals sign.
- if (!this._attemptCharCode($EQ)) {
- startToken.type = 32 /* TokenType.INCOMPLETE_LET */;
- return;
- }
- // Skip spaces after the equals.
- this._attemptCharCodeUntilFn((code) => isNotWhitespace(code) && !isNewLine(code));
- this._consumeLetDeclarationValue();
- // Terminate the `@let` with a semicolon.
- const endChar = this._cursor.peek();
- if (endChar === $SEMICOLON) {
- this._beginToken(31 /* TokenType.LET_END */);
- this._endToken([]);
- this._cursor.advance();
- }
- else {
- startToken.type = 32 /* TokenType.INCOMPLETE_LET */;
- startToken.sourceSpan = this._cursor.getSpan(start);
- }
- }
- _getLetDeclarationName() {
- const nameCursor = this._cursor.clone();
- let allowDigit = false;
- this._attemptCharCodeUntilFn((code) => {
- if (isAsciiLetter(code) ||
- code === $$ ||
- code === $_ ||
- // `@let` names can't start with a digit, but digits are valid anywhere else in the name.
- (allowDigit && isDigit(code))) {
- allowDigit = true;
- return false;
- }
- return true;
- });
- return this._cursor.getChars(nameCursor).trim();
- }
- _consumeLetDeclarationValue() {
- const start = this._cursor.clone();
- this._beginToken(30 /* TokenType.LET_VALUE */, start);
- while (this._cursor.peek() !== $EOF) {
- const char = this._cursor.peek();
- // `@let` declarations terminate with a semicolon.
- if (char === $SEMICOLON) {
- break;
- }
- // If we hit a quote, skip over its content since we don't care what's inside.
- if (isQuote(char)) {
- this._cursor.advance();
- this._attemptCharCodeUntilFn((inner) => {
- if (inner === $BACKSLASH) {
- this._cursor.advance();
- return false;
- }
- return inner === char;
- });
- }
- this._cursor.advance();
- }
- this._endToken([this._cursor.getChars(start)]);
- }
- /**
- * @returns whether an ICU token has been created
- * @internal
- */
- _tokenizeExpansionForm() {
- if (this.isExpansionFormStart()) {
- this._consumeExpansionFormStart();
- return true;
- }
- if (isExpansionCaseStart(this._cursor.peek()) && this._isInExpansionForm()) {
- this._consumeExpansionCaseStart();
- return true;
- }
- if (this._cursor.peek() === $RBRACE) {
- if (this._isInExpansionCase()) {
- this._consumeExpansionCaseEnd();
- return true;
- }
- if (this._isInExpansionForm()) {
- this._consumeExpansionFormEnd();
- return true;
- }
- }
- return false;
- }
- _beginToken(type, start = this._cursor.clone()) {
- this._currentTokenStart = start;
- this._currentTokenType = type;
- }
- _endToken(parts, end) {
- if (this._currentTokenStart === null) {
- throw new TokenError('Programming error - attempted to end a token when there was no start to the token', this._currentTokenType, this._cursor.getSpan(end));
- }
- if (this._currentTokenType === null) {
- throw new TokenError('Programming error - attempted to end a token which has no token type', null, this._cursor.getSpan(this._currentTokenStart));
- }
- const token = {
- type: this._currentTokenType,
- parts,
- sourceSpan: (end ?? this._cursor).getSpan(this._currentTokenStart, this._leadingTriviaCodePoints),
- };
- this.tokens.push(token);
- this._currentTokenStart = null;
- this._currentTokenType = null;
- return token;
- }
- _createError(msg, span) {
- if (this._isInExpansionForm()) {
- msg += ` (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.)`;
- }
- const error = new TokenError(msg, this._currentTokenType, span);
- this._currentTokenStart = null;
- this._currentTokenType = null;
- return new _ControlFlowError(error);
- }
- handleError(e) {
- if (e instanceof CursorError) {
- e = this._createError(e.msg, this._cursor.getSpan(e.cursor));
- }
- if (e instanceof _ControlFlowError) {
- this.errors.push(e.error);
- }
- else {
- throw e;
- }
- }
- _attemptCharCode(charCode) {
- if (this._cursor.peek() === charCode) {
- this._cursor.advance();
- return true;
- }
- return false;
- }
- _attemptCharCodeCaseInsensitive(charCode) {
- if (compareCharCodeCaseInsensitive(this._cursor.peek(), charCode)) {
- this._cursor.advance();
- return true;
- }
- return false;
- }
- _requireCharCode(charCode) {
- const location = this._cursor.clone();
- if (!this._attemptCharCode(charCode)) {
- throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(location));
- }
- }
- _attemptStr(chars) {
- const len = chars.length;
- if (this._cursor.charsLeft() < len) {
- return false;
- }
- const initialPosition = this._cursor.clone();
- for (let i = 0; i < len; i++) {
- if (!this._attemptCharCode(chars.charCodeAt(i))) {
- // If attempting to parse the string fails, we want to reset the parser
- // to where it was before the attempt
- this._cursor = initialPosition;
- return false;
- }
- }
- return true;
- }
- _attemptStrCaseInsensitive(chars) {
- for (let i = 0; i < chars.length; i++) {
- if (!this._attemptCharCodeCaseInsensitive(chars.charCodeAt(i))) {
- return false;
- }
- }
- return true;
- }
- _requireStr(chars) {
- const location = this._cursor.clone();
- if (!this._attemptStr(chars)) {
- throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(location));
- }
- }
- _attemptCharCodeUntilFn(predicate) {
- while (!predicate(this._cursor.peek())) {
- this._cursor.advance();
- }
- }
- _requireCharCodeUntilFn(predicate, len) {
- const start = this._cursor.clone();
- this._attemptCharCodeUntilFn(predicate);
- if (this._cursor.diff(start) < len) {
- throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
- }
- }
- _attemptUntilChar(char) {
- while (this._cursor.peek() !== char) {
- this._cursor.advance();
- }
- }
- _readChar() {
- // Don't rely upon reading directly from `_input` as the actual char value
- // may have been generated from an escape sequence.
- const char = String.fromCodePoint(this._cursor.peek());
- this._cursor.advance();
- return char;
- }
- _consumeEntity(textTokenType) {
- this._beginToken(9 /* TokenType.ENCODED_ENTITY */);
- const start = this._cursor.clone();
- this._cursor.advance();
- if (this._attemptCharCode($HASH)) {
- const isHex = this._attemptCharCode($x) || this._attemptCharCode($X);
- const codeStart = this._cursor.clone();
- this._attemptCharCodeUntilFn(isDigitEntityEnd);
- if (this._cursor.peek() != $SEMICOLON) {
- // Advance cursor to include the peeked character in the string provided to the error
- // message.
- this._cursor.advance();
- const entityType = isHex ? CharacterReferenceType.HEX : CharacterReferenceType.DEC;
- throw this._createError(_unparsableEntityErrorMsg(entityType, this._cursor.getChars(start)), this._cursor.getSpan());
- }
- const strNum = this._cursor.getChars(codeStart);
- this._cursor.advance();
- try {
- const charCode = parseInt(strNum, isHex ? 16 : 10);
- this._endToken([String.fromCharCode(charCode), this._cursor.getChars(start)]);
- }
- catch {
- throw this._createError(_unknownEntityErrorMsg(this._cursor.getChars(start)), this._cursor.getSpan());
- }
- }
- else {
- const nameStart = this._cursor.clone();
- this._attemptCharCodeUntilFn(isNamedEntityEnd);
- if (this._cursor.peek() != $SEMICOLON) {
- // No semicolon was found so abort the encoded entity token that was in progress, and treat
- // this as a text token
- this._beginToken(textTokenType, start);
- this._cursor = nameStart;
- this._endToken(['&']);
- }
- else {
- const name = this._cursor.getChars(nameStart);
- this._cursor.advance();
- const char = NAMED_ENTITIES.hasOwnProperty(name) && NAMED_ENTITIES[name];
- if (!char) {
- throw this._createError(_unknownEntityErrorMsg(name), this._cursor.getSpan(start));
- }
- this._endToken([char, `&${name};`]);
- }
- }
- }
- _consumeRawText(consumeEntities, endMarkerPredicate) {
- this._beginToken(consumeEntities ? 6 /* TokenType.ESCAPABLE_RAW_TEXT */ : 7 /* TokenType.RAW_TEXT */);
- const parts = [];
- while (true) {
- const tagCloseStart = this._cursor.clone();
- const foundEndMarker = endMarkerPredicate();
- this._cursor = tagCloseStart;
- if (foundEndMarker) {
- break;
- }
- if (consumeEntities && this._cursor.peek() === $AMPERSAND) {
- this._endToken([this._processCarriageReturns(parts.join(''))]);
- parts.length = 0;
- this._consumeEntity(6 /* TokenType.ESCAPABLE_RAW_TEXT */);
- this._beginToken(6 /* TokenType.ESCAPABLE_RAW_TEXT */);
- }
- else {
- parts.push(this._readChar());
- }
- }
- this._endToken([this._processCarriageReturns(parts.join(''))]);
- }
- _consumeComment(start) {
- this._beginToken(10 /* TokenType.COMMENT_START */, start);
- this._requireCharCode($MINUS);
- this._endToken([]);
- this._consumeRawText(false, () => this._attemptStr('-->'));
- this._beginToken(11 /* TokenType.COMMENT_END */);
- this._requireStr('-->');
- this._endToken([]);
- }
- _consumeCdata(start) {
- this._beginToken(12 /* TokenType.CDATA_START */, start);
- this._requireStr('CDATA[');
- this._endToken([]);
- this._consumeRawText(false, () => this._attemptStr(']]>'));
- this._beginToken(13 /* TokenType.CDATA_END */);
- this._requireStr(']]>');
- this._endToken([]);
- }
- _consumeDocType(start) {
- this._beginToken(18 /* TokenType.DOC_TYPE */, start);
- const contentStart = this._cursor.clone();
- this._attemptUntilChar($GT);
- const content = this._cursor.getChars(contentStart);
- this._cursor.advance();
- this._endToken([content]);
- }
- _consumePrefixAndName() {
- const nameOrPrefixStart = this._cursor.clone();
- let prefix = '';
- while (this._cursor.peek() !== $COLON && !isPrefixEnd(this._cursor.peek())) {
- this._cursor.advance();
- }
- let nameStart;
- if (this._cursor.peek() === $COLON) {
- prefix = this._cursor.getChars(nameOrPrefixStart);
- this._cursor.advance();
- nameStart = this._cursor.clone();
- }
- else {
- nameStart = nameOrPrefixStart;
- }
- this._requireCharCodeUntilFn(isNameEnd, prefix === '' ? 0 : 1);
- const name = this._cursor.getChars(nameStart);
- return [prefix, name];
- }
- _consumeTagOpen(start) {
- let tagName;
- let prefix;
- let openTagToken;
- try {
- if (!isAsciiLetter(this._cursor.peek())) {
- throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
- }
- openTagToken = this._consumeTagOpenStart(start);
- prefix = openTagToken.parts[0];
- tagName = openTagToken.parts[1];
- this._attemptCharCodeUntilFn(isNotWhitespace);
- while (this._cursor.peek() !== $SLASH &&
- this._cursor.peek() !== $GT &&
- this._cursor.peek() !== $LT &&
- this._cursor.peek() !== $EOF) {
- this._consumeAttributeName();
- this._attemptCharCodeUntilFn(isNotWhitespace);
- if (this._attemptCharCode($EQ)) {
- this._attemptCharCodeUntilFn(isNotWhitespace);
- this._consumeAttributeValue();
- }
- this._attemptCharCodeUntilFn(isNotWhitespace);
- }
- this._consumeTagOpenEnd();
- }
- catch (e) {
- if (e instanceof _ControlFlowError) {
- if (openTagToken) {
- // We errored before we could close the opening tag, so it is incomplete.
- openTagToken.type = 4 /* TokenType.INCOMPLETE_TAG_OPEN */;
- }
- else {
- // When the start tag is invalid, assume we want a "<" as text.
- // Back to back text tokens are merged at the end.
- this._beginToken(5 /* TokenType.TEXT */, start);
- this._endToken(['<']);
- }
- return;
- }
- throw e;
- }
- const contentTokenType = this._getTagDefinition(tagName).getContentType(prefix);
- if (contentTokenType === exports.TagContentType.RAW_TEXT) {
- this._consumeRawTextWithTagClose(prefix, tagName, false);
- }
- else if (contentTokenType === exports.TagContentType.ESCAPABLE_RAW_TEXT) {
- this._consumeRawTextWithTagClose(prefix, tagName, true);
- }
- }
- _consumeRawTextWithTagClose(prefix, tagName, consumeEntities) {
- this._consumeRawText(consumeEntities, () => {
- if (!this._attemptCharCode($LT))
- return false;
- if (!this._attemptCharCode($SLASH))
- return false;
- this._attemptCharCodeUntilFn(isNotWhitespace);
- if (!this._attemptStrCaseInsensitive(tagName))
- return false;
- this._attemptCharCodeUntilFn(isNotWhitespace);
- return this._attemptCharCode($GT);
- });
- this._beginToken(3 /* TokenType.TAG_CLOSE */);
- this._requireCharCodeUntilFn((code) => code === $GT, 3);
- this._cursor.advance(); // Consume the `>`
- this._endToken([prefix, tagName]);
- }
- _consumeTagOpenStart(start) {
- this._beginToken(0 /* TokenType.TAG_OPEN_START */, start);
- const parts = this._consumePrefixAndName();
- return this._endToken(parts);
- }
- _consumeAttributeName() {
- const attrNameStart = this._cursor.peek();
- if (attrNameStart === $SQ || attrNameStart === $DQ) {
- throw this._createError(_unexpectedCharacterErrorMsg(attrNameStart), this._cursor.getSpan());
- }
- this._beginToken(14 /* TokenType.ATTR_NAME */);
- const prefixAndName = this._consumePrefixAndName();
- this._endToken(prefixAndName);
- }
- _consumeAttributeValue() {
- if (this._cursor.peek() === $SQ || this._cursor.peek() === $DQ) {
- const quoteChar = this._cursor.peek();
- this._consumeQuote(quoteChar);
- // In an attribute then end of the attribute value and the premature end to an interpolation
- // are both triggered by the `quoteChar`.
- const endPredicate = () => this._cursor.peek() === quoteChar;
- this._consumeWithInterpolation(16 /* TokenType.ATTR_VALUE_TEXT */, 17 /* TokenType.ATTR_VALUE_INTERPOLATION */, endPredicate, endPredicate);
- this._consumeQuote(quoteChar);
- }
- else {
- const endPredicate = () => isNameEnd(this._cursor.peek());
- this._consumeWithInterpolation(16 /* TokenType.ATTR_VALUE_TEXT */, 17 /* TokenType.ATTR_VALUE_INTERPOLATION */, endPredicate, endPredicate);
- }
- }
- _consumeQuote(quoteChar) {
- this._beginToken(15 /* TokenType.ATTR_QUOTE */);
- this._requireCharCode(quoteChar);
- this._endToken([String.fromCodePoint(quoteChar)]);
- }
- _consumeTagOpenEnd() {
- const tokenType = this._attemptCharCode($SLASH)
- ? 2 /* TokenType.TAG_OPEN_END_VOID */
- : 1 /* TokenType.TAG_OPEN_END */;
- this._beginToken(tokenType);
- this._requireCharCode($GT);
- this._endToken([]);
- }
- _consumeTagClose(start) {
- this._beginToken(3 /* TokenType.TAG_CLOSE */, start);
- this._attemptCharCodeUntilFn(isNotWhitespace);
- const prefixAndName = this._consumePrefixAndName();
- this._attemptCharCodeUntilFn(isNotWhitespace);
- this._requireCharCode($GT);
- this._endToken(prefixAndName);
- }
- _consumeExpansionFormStart() {
- this._beginToken(19 /* TokenType.EXPANSION_FORM_START */);
- this._requireCharCode($LBRACE);
- this._endToken([]);
- this._expansionCaseStack.push(19 /* TokenType.EXPANSION_FORM_START */);
- this._beginToken(7 /* TokenType.RAW_TEXT */);
- const condition = this._readUntil($COMMA);
- const normalizedCondition = this._processCarriageReturns(condition);
- if (this._i18nNormalizeLineEndingsInICUs) {
- // We explicitly want to normalize line endings for this text.
- this._endToken([normalizedCondition]);
- }
- else {
- // We are not normalizing line endings.
- const conditionToken = this._endToken([condition]);
- if (normalizedCondition !== condition) {
- this.nonNormalizedIcuExpressions.push(conditionToken);
- }
- }
- this._requireCharCode($COMMA);
- this._attemptCharCodeUntilFn(isNotWhitespace);
- this._beginToken(7 /* TokenType.RAW_TEXT */);
- const type = this._readUntil($COMMA);
- this._endToken([type]);
- this._requireCharCode($COMMA);
- this._attemptCharCodeUntilFn(isNotWhitespace);
- }
- _consumeExpansionCaseStart() {
- this._beginToken(20 /* TokenType.EXPANSION_CASE_VALUE */);
- const value = this._readUntil($LBRACE).trim();
- this._endToken([value]);
- this._attemptCharCodeUntilFn(isNotWhitespace);
- this._beginToken(21 /* TokenType.EXPANSION_CASE_EXP_START */);
- this._requireCharCode($LBRACE);
- this._endToken([]);
- this._attemptCharCodeUntilFn(isNotWhitespace);
- this._expansionCaseStack.push(21 /* TokenType.EXPANSION_CASE_EXP_START */);
- }
- _consumeExpansionCaseEnd() {
- this._beginToken(22 /* TokenType.EXPANSION_CASE_EXP_END */);
- this._requireCharCode($RBRACE);
- this._endToken([]);
- this._attemptCharCodeUntilFn(isNotWhitespace);
- this._expansionCaseStack.pop();
- }
- _consumeExpansionFormEnd() {
- this._beginToken(23 /* TokenType.EXPANSION_FORM_END */);
- this._requireCharCode($RBRACE);
- this._endToken([]);
- this._expansionCaseStack.pop();
- }
- /**
- * Consume a string that may contain interpolation expressions.
- *
- * The first token consumed will be of `tokenType` and then there will be alternating
- * `interpolationTokenType` and `tokenType` tokens until the `endPredicate()` returns true.
- *
- * If an interpolation token ends prematurely it will have no end marker in its `parts` array.
- *
- * @param textTokenType the kind of tokens to interleave around interpolation tokens.
- * @param interpolationTokenType the kind of tokens that contain interpolation.
- * @param endPredicate a function that should return true when we should stop consuming.
- * @param endInterpolation a function that should return true if there is a premature end to an
- * interpolation expression - i.e. before we get to the normal interpolation closing marker.
- */
- _consumeWithInterpolation(textTokenType, interpolationTokenType, endPredicate, endInterpolation) {
- this._beginToken(textTokenType);
- const parts = [];
- while (!endPredicate()) {
- const current = this._cursor.clone();
- if (this._interpolationConfig && this._attemptStr(this._interpolationConfig.start)) {
- this._endToken([this._processCarriageReturns(parts.join(''))], current);
- parts.length = 0;
- this._consumeInterpolation(interpolationTokenType, current, endInterpolation);
- this._beginToken(textTokenType);
- }
- else if (this._cursor.peek() === $AMPERSAND) {
- this._endToken([this._processCarriageReturns(parts.join(''))]);
- parts.length = 0;
- this._consumeEntity(textTokenType);
- this._beginToken(textTokenType);
- }
- else {
- parts.push(this._readChar());
- }
- }
- // It is possible that an interpolation was started but not ended inside this text token.
- // Make sure that we reset the state of the lexer correctly.
- this._inInterpolation = false;
- this._endToken([this._processCarriageReturns(parts.join(''))]);
- }
- /**
- * Consume a block of text that has been interpreted as an Angular interpolation.
- *
- * @param interpolationTokenType the type of the interpolation token to generate.
- * @param interpolationStart a cursor that points to the start of this interpolation.
- * @param prematureEndPredicate a function that should return true if the next characters indicate
- * an end to the interpolation before its normal closing marker.
- */
- _consumeInterpolation(interpolationTokenType, interpolationStart, prematureEndPredicate) {
- const parts = [];
- this._beginToken(interpolationTokenType, interpolationStart);
- parts.push(this._interpolationConfig.start);
- // Find the end of the interpolation, ignoring content inside quotes.
- const expressionStart = this._cursor.clone();
- let inQuote = null;
- let inComment = false;
- while (this._cursor.peek() !== $EOF &&
- (prematureEndPredicate === null || !prematureEndPredicate())) {
- const current = this._cursor.clone();
- if (this._isTagStart()) {
- // We are starting what looks like an HTML element in the middle of this interpolation.
- // Reset the cursor to before the `<` character and end the interpolation token.
- // (This is actually wrong but here for backward compatibility).
- this._cursor = current;
- parts.push(this._getProcessedChars(expressionStart, current));
- this._endToken(parts);
- return;
- }
- if (inQuote === null) {
- if (this._attemptStr(this._interpolationConfig.end)) {
- // We are not in a string, and we hit the end interpolation marker
- parts.push(this._getProcessedChars(expressionStart, current));
- parts.push(this._interpolationConfig.end);
- this._endToken(parts);
- return;
- }
- else if (this._attemptStr('//')) {
- // Once we are in a comment we ignore any quotes
- inComment = true;
- }
- }
- const char = this._cursor.peek();
- this._cursor.advance();
- if (char === $BACKSLASH) {
- // Skip the next character because it was escaped.
- this._cursor.advance();
- }
- else if (char === inQuote) {
- // Exiting the current quoted string
- inQuote = null;
- }
- else if (!inComment && inQuote === null && isQuote(char)) {
- // Entering a new quoted string
- inQuote = char;
- }
- }
- // We hit EOF without finding a closing interpolation marker
- parts.push(this._getProcessedChars(expressionStart, this._cursor));
- this._endToken(parts);
- }
- _getProcessedChars(start, end) {
- return this._processCarriageReturns(end.getChars(start));
- }
- _isTextEnd() {
- if (this._isTagStart() || this._cursor.peek() === $EOF) {
- return true;
- }
- if (this._tokenizeIcu && !this._inInterpolation) {
- if (this.isExpansionFormStart()) {
- // start of an expansion form
- return true;
- }
- if (this._cursor.peek() === $RBRACE && this._isInExpansionCase()) {
- // end of and expansion case
- return true;
- }
- }
- if (this._tokenizeBlocks &&
- !this._inInterpolation &&
- !this._isInExpansion() &&
- (this._cursor.peek() === $AT || this._cursor.peek() === $RBRACE)) {
- return true;
- }
- return false;
- }
- /**
- * Returns true if the current cursor is pointing to the start of a tag
- * (opening/closing/comments/cdata/etc).
- */
- _isTagStart() {
- if (this._cursor.peek() === $LT) {
- // We assume that `<` followed by whitespace is not the start of an HTML element.
- const tmp = this._cursor.clone();
- tmp.advance();
- // If the next character is alphabetic, ! nor / then it is a tag start
- const code = tmp.peek();
- if (($a <= code && code <= $z) ||
- ($A <= code && code <= $Z) ||
- code === $SLASH ||
- code === $BANG) {
- return true;
- }
- }
- return false;
- }
- _readUntil(char) {
- const start = this._cursor.clone();
- this._attemptUntilChar(char);
- return this._cursor.getChars(start);
- }
- _isInExpansion() {
- return this._isInExpansionCase() || this._isInExpansionForm();
- }
- _isInExpansionCase() {
- return (this._expansionCaseStack.length > 0 &&
- this._expansionCaseStack[this._expansionCaseStack.length - 1] ===
- 21 /* TokenType.EXPANSION_CASE_EXP_START */);
- }
- _isInExpansionForm() {
- return (this._expansionCaseStack.length > 0 &&
- this._expansionCaseStack[this._expansionCaseStack.length - 1] ===
- 19 /* TokenType.EXPANSION_FORM_START */);
- }
- isExpansionFormStart() {
- if (this._cursor.peek() !== $LBRACE) {
- return false;
- }
- if (this._interpolationConfig) {
- const start = this._cursor.clone();
- const isInterpolation = this._attemptStr(this._interpolationConfig.start);
- this._cursor = start;
- return !isInterpolation;
- }
- return true;
- }
- }
- function isNotWhitespace(code) {
- return !isWhitespace(code) || code === $EOF;
- }
- function isNameEnd(code) {
- return (isWhitespace(code) ||
- code === $GT ||
- code === $LT ||
- code === $SLASH ||
- code === $SQ ||
- code === $DQ ||
- code === $EQ ||
- code === $EOF);
- }
- function isPrefixEnd(code) {
- return ((code < $a || $z < code) &&
- (code < $A || $Z < code) &&
- (code < $0 || code > $9));
- }
- function isDigitEntityEnd(code) {
- return code === $SEMICOLON || code === $EOF || !isAsciiHexDigit(code);
- }
- function isNamedEntityEnd(code) {
- return code === $SEMICOLON || code === $EOF || !isAsciiLetter(code);
- }
- function isExpansionCaseStart(peek) {
- return peek !== $RBRACE;
- }
- function compareCharCodeCaseInsensitive(code1, code2) {
- return toUpperCaseCharCode(code1) === toUpperCaseCharCode(code2);
- }
- function toUpperCaseCharCode(code) {
- return code >= $a && code <= $z ? code - $a + $A : code;
- }
- function isBlockNameChar(code) {
- return isAsciiLetter(code) || isDigit(code) || code === $_;
- }
- function isBlockParameterChar(code) {
- return code !== $SEMICOLON && isNotWhitespace(code);
- }
- function mergeTextTokens(srcTokens) {
- const dstTokens = [];
- let lastDstToken = undefined;
- for (let i = 0; i < srcTokens.length; i++) {
- const token = srcTokens[i];
- if ((lastDstToken && lastDstToken.type === 5 /* TokenType.TEXT */ && token.type === 5 /* TokenType.TEXT */) ||
- (lastDstToken &&
- lastDstToken.type === 16 /* TokenType.ATTR_VALUE_TEXT */ &&
- token.type === 16 /* TokenType.ATTR_VALUE_TEXT */)) {
- lastDstToken.parts[0] += token.parts[0];
- lastDstToken.sourceSpan.end = token.sourceSpan.end;
- }
- else {
- lastDstToken = token;
- dstTokens.push(lastDstToken);
- }
- }
- return dstTokens;
- }
- class PlainCharacterCursor {
- state;
- file;
- input;
- end;
- constructor(fileOrCursor, range) {
- if (fileOrCursor instanceof PlainCharacterCursor) {
- this.file = fileOrCursor.file;
- this.input = fileOrCursor.input;
- this.end = fileOrCursor.end;
- const state = fileOrCursor.state;
- // Note: avoid using `{...fileOrCursor.state}` here as that has a severe performance penalty.
- // In ES5 bundles the object spread operator is translated into the `__assign` helper, which
- // is not optimized by VMs as efficiently as a raw object literal. Since this constructor is
- // called in tight loops, this difference matters.
- this.state = {
- peek: state.peek,
- offset: state.offset,
- line: state.line,
- column: state.column,
- };
- }
- else {
- if (!range) {
- throw new Error('Programming error: the range argument must be provided with a file argument.');
- }
- this.file = fileOrCursor;
- this.input = fileOrCursor.content;
- this.end = range.endPos;
- this.state = {
- peek: -1,
- offset: range.startPos,
- line: range.startLine,
- column: range.startCol,
- };
- }
- }
- clone() {
- return new PlainCharacterCursor(this);
- }
- peek() {
- return this.state.peek;
- }
- charsLeft() {
- return this.end - this.state.offset;
- }
- diff(other) {
- return this.state.offset - other.state.offset;
- }
- advance() {
- this.advanceState(this.state);
- }
- init() {
- this.updatePeek(this.state);
- }
- getSpan(start, leadingTriviaCodePoints) {
- start = start || this;
- let fullStart = start;
- if (leadingTriviaCodePoints) {
- while (this.diff(start) > 0 && leadingTriviaCodePoints.indexOf(start.peek()) !== -1) {
- if (fullStart === start) {
- start = start.clone();
- }
- start.advance();
- }
- }
- const startLocation = this.locationFromCursor(start);
- const endLocation = this.locationFromCursor(this);
- const fullStartLocation = fullStart !== start ? this.locationFromCursor(fullStart) : startLocation;
- return new ParseSourceSpan(startLocation, endLocation, fullStartLocation);
- }
- getChars(start) {
- return this.input.substring(start.state.offset, this.state.offset);
- }
- charAt(pos) {
- return this.input.charCodeAt(pos);
- }
- advanceState(state) {
- if (state.offset >= this.end) {
- this.state = state;
- throw new CursorError('Unexpected character "EOF"', this);
- }
- const currentChar = this.charAt(state.offset);
- if (currentChar === $LF) {
- state.line++;
- state.column = 0;
- }
- else if (!isNewLine(currentChar)) {
- state.column++;
- }
- state.offset++;
- this.updatePeek(state);
- }
- updatePeek(state) {
- state.peek = state.offset >= this.end ? $EOF : this.charAt(state.offset);
- }
- locationFromCursor(cursor) {
- return new ParseLocation(cursor.file, cursor.state.offset, cursor.state.line, cursor.state.column);
- }
- }
- class EscapedCharacterCursor extends PlainCharacterCursor {
- internalState;
- constructor(fileOrCursor, range) {
- if (fileOrCursor instanceof EscapedCharacterCursor) {
- super(fileOrCursor);
- this.internalState = { ...fileOrCursor.internalState };
- }
- else {
- super(fileOrCursor, range);
- this.internalState = this.state;
- }
- }
- advance() {
- this.state = this.internalState;
- super.advance();
- this.processEscapeSequence();
- }
- init() {
- super.init();
- this.processEscapeSequence();
- }
- clone() {
- return new EscapedCharacterCursor(this);
- }
- getChars(start) {
- const cursor = start.clone();
- let chars = '';
- while (cursor.internalState.offset < this.internalState.offset) {
- chars += String.fromCodePoint(cursor.peek());
- cursor.advance();
- }
- return chars;
- }
- /**
- * Process the escape sequence that starts at the current position in the text.
- *
- * This method is called to ensure that `peek` has the unescaped value of escape sequences.
- */
- processEscapeSequence() {
- const peek = () => this.internalState.peek;
- if (peek() === $BACKSLASH) {
- // We have hit an escape sequence so we need the internal state to become independent
- // of the external state.
- this.internalState = { ...this.state };
- // Move past the backslash
- this.advanceState(this.internalState);
- // First check for standard control char sequences
- if (peek() === $n) {
- this.state.peek = $LF;
- }
- else if (peek() === $r) {
- this.state.peek = $CR;
- }
- else if (peek() === $v) {
- this.state.peek = $VTAB;
- }
- else if (peek() === $t) {
- this.state.peek = $TAB;
- }
- else if (peek() === $b) {
- this.state.peek = $BSPACE;
- }
- else if (peek() === $f) {
- this.state.peek = $FF;
- }
- // Now consider more complex sequences
- else if (peek() === $u) {
- // Unicode code-point sequence
- this.advanceState(this.internalState); // advance past the `u` char
- if (peek() === $LBRACE) {
- // Variable length Unicode, e.g. `\x{123}`
- this.advanceState(this.internalState); // advance past the `{` char
- // Advance past the variable number of hex digits until we hit a `}` char
- const digitStart = this.clone();
- let length = 0;
- while (peek() !== $RBRACE) {
- this.advanceState(this.internalState);
- length++;
- }
- this.state.peek = this.decodeHexDigits(digitStart, length);
- }
- else {
- // Fixed length Unicode, e.g. `\u1234`
- const digitStart = this.clone();
- this.advanceState(this.internalState);
- this.advanceState(this.internalState);
- this.advanceState(this.internalState);
- this.state.peek = this.decodeHexDigits(digitStart, 4);
- }
- }
- else if (peek() === $x) {
- // Hex char code, e.g. `\x2F`
- this.advanceState(this.internalState); // advance past the `x` char
- const digitStart = this.clone();
- this.advanceState(this.internalState);
- this.state.peek = this.decodeHexDigits(digitStart, 2);
- }
- else if (isOctalDigit(peek())) {
- // Octal char code, e.g. `\012`,
- let octal = '';
- let length = 0;
- let previous = this.clone();
- while (isOctalDigit(peek()) && length < 3) {
- previous = this.clone();
- octal += String.fromCodePoint(peek());
- this.advanceState(this.internalState);
- length++;
- }
- this.state.peek = parseInt(octal, 8);
- // Backup one char
- this.internalState = previous.internalState;
- }
- else if (isNewLine(this.internalState.peek)) {
- // Line continuation `\` followed by a new line
- this.advanceState(this.internalState); // advance over the newline
- this.state = this.internalState;
- }
- else {
- // If none of the `if` blocks were executed then we just have an escaped normal character.
- // In that case we just, effectively, skip the backslash from the character.
- this.state.peek = this.internalState.peek;
- }
- }
- }
- decodeHexDigits(start, length) {
- const hex = this.input.slice(start.internalState.offset, start.internalState.offset + length);
- const charCode = parseInt(hex, 16);
- if (!isNaN(charCode)) {
- return charCode;
- }
- else {
- start.state = start.internalState;
- throw new CursorError('Invalid hexadecimal escape sequence', start);
- }
- }
- }
- class CursorError {
- msg;
- cursor;
- constructor(msg, cursor) {
- this.msg = msg;
- this.cursor = cursor;
- }
- }
- class TreeError extends ParseError {
- elementName;
- static create(elementName, span, msg) {
- return new TreeError(elementName, span, msg);
- }
- constructor(elementName, span, msg) {
- super(span, msg);
- this.elementName = elementName;
- }
- }
- class ParseTreeResult {
- rootNodes;
- errors;
- constructor(rootNodes, errors) {
- this.rootNodes = rootNodes;
- this.errors = errors;
- }
- }
- let Parser$1 = class Parser {
- getTagDefinition;
- constructor(getTagDefinition) {
- this.getTagDefinition = getTagDefinition;
- }
- parse(source, url, options) {
- const tokenizeResult = tokenize(source, url, this.getTagDefinition, options);
- const parser = new _TreeBuilder(tokenizeResult.tokens, this.getTagDefinition);
- parser.build();
- return new ParseTreeResult(parser.rootNodes, tokenizeResult.errors.concat(parser.errors));
- }
- };
- class _TreeBuilder {
- tokens;
- getTagDefinition;
- _index = -1;
- // `_peek` will be initialized by the call to `_advance()` in the constructor.
- _peek;
- _containerStack = [];
- rootNodes = [];
- errors = [];
- constructor(tokens, getTagDefinition) {
- this.tokens = tokens;
- this.getTagDefinition = getTagDefinition;
- this._advance();
- }
- build() {
- while (this._peek.type !== 33 /* TokenType.EOF */) {
- if (this._peek.type === 0 /* TokenType.TAG_OPEN_START */ ||
- this._peek.type === 4 /* TokenType.INCOMPLETE_TAG_OPEN */) {
- this._consumeStartTag(this._advance());
- }
- else if (this._peek.type === 3 /* TokenType.TAG_CLOSE */) {
- this._consumeEndTag(this._advance());
- }
- else if (this._peek.type === 12 /* TokenType.CDATA_START */) {
- this._closeVoidElement();
- this._consumeCdata(this._advance());
- }
- else if (this._peek.type === 10 /* TokenType.COMMENT_START */) {
- this._closeVoidElement();
- this._consumeComment(this._advance());
- }
- else if (this._peek.type === 5 /* TokenType.TEXT */ ||
- this._peek.type === 7 /* TokenType.RAW_TEXT */ ||
- this._peek.type === 6 /* TokenType.ESCAPABLE_RAW_TEXT */) {
- this._closeVoidElement();
- this._consumeText(this._advance());
- }
- else if (this._peek.type === 19 /* TokenType.EXPANSION_FORM_START */) {
- this._consumeExpansion(this._advance());
- }
- else if (this._peek.type === 24 /* TokenType.BLOCK_OPEN_START */) {
- this._closeVoidElement();
- this._consumeBlockOpen(this._advance());
- }
- else if (this._peek.type === 26 /* TokenType.BLOCK_CLOSE */) {
- this._closeVoidElement();
- this._consumeBlockClose(this._advance());
- }
- else if (this._peek.type === 28 /* TokenType.INCOMPLETE_BLOCK_OPEN */) {
- this._closeVoidElement();
- this._consumeIncompleteBlock(this._advance());
- }
- else if (this._peek.type === 29 /* TokenType.LET_START */) {
- this._closeVoidElement();
- this._consumeLet(this._advance());
- }
- else if (this._peek.type === 32 /* TokenType.INCOMPLETE_LET */) {
- this._closeVoidElement();
- this._consumeIncompleteLet(this._advance());
- }
- else {
- // Skip all other tokens...
- this._advance();
- }
- }
- for (const leftoverContainer of this._containerStack) {
- // Unlike HTML elements, blocks aren't closed implicitly by the end of the file.
- if (leftoverContainer instanceof Block) {
- this.errors.push(TreeError.create(leftoverContainer.name, leftoverContainer.sourceSpan, `Unclosed block "${leftoverContainer.name}"`));
- }
- }
- }
- _advance() {
- const prev = this._peek;
- if (this._index < this.tokens.length - 1) {
- // Note: there is always an EOF token at the end
- this._index++;
- }
- this._peek = this.tokens[this._index];
- return prev;
- }
- _advanceIf(type) {
- if (this._peek.type === type) {
- return this._advance();
- }
- return null;
- }
- _consumeCdata(_startToken) {
- this._consumeText(this._advance());
- this._advanceIf(13 /* TokenType.CDATA_END */);
- }
- _consumeComment(token) {
- const text = this._advanceIf(7 /* TokenType.RAW_TEXT */);
- const endToken = this._advanceIf(11 /* TokenType.COMMENT_END */);
- const value = text != null ? text.parts[0].trim() : null;
- const sourceSpan = endToken == null
- ? token.sourceSpan
- : new ParseSourceSpan(token.sourceSpan.start, endToken.sourceSpan.end, token.sourceSpan.fullStart);
- this._addToParent(new Comment(value, sourceSpan));
- }
- _consumeExpansion(token) {
- const switchValue = this._advance();
- const type = this._advance();
- const cases = [];
- // read =
- while (this._peek.type === 20 /* TokenType.EXPANSION_CASE_VALUE */) {
- const expCase = this._parseExpansionCase();
- if (!expCase)
- return; // error
- cases.push(expCase);
- }
- // read the final }
- if (this._peek.type !== 23 /* TokenType.EXPANSION_FORM_END */) {
- this.errors.push(TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '}'.`));
- return;
- }
- const sourceSpan = new ParseSourceSpan(token.sourceSpan.start, this._peek.sourceSpan.end, token.sourceSpan.fullStart);
- this._addToParent(new Expansion(switchValue.parts[0], type.parts[0], cases, sourceSpan, switchValue.sourceSpan));
- this._advance();
- }
- _parseExpansionCase() {
- const value = this._advance();
- // read {
- if (this._peek.type !== 21 /* TokenType.EXPANSION_CASE_EXP_START */) {
- this.errors.push(TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '{'.`));
- return null;
- }
- // read until }
- const start = this._advance();
- const exp = this._collectExpansionExpTokens(start);
- if (!exp)
- return null;
- const end = this._advance();
- exp.push({ type: 33 /* TokenType.EOF */, parts: [], sourceSpan: end.sourceSpan });
- // parse everything in between { and }
- const expansionCaseParser = new _TreeBuilder(exp, this.getTagDefinition);
- expansionCaseParser.build();
- if (expansionCaseParser.errors.length > 0) {
- this.errors = this.errors.concat(expansionCaseParser.errors);
- return null;
- }
- const sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end, value.sourceSpan.fullStart);
- const expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end, start.sourceSpan.fullStart);
- return new ExpansionCase(value.parts[0], expansionCaseParser.rootNodes, sourceSpan, value.sourceSpan, expSourceSpan);
- }
- _collectExpansionExpTokens(start) {
- const exp = [];
- const expansionFormStack = [21 /* TokenType.EXPANSION_CASE_EXP_START */];
- while (true) {
- if (this._peek.type === 19 /* TokenType.EXPANSION_FORM_START */ ||
- this._peek.type === 21 /* TokenType.EXPANSION_CASE_EXP_START */) {
- expansionFormStack.push(this._peek.type);
- }
- if (this._peek.type === 22 /* TokenType.EXPANSION_CASE_EXP_END */) {
- if (lastOnStack(expansionFormStack, 21 /* TokenType.EXPANSION_CASE_EXP_START */)) {
- expansionFormStack.pop();
- if (expansionFormStack.length === 0)
- return exp;
- }
- else {
- this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
- return null;
- }
- }
- if (this._peek.type === 23 /* TokenType.EXPANSION_FORM_END */) {
- if (lastOnStack(expansionFormStack, 19 /* TokenType.EXPANSION_FORM_START */)) {
- expansionFormStack.pop();
- }
- else {
- this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
- return null;
- }
- }
- if (this._peek.type === 33 /* TokenType.EOF */) {
- this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
- return null;
- }
- exp.push(this._advance());
- }
- }
- _consumeText(token) {
- const tokens = [token];
- const startSpan = token.sourceSpan;
- let text = token.parts[0];
- if (text.length > 0 && text[0] === '\n') {
- const parent = this._getContainer();
- if (parent != null &&
- parent.children.length === 0 &&
- this.getTagDefinition(parent.name).ignoreFirstLf) {
- text = text.substring(1);
- tokens[0] = { type: token.type, sourceSpan: token.sourceSpan, parts: [text] };
- }
- }
- while (this._peek.type === 8 /* TokenType.INTERPOLATION */ ||
- this._peek.type === 5 /* TokenType.TEXT */ ||
- this._peek.type === 9 /* TokenType.ENCODED_ENTITY */) {
- token = this._advance();
- tokens.push(token);
- if (token.type === 8 /* TokenType.INTERPOLATION */) {
- // For backward compatibility we decode HTML entities that appear in interpolation
- // expressions. This is arguably a bug, but it could be a considerable breaking change to
- // fix it. It should be addressed in a larger project to refactor the entire parser/lexer
- // chain after View Engine has been removed.
- text += token.parts.join('').replace(/&([^;]+);/g, decodeEntity);
- }
- else if (token.type === 9 /* TokenType.ENCODED_ENTITY */) {
- text += token.parts[0];
- }
- else {
- text += token.parts.join('');
- }
- }
- if (text.length > 0) {
- const endSpan = token.sourceSpan;
- this._addToParent(new Text(text, new ParseSourceSpan(startSpan.start, endSpan.end, startSpan.fullStart, startSpan.details), tokens));
- }
- }
- _closeVoidElement() {
- const el = this._getContainer();
- if (el instanceof Element && this.getTagDefinition(el.name).isVoid) {
- this._containerStack.pop();
- }
- }
- _consumeStartTag(startTagToken) {
- const [prefix, name] = startTagToken.parts;
- const attrs = [];
- while (this._peek.type === 14 /* TokenType.ATTR_NAME */) {
- attrs.push(this._consumeAttr(this._advance()));
- }
- const fullName = this._getElementFullName(prefix, name, this._getClosestParentElement());
- let selfClosing = false;
- // Note: There could have been a tokenizer error
- // so that we don't get a token for the end tag...
- if (this._peek.type === 2 /* TokenType.TAG_OPEN_END_VOID */) {
- this._advance();
- selfClosing = true;
- const tagDef = this.getTagDefinition(fullName);
- if (!(tagDef.canSelfClose || getNsPrefix(fullName) !== null || tagDef.isVoid)) {
- this.errors.push(TreeError.create(fullName, startTagToken.sourceSpan, `Only void, custom and foreign elements can be self closed "${startTagToken.parts[1]}"`));
- }
- }
- else if (this._peek.type === 1 /* TokenType.TAG_OPEN_END */) {
- this._advance();
- selfClosing = false;
- }
- const end = this._peek.sourceSpan.fullStart;
- const span = new ParseSourceSpan(startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
- // Create a separate `startSpan` because `span` will be modified when there is an `end` span.
- const startSpan = new ParseSourceSpan(startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
- const el = new Element(fullName, attrs, [], span, startSpan, undefined);
- const parentEl = this._getContainer();
- this._pushContainer(el, parentEl instanceof Element &&
- this.getTagDefinition(parentEl.name).isClosedByChild(el.name));
- if (selfClosing) {
- // Elements that are self-closed have their `endSourceSpan` set to the full span, as the
- // element start tag also represents the end tag.
- this._popContainer(fullName, Element, span);
- }
- else if (startTagToken.type === 4 /* TokenType.INCOMPLETE_TAG_OPEN */) {
- // We already know the opening tag is not complete, so it is unlikely it has a corresponding
- // close tag. Let's optimistically parse it as a full element and emit an error.
- this._popContainer(fullName, Element, null);
- this.errors.push(TreeError.create(fullName, span, `Opening tag "${fullName}" not terminated.`));
- }
- }
- _pushContainer(node, isClosedByChild) {
- if (isClosedByChild) {
- this._containerStack.pop();
- }
- this._addToParent(node);
- this._containerStack.push(node);
- }
- _consumeEndTag(endTagToken) {
- const fullName = this._getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getClosestParentElement());
- if (this.getTagDefinition(fullName).isVoid) {
- this.errors.push(TreeError.create(fullName, endTagToken.sourceSpan, `Void elements do not have end tags "${endTagToken.parts[1]}"`));
- }
- else if (!this._popContainer(fullName, Element, endTagToken.sourceSpan)) {
- const errMsg = `Unexpected closing tag "${fullName}". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags`;
- this.errors.push(TreeError.create(fullName, endTagToken.sourceSpan, errMsg));
- }
- }
- /**
- * Closes the nearest element with the tag name `fullName` in the parse tree.
- * `endSourceSpan` is the span of the closing tag, or null if the element does
- * not have a closing tag (for example, this happens when an incomplete
- * opening tag is recovered).
- */
- _popContainer(expectedName, expectedType, endSourceSpan) {
- let unexpectedCloseTagDetected = false;
- for (let stackIndex = this._containerStack.length - 1; stackIndex >= 0; stackIndex--) {
- const node = this._containerStack[stackIndex];
- if ((node.name === expectedName || expectedName === null) && node instanceof expectedType) {
- // Record the parse span with the element that is being closed. Any elements that are
- // removed from the element stack at this point are closed implicitly, so they won't get
- // an end source span (as there is no explicit closing element).
- node.endSourceSpan = endSourceSpan;
- node.sourceSpan.end = endSourceSpan !== null ? endSourceSpan.end : node.sourceSpan.end;
- this._containerStack.splice(stackIndex, this._containerStack.length - stackIndex);
- return !unexpectedCloseTagDetected;
- }
- // Blocks and most elements are not self closing.
- if (node instanceof Block ||
- (node instanceof Element && !this.getTagDefinition(node.name).closedByParent)) {
- // Note that we encountered an unexpected close tag but continue processing the element
- // stack so we can assign an `endSourceSpan` if there is a corresponding start tag for this
- // end tag in the stack.
- unexpectedCloseTagDetected = true;
- }
- }
- return false;
- }
- _consumeAttr(attrName) {
- const fullName = mergeNsAndName(attrName.parts[0], attrName.parts[1]);
- let attrEnd = attrName.sourceSpan.end;
- // Consume any quote
- if (this._peek.type === 15 /* TokenType.ATTR_QUOTE */) {
- this._advance();
- }
- // Consume the attribute value
- let value = '';
- const valueTokens = [];
- let valueStartSpan = undefined;
- let valueEnd = undefined;
- // NOTE: We need to use a new variable `nextTokenType` here to hide the actual type of
- // `_peek.type` from TS. Otherwise TS will narrow the type of `_peek.type` preventing it from
- // being able to consider `ATTR_VALUE_INTERPOLATION` as an option. This is because TS is not
- // able to see that `_advance()` will actually mutate `_peek`.
- const nextTokenType = this._peek.type;
- if (nextTokenType === 16 /* TokenType.ATTR_VALUE_TEXT */) {
- valueStartSpan = this._peek.sourceSpan;
- valueEnd = this._peek.sourceSpan.end;
- while (this._peek.type === 16 /* TokenType.ATTR_VALUE_TEXT */ ||
- this._peek.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */ ||
- this._peek.type === 9 /* TokenType.ENCODED_ENTITY */) {
- const valueToken = this._advance();
- valueTokens.push(valueToken);
- if (valueToken.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */) {
- // For backward compatibility we decode HTML entities that appear in interpolation
- // expressions. This is arguably a bug, but it could be a considerable breaking change to
- // fix it. It should be addressed in a larger project to refactor the entire parser/lexer
- // chain after View Engine has been removed.
- value += valueToken.parts.join('').replace(/&([^;]+);/g, decodeEntity);
- }
- else if (valueToken.type === 9 /* TokenType.ENCODED_ENTITY */) {
- value += valueToken.parts[0];
- }
- else {
- value += valueToken.parts.join('');
- }
- valueEnd = attrEnd = valueToken.sourceSpan.end;
- }
- }
- // Consume any quote
- if (this._peek.type === 15 /* TokenType.ATTR_QUOTE */) {
- const quoteToken = this._advance();
- attrEnd = quoteToken.sourceSpan.end;
- }
- const valueSpan = valueStartSpan &&
- valueEnd &&
- new ParseSourceSpan(valueStartSpan.start, valueEnd, valueStartSpan.fullStart);
- return new Attribute(fullName, value, new ParseSourceSpan(attrName.sourceSpan.start, attrEnd, attrName.sourceSpan.fullStart), attrName.sourceSpan, valueSpan, valueTokens.length > 0 ? valueTokens : undefined, undefined);
- }
- _consumeBlockOpen(token) {
- const parameters = [];
- while (this._peek.type === 27 /* TokenType.BLOCK_PARAMETER */) {
- const paramToken = this._advance();
- parameters.push(new BlockParameter(paramToken.parts[0], paramToken.sourceSpan));
- }
- if (this._peek.type === 25 /* TokenType.BLOCK_OPEN_END */) {
- this._advance();
- }
- const end = this._peek.sourceSpan.fullStart;
- const span = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
- // Create a separate `startSpan` because `span` will be modified when there is an `end` span.
- const startSpan = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
- const block = new Block(token.parts[0], parameters, [], span, token.sourceSpan, startSpan);
- this._pushContainer(block, false);
- }
- _consumeBlockClose(token) {
- if (!this._popContainer(null, Block, token.sourceSpan)) {
- this.errors.push(TreeError.create(null, token.sourceSpan, `Unexpected closing block. The block may have been closed earlier. ` +
- `If you meant to write the } character, you should use the "}" ` +
- `HTML entity instead.`));
- }
- }
- _consumeIncompleteBlock(token) {
- const parameters = [];
- while (this._peek.type === 27 /* TokenType.BLOCK_PARAMETER */) {
- const paramToken = this._advance();
- parameters.push(new BlockParameter(paramToken.parts[0], paramToken.sourceSpan));
- }
- const end = this._peek.sourceSpan.fullStart;
- const span = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
- // Create a separate `startSpan` because `span` will be modified when there is an `end` span.
- const startSpan = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
- const block = new Block(token.parts[0], parameters, [], span, token.sourceSpan, startSpan);
- this._pushContainer(block, false);
- // Incomplete blocks don't have children so we close them immediately and report an error.
- this._popContainer(null, Block, null);
- this.errors.push(TreeError.create(token.parts[0], span, `Incomplete block "${token.parts[0]}". If you meant to write the @ character, ` +
- `you should use the "@" HTML entity instead.`));
- }
- _consumeLet(startToken) {
- const name = startToken.parts[0];
- let valueToken;
- let endToken;
- if (this._peek.type !== 30 /* TokenType.LET_VALUE */) {
- this.errors.push(TreeError.create(startToken.parts[0], startToken.sourceSpan, `Invalid @let declaration "${name}". Declaration must have a value.`));
- return;
- }
- else {
- valueToken = this._advance();
- }
- // Type cast is necessary here since TS narrowed the type of `peek` above.
- if (this._peek.type !== 31 /* TokenType.LET_END */) {
- this.errors.push(TreeError.create(startToken.parts[0], startToken.sourceSpan, `Unterminated @let declaration "${name}". Declaration must be terminated with a semicolon.`));
- return;
- }
- else {
- endToken = this._advance();
- }
- const end = endToken.sourceSpan.fullStart;
- const span = new ParseSourceSpan(startToken.sourceSpan.start, end, startToken.sourceSpan.fullStart);
- // The start token usually captures the `@let`. Construct a name span by
- // offsetting the start by the length of any text before the name.
- const startOffset = startToken.sourceSpan.toString().lastIndexOf(name);
- const nameStart = startToken.sourceSpan.start.moveBy(startOffset);
- const nameSpan = new ParseSourceSpan(nameStart, startToken.sourceSpan.end);
- const node = new LetDeclaration(name, valueToken.parts[0], span, nameSpan, valueToken.sourceSpan);
- this._addToParent(node);
- }
- _consumeIncompleteLet(token) {
- // Incomplete `@let` declaration may end up with an empty name.
- const name = token.parts[0] ?? '';
- const nameString = name ? ` "${name}"` : '';
- // If there's at least a name, we can salvage an AST node that can be used for completions.
- if (name.length > 0) {
- const startOffset = token.sourceSpan.toString().lastIndexOf(name);
- const nameStart = token.sourceSpan.start.moveBy(startOffset);
- const nameSpan = new ParseSourceSpan(nameStart, token.sourceSpan.end);
- const valueSpan = new ParseSourceSpan(token.sourceSpan.start, token.sourceSpan.start.moveBy(0));
- const node = new LetDeclaration(name, '', token.sourceSpan, nameSpan, valueSpan);
- this._addToParent(node);
- }
- this.errors.push(TreeError.create(token.parts[0], token.sourceSpan, `Incomplete @let declaration${nameString}. ` +
- `@let declarations must be written as \`@let <name> = <value>;\``));
- }
- _getContainer() {
- return this._containerStack.length > 0
- ? this._containerStack[this._containerStack.length - 1]
- : null;
- }
- _getClosestParentElement() {
- for (let i = this._containerStack.length - 1; i > -1; i--) {
- if (this._containerStack[i] instanceof Element) {
- return this._containerStack[i];
- }
- }
- return null;
- }
- _addToParent(node) {
- const parent = this._getContainer();
- if (parent === null) {
- this.rootNodes.push(node);
- }
- else {
- parent.children.push(node);
- }
- }
- _getElementFullName(prefix, localName, parentElement) {
- if (prefix === '') {
- prefix = this.getTagDefinition(localName).implicitNamespacePrefix || '';
- if (prefix === '' && parentElement != null) {
- const parentTagName = splitNsName(parentElement.name)[1];
- const parentTagDefinition = this.getTagDefinition(parentTagName);
- if (!parentTagDefinition.preventNamespaceInheritance) {
- prefix = getNsPrefix(parentElement.name);
- }
- }
- }
- return mergeNsAndName(prefix, localName);
- }
- }
- function lastOnStack(stack, element) {
- return stack.length > 0 && stack[stack.length - 1] === element;
- }
- /**
- * Decode the `entity` string, which we believe is the contents of an HTML entity.
- *
- * If the string is not actually a valid/known entity then just return the original `match` string.
- */
- function decodeEntity(match, entity) {
- if (NAMED_ENTITIES[entity] !== undefined) {
- return NAMED_ENTITIES[entity] || match;
- }
- if (/^#x[a-f0-9]+$/i.test(entity)) {
- return String.fromCodePoint(parseInt(entity.slice(2), 16));
- }
- if (/^#\d+$/.test(entity)) {
- return String.fromCodePoint(parseInt(entity.slice(1), 10));
- }
- return match;
- }
- const PRESERVE_WS_ATTR_NAME = 'ngPreserveWhitespaces';
- const SKIP_WS_TRIM_TAGS = new Set(['pre', 'template', 'textarea', 'script', 'style']);
- // Equivalent to \s with \u00a0 (non-breaking space) excluded.
- // Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
- const WS_CHARS = ' \f\n\r\t\v\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff';
- const NO_WS_REGEXP = new RegExp(`[^${WS_CHARS}]`);
- const WS_REPLACE_REGEXP = new RegExp(`[${WS_CHARS}]{2,}`, 'g');
- function hasPreserveWhitespacesAttr(attrs) {
- return attrs.some((attr) => attr.name === PRESERVE_WS_ATTR_NAME);
- }
- /**
- * &ngsp; is a placeholder for non-removable space
- * &ngsp; is converted to the 0xE500 PUA (Private Use Areas) unicode character
- * and later on replaced by a space.
- */
- function replaceNgsp(value) {
- // lexer is replacing the &ngsp; pseudo-entity with NGSP_UNICODE
- return value.replace(new RegExp(NGSP_UNICODE, 'g'), ' ');
- }
- /**
- * This visitor can walk HTML parse tree and remove / trim text nodes using the following rules:
- * - consider spaces, tabs and new lines as whitespace characters;
- * - drop text nodes consisting of whitespace characters only;
- * - for all other text nodes replace consecutive whitespace characters with one space;
- * - convert &ngsp; pseudo-entity to a single space;
- *
- * Removal and trimming of whitespaces have positive performance impact (less code to generate
- * while compiling templates, faster view creation). At the same time it can be "destructive"
- * in some cases (whitespaces can influence layout). Because of the potential of breaking layout
- * this visitor is not activated by default in Angular 5 and people need to explicitly opt-in for
- * whitespace removal. The default option for whitespace removal will be revisited in Angular 6
- * and might be changed to "on" by default.
- *
- * If `originalNodeMap` is provided, the transformed nodes will be mapped back to their original
- * inputs. Any output nodes not in the map were not transformed. This supports correlating and
- * porting information between the trimmed nodes and original nodes (such as `i18n` properties)
- * such that trimming whitespace does not does not drop required information from the node.
- */
- class WhitespaceVisitor {
- preserveSignificantWhitespace;
- originalNodeMap;
- requireContext;
- // How many ICU expansions which are currently being visited. ICUs can be nested, so this
- // tracks the current depth of nesting. If this depth is greater than 0, then this visitor is
- // currently processing content inside an ICU expansion.
- icuExpansionDepth = 0;
- constructor(preserveSignificantWhitespace, originalNodeMap, requireContext = true) {
- this.preserveSignificantWhitespace = preserveSignificantWhitespace;
- this.originalNodeMap = originalNodeMap;
- this.requireContext = requireContext;
- }
- visitElement(element, context) {
- if (SKIP_WS_TRIM_TAGS.has(element.name) || hasPreserveWhitespacesAttr(element.attrs)) {
- // don't descent into elements where we need to preserve whitespaces
- // but still visit all attributes to eliminate one used as a market to preserve WS
- const newElement = new Element(element.name, visitAllWithSiblings(this, element.attrs), element.children, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
- this.originalNodeMap?.set(newElement, element);
- return newElement;
- }
- const newElement = new Element(element.name, element.attrs, visitAllWithSiblings(this, element.children), element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
- this.originalNodeMap?.set(newElement, element);
- return newElement;
- }
- visitAttribute(attribute, context) {
- return attribute.name !== PRESERVE_WS_ATTR_NAME ? attribute : null;
- }
- visitText(text, context) {
- const isNotBlank = text.value.match(NO_WS_REGEXP);
- const hasExpansionSibling = context && (context.prev instanceof Expansion || context.next instanceof Expansion);
- // Do not trim whitespace within ICU expansions when preserving significant whitespace.
- // Historically, ICU whitespace was never trimmed and this is really a bug. However fixing it
- // would change message IDs which we can't easily do. Instead we only trim ICU whitespace within
- // ICU expansions when not preserving significant whitespace, which is the new behavior where it
- // most matters.
- const inIcuExpansion = this.icuExpansionDepth > 0;
- if (inIcuExpansion && this.preserveSignificantWhitespace)
- return text;
- if (isNotBlank || hasExpansionSibling) {
- // Process the whitespace in the tokens of this Text node
- const tokens = text.tokens.map((token) => token.type === 5 /* TokenType.TEXT */ ? createWhitespaceProcessedTextToken(token) : token);
- // Fully trim message when significant whitespace is not preserved.
- if (!this.preserveSignificantWhitespace && tokens.length > 0) {
- // The first token should only call `.trimStart()` and the last token
- // should only call `.trimEnd()`, but there might be only one token which
- // needs to call both.
- const firstToken = tokens[0];
- tokens.splice(0, 1, trimLeadingWhitespace(firstToken, context));
- const lastToken = tokens[tokens.length - 1]; // Could be the same as the first token.
- tokens.splice(tokens.length - 1, 1, trimTrailingWhitespace(lastToken, context));
- }
- // Process the whitespace of the value of this Text node. Also trim the leading/trailing
- // whitespace when we don't need to preserve significant whitespace.
- const processed = processWhitespace(text.value);
- const value = this.preserveSignificantWhitespace
- ? processed
- : trimLeadingAndTrailingWhitespace(processed, context);
- const result = new Text(value, text.sourceSpan, tokens, text.i18n);
- this.originalNodeMap?.set(result, text);
- return result;
- }
- return null;
- }
- visitComment(comment, context) {
- return comment;
- }
- visitExpansion(expansion, context) {
- this.icuExpansionDepth++;
- let newExpansion;
- try {
- newExpansion = new Expansion(expansion.switchValue, expansion.type, visitAllWithSiblings(this, expansion.cases), expansion.sourceSpan, expansion.switchValueSourceSpan, expansion.i18n);
- }
- finally {
- this.icuExpansionDepth--;
- }
- this.originalNodeMap?.set(newExpansion, expansion);
- return newExpansion;
- }
- visitExpansionCase(expansionCase, context) {
- const newExpansionCase = new ExpansionCase(expansionCase.value, visitAllWithSiblings(this, expansionCase.expression), expansionCase.sourceSpan, expansionCase.valueSourceSpan, expansionCase.expSourceSpan);
- this.originalNodeMap?.set(newExpansionCase, expansionCase);
- return newExpansionCase;
- }
- visitBlock(block, context) {
- const newBlock = new Block(block.name, block.parameters, visitAllWithSiblings(this, block.children), block.sourceSpan, block.nameSpan, block.startSourceSpan, block.endSourceSpan);
- this.originalNodeMap?.set(newBlock, block);
- return newBlock;
- }
- visitBlockParameter(parameter, context) {
- return parameter;
- }
- visitLetDeclaration(decl, context) {
- return decl;
- }
- visit(_node, context) {
- // `visitAllWithSiblings` provides context necessary for ICU messages to be handled correctly.
- // Prefer that over calling `html.visitAll` directly on this visitor.
- if (this.requireContext && !context) {
- throw new Error(`WhitespaceVisitor requires context. Visit via \`visitAllWithSiblings\` to get this context.`);
- }
- return false;
- }
- }
- function trimLeadingWhitespace(token, context) {
- if (token.type !== 5 /* TokenType.TEXT */)
- return token;
- const isFirstTokenInTag = !context?.prev;
- if (!isFirstTokenInTag)
- return token;
- return transformTextToken(token, (text) => text.trimStart());
- }
- function trimTrailingWhitespace(token, context) {
- if (token.type !== 5 /* TokenType.TEXT */)
- return token;
- const isLastTokenInTag = !context?.next;
- if (!isLastTokenInTag)
- return token;
- return transformTextToken(token, (text) => text.trimEnd());
- }
- function trimLeadingAndTrailingWhitespace(text, context) {
- const isFirstTokenInTag = !context?.prev;
- const isLastTokenInTag = !context?.next;
- const maybeTrimmedStart = isFirstTokenInTag ? text.trimStart() : text;
- const maybeTrimmed = isLastTokenInTag ? maybeTrimmedStart.trimEnd() : maybeTrimmedStart;
- return maybeTrimmed;
- }
- function createWhitespaceProcessedTextToken({ type, parts, sourceSpan }) {
- return { type, parts: [processWhitespace(parts[0])], sourceSpan };
- }
- function transformTextToken({ type, parts, sourceSpan }, transform) {
- // `TextToken` only ever has one part as defined in its type, so we just transform the first element.
- return { type, parts: [transform(parts[0])], sourceSpan };
- }
- function processWhitespace(text) {
- return replaceNgsp(text).replace(WS_REPLACE_REGEXP, ' ');
- }
- function visitAllWithSiblings(visitor, nodes) {
- const result = [];
- nodes.forEach((ast, i) => {
- const context = { prev: nodes[i - 1], next: nodes[i + 1] };
- const astResult = ast.visit(visitor, context);
- if (astResult) {
- result.push(astResult);
- }
- });
- return result;
- }
- var TokenType;
- (function (TokenType) {
- TokenType[TokenType["Character"] = 0] = "Character";
- TokenType[TokenType["Identifier"] = 1] = "Identifier";
- TokenType[TokenType["PrivateIdentifier"] = 2] = "PrivateIdentifier";
- TokenType[TokenType["Keyword"] = 3] = "Keyword";
- TokenType[TokenType["String"] = 4] = "String";
- TokenType[TokenType["Operator"] = 5] = "Operator";
- TokenType[TokenType["Number"] = 6] = "Number";
- TokenType[TokenType["Error"] = 7] = "Error";
- })(TokenType || (TokenType = {}));
- var StringTokenKind;
- (function (StringTokenKind) {
- StringTokenKind[StringTokenKind["Plain"] = 0] = "Plain";
- StringTokenKind[StringTokenKind["TemplateLiteralPart"] = 1] = "TemplateLiteralPart";
- StringTokenKind[StringTokenKind["TemplateLiteralEnd"] = 2] = "TemplateLiteralEnd";
- })(StringTokenKind || (StringTokenKind = {}));
- const KEYWORDS = [
- 'var',
- 'let',
- 'as',
- 'null',
- 'undefined',
- 'true',
- 'false',
- 'if',
- 'else',
- 'this',
- 'typeof',
- ];
- class Lexer {
- tokenize(text) {
- return new _Scanner(text).scan();
- }
- }
- class Token {
- index;
- end;
- type;
- numValue;
- strValue;
- constructor(index, end, type, numValue, strValue) {
- this.index = index;
- this.end = end;
- this.type = type;
- this.numValue = numValue;
- this.strValue = strValue;
- }
- isCharacter(code) {
- return this.type === TokenType.Character && this.numValue === code;
- }
- isNumber() {
- return this.type === TokenType.Number;
- }
- isString() {
- return this.type === TokenType.String;
- }
- isOperator(operator) {
- return this.type === TokenType.Operator && this.strValue === operator;
- }
- isIdentifier() {
- return this.type === TokenType.Identifier;
- }
- isPrivateIdentifier() {
- return this.type === TokenType.PrivateIdentifier;
- }
- isKeyword() {
- return this.type === TokenType.Keyword;
- }
- isKeywordLet() {
- return this.type === TokenType.Keyword && this.strValue === 'let';
- }
- isKeywordAs() {
- return this.type === TokenType.Keyword && this.strValue === 'as';
- }
- isKeywordNull() {
- return this.type === TokenType.Keyword && this.strValue === 'null';
- }
- isKeywordUndefined() {
- return this.type === TokenType.Keyword && this.strValue === 'undefined';
- }
- isKeywordTrue() {
- return this.type === TokenType.Keyword && this.strValue === 'true';
- }
- isKeywordFalse() {
- return this.type === TokenType.Keyword && this.strValue === 'false';
- }
- isKeywordThis() {
- return this.type === TokenType.Keyword && this.strValue === 'this';
- }
- isKeywordTypeof() {
- return this.type === TokenType.Keyword && this.strValue === 'typeof';
- }
- isError() {
- return this.type === TokenType.Error;
- }
- toNumber() {
- return this.type === TokenType.Number ? this.numValue : -1;
- }
- isTemplateLiteralPart() {
- return this.isString() && this.kind === StringTokenKind.TemplateLiteralPart;
- }
- isTemplateLiteralEnd() {
- return this.isString() && this.kind === StringTokenKind.TemplateLiteralEnd;
- }
- isTemplateLiteralInterpolationStart() {
- return this.isOperator('${');
- }
- isTemplateLiteralInterpolationEnd() {
- return this.isOperator('}');
- }
- toString() {
- switch (this.type) {
- case TokenType.Character:
- case TokenType.Identifier:
- case TokenType.Keyword:
- case TokenType.Operator:
- case TokenType.PrivateIdentifier:
- case TokenType.String:
- case TokenType.Error:
- return this.strValue;
- case TokenType.Number:
- return this.numValue.toString();
- default:
- return null;
- }
- }
- }
- class StringToken extends Token {
- kind;
- constructor(index, end, strValue, kind) {
- super(index, end, TokenType.String, 0, strValue);
- this.kind = kind;
- }
- }
- function newCharacterToken(index, end, code) {
- return new Token(index, end, TokenType.Character, code, String.fromCharCode(code));
- }
- function newIdentifierToken(index, end, text) {
- return new Token(index, end, TokenType.Identifier, 0, text);
- }
- function newPrivateIdentifierToken(index, end, text) {
- return new Token(index, end, TokenType.PrivateIdentifier, 0, text);
- }
- function newKeywordToken(index, end, text) {
- return new Token(index, end, TokenType.Keyword, 0, text);
- }
- function newOperatorToken(index, end, text) {
- return new Token(index, end, TokenType.Operator, 0, text);
- }
- function newNumberToken(index, end, n) {
- return new Token(index, end, TokenType.Number, n, '');
- }
- function newErrorToken(index, end, message) {
- return new Token(index, end, TokenType.Error, 0, message);
- }
- const EOF = new Token(-1, -1, TokenType.Character, 0, '');
- class _Scanner {
- input;
- tokens = [];
- length;
- peek = 0;
- index = -1;
- literalInterpolationDepth = 0;
- braceDepth = 0;
- constructor(input) {
- this.input = input;
- this.length = input.length;
- this.advance();
- }
- scan() {
- let token = this.scanToken();
- while (token !== null) {
- this.tokens.push(token);
- token = this.scanToken();
- }
- return this.tokens;
- }
- advance() {
- this.peek = ++this.index >= this.length ? $EOF : this.input.charCodeAt(this.index);
- }
- scanToken() {
- const input = this.input;
- const length = this.length;
- let peek = this.peek;
- let index = this.index;
- // Skip whitespace.
- while (peek <= $SPACE) {
- if (++index >= length) {
- peek = $EOF;
- break;
- }
- else {
- peek = input.charCodeAt(index);
- }
- }
- this.peek = peek;
- this.index = index;
- if (index >= length) {
- return null;
- }
- // Handle identifiers and numbers.
- if (isIdentifierStart(peek)) {
- return this.scanIdentifier();
- }
- if (isDigit(peek)) {
- return this.scanNumber(index);
- }
- const start = index;
- switch (peek) {
- case $PERIOD:
- this.advance();
- return isDigit(this.peek)
- ? this.scanNumber(start)
- : newCharacterToken(start, this.index, $PERIOD);
- case $LPAREN:
- case $RPAREN:
- case $LBRACKET:
- case $RBRACKET:
- case $COMMA:
- case $COLON:
- case $SEMICOLON:
- return this.scanCharacter(start, peek);
- case $LBRACE:
- return this.scanOpenBrace(start, peek);
- case $RBRACE:
- return this.scanCloseBrace(start, peek);
- case $SQ:
- case $DQ:
- return this.scanString();
- case $BT:
- this.advance();
- return this.scanTemplateLiteralPart(start);
- case $HASH:
- return this.scanPrivateIdentifier();
- case $PLUS:
- case $MINUS:
- case $STAR:
- case $SLASH:
- case $PERCENT:
- case $CARET:
- return this.scanOperator(start, String.fromCharCode(peek));
- case $QUESTION:
- return this.scanQuestion(start);
- case $LT:
- case $GT:
- return this.scanComplexOperator(start, String.fromCharCode(peek), $EQ, '=');
- case $BANG:
- case $EQ:
- return this.scanComplexOperator(start, String.fromCharCode(peek), $EQ, '=', $EQ, '=');
- case $AMPERSAND:
- return this.scanComplexOperator(start, '&', $AMPERSAND, '&');
- case $BAR:
- return this.scanComplexOperator(start, '|', $BAR, '|');
- case $NBSP:
- while (isWhitespace(this.peek))
- this.advance();
- return this.scanToken();
- }
- this.advance();
- return this.error(`Unexpected character [${String.fromCharCode(peek)}]`, 0);
- }
- scanCharacter(start, code) {
- this.advance();
- return newCharacterToken(start, this.index, code);
- }
- scanOperator(start, str) {
- this.advance();
- return newOperatorToken(start, this.index, str);
- }
- scanOpenBrace(start, code) {
- this.braceDepth++;
- this.advance();
- return newCharacterToken(start, this.index, code);
- }
- scanCloseBrace(start, code) {
- this.advance();
- if (this.braceDepth === 0 && this.literalInterpolationDepth > 0) {
- this.literalInterpolationDepth--;
- this.tokens.push(newOperatorToken(start, this.index, '}'));
- return this.scanTemplateLiteralPart(this.index);
- }
- this.braceDepth--;
- return newCharacterToken(start, this.index, code);
- }
- /**
- * Tokenize a 2/3 char long operator
- *
- * @param start start index in the expression
- * @param one first symbol (always part of the operator)
- * @param twoCode code point for the second symbol
- * @param two second symbol (part of the operator when the second code point matches)
- * @param threeCode code point for the third symbol
- * @param three third symbol (part of the operator when provided and matches source expression)
- */
- scanComplexOperator(start, one, twoCode, two, threeCode, three) {
- this.advance();
- let str = one;
- if (this.peek == twoCode) {
- this.advance();
- str += two;
- }
- if (threeCode != null && this.peek == threeCode) {
- this.advance();
- str += three;
- }
- return newOperatorToken(start, this.index, str);
- }
- scanIdentifier() {
- const start = this.index;
- this.advance();
- while (isIdentifierPart(this.peek))
- this.advance();
- const str = this.input.substring(start, this.index);
- return KEYWORDS.indexOf(str) > -1
- ? newKeywordToken(start, this.index, str)
- : newIdentifierToken(start, this.index, str);
- }
- /** Scans an ECMAScript private identifier. */
- scanPrivateIdentifier() {
- const start = this.index;
- this.advance();
- if (!isIdentifierStart(this.peek)) {
- return this.error('Invalid character [#]', -1);
- }
- while (isIdentifierPart(this.peek))
- this.advance();
- const identifierName = this.input.substring(start, this.index);
- return newPrivateIdentifierToken(start, this.index, identifierName);
- }
- scanNumber(start) {
- let simple = this.index === start;
- let hasSeparators = false;
- this.advance(); // Skip initial digit.
- while (true) {
- if (isDigit(this.peek)) ;
- else if (this.peek === $_) {
- // Separators are only valid when they're surrounded by digits. E.g. `1_0_1` is
- // valid while `_101` and `101_` are not. The separator can't be next to the decimal
- // point or another separator either. Note that it's unlikely that we'll hit a case where
- // the underscore is at the start, because that's a valid identifier and it will be picked
- // up earlier in the parsing. We validate for it anyway just in case.
- if (!isDigit(this.input.charCodeAt(this.index - 1)) ||
- !isDigit(this.input.charCodeAt(this.index + 1))) {
- return this.error('Invalid numeric separator', 0);
- }
- hasSeparators = true;
- }
- else if (this.peek === $PERIOD) {
- simple = false;
- }
- else if (isExponentStart(this.peek)) {
- this.advance();
- if (isExponentSign(this.peek))
- this.advance();
- if (!isDigit(this.peek))
- return this.error('Invalid exponent', -1);
- simple = false;
- }
- else {
- break;
- }
- this.advance();
- }
- let str = this.input.substring(start, this.index);
- if (hasSeparators) {
- str = str.replace(/_/g, '');
- }
- const value = simple ? parseIntAutoRadix(str) : parseFloat(str);
- return newNumberToken(start, this.index, value);
- }
- scanString() {
- const start = this.index;
- const quote = this.peek;
- this.advance(); // Skip initial quote.
- let buffer = '';
- let marker = this.index;
- const input = this.input;
- while (this.peek != quote) {
- if (this.peek == $BACKSLASH) {
- const result = this.scanStringBackslash(buffer, marker);
- if (typeof result !== 'string') {
- return result; // Error
- }
- buffer = result;
- marker = this.index;
- }
- else if (this.peek == $EOF) {
- return this.error('Unterminated quote', 0);
- }
- else {
- this.advance();
- }
- }
- const last = input.substring(marker, this.index);
- this.advance(); // Skip terminating quote.
- return new StringToken(start, this.index, buffer + last, StringTokenKind.Plain);
- }
- scanQuestion(start) {
- this.advance();
- let str = '?';
- // Either `a ?? b` or 'a?.b'.
- if (this.peek === $QUESTION || this.peek === $PERIOD) {
- str += this.peek === $PERIOD ? '.' : '?';
- this.advance();
- }
- return newOperatorToken(start, this.index, str);
- }
- scanTemplateLiteralPart(start) {
- let buffer = '';
- let marker = this.index;
- while (this.peek !== $BT) {
- if (this.peek === $BACKSLASH) {
- const result = this.scanStringBackslash(buffer, marker);
- if (typeof result !== 'string') {
- return result; // Error
- }
- buffer = result;
- marker = this.index;
- }
- else if (this.peek === $$) {
- const dollar = this.index;
- this.advance();
- // @ts-expect-error
- if (this.peek === $LBRACE) {
- this.literalInterpolationDepth++;
- this.tokens.push(new StringToken(start, dollar, buffer + this.input.substring(marker, dollar), StringTokenKind.TemplateLiteralPart));
- this.advance();
- return newOperatorToken(dollar, this.index, this.input.substring(dollar, this.index));
- }
- }
- else if (this.peek === $EOF) {
- return this.error('Unterminated template literal', 0);
- }
- else {
- this.advance();
- }
- }
- const last = this.input.substring(marker, this.index);
- this.advance();
- return new StringToken(start, this.index, buffer + last, StringTokenKind.TemplateLiteralEnd);
- }
- error(message, offset) {
- const position = this.index + offset;
- return newErrorToken(position, this.index, `Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
- }
- scanStringBackslash(buffer, marker) {
- buffer += this.input.substring(marker, this.index);
- let unescapedCode;
- this.advance();
- if (this.peek === $u) {
- // 4 character hex code for unicode character.
- const hex = this.input.substring(this.index + 1, this.index + 5);
- if (/^[0-9a-f]+$/i.test(hex)) {
- unescapedCode = parseInt(hex, 16);
- }
- else {
- return this.error(`Invalid unicode escape [\\u${hex}]`, 0);
- }
- for (let i = 0; i < 5; i++) {
- this.advance();
- }
- }
- else {
- unescapedCode = unescape$1(this.peek);
- this.advance();
- }
- buffer += String.fromCharCode(unescapedCode);
- return buffer;
- }
- }
- function isIdentifierStart(code) {
- return (($a <= code && code <= $z) ||
- ($A <= code && code <= $Z) ||
- code == $_ ||
- code == $$);
- }
- function isIdentifierPart(code) {
- return isAsciiLetter(code) || isDigit(code) || code == $_ || code == $$;
- }
- function isExponentStart(code) {
- return code == $e || code == $E;
- }
- function isExponentSign(code) {
- return code == $MINUS || code == $PLUS;
- }
- function unescape$1(code) {
- switch (code) {
- case $n:
- return $LF;
- case $f:
- return $FF;
- case $r:
- return $CR;
- case $t:
- return $TAB;
- case $v:
- return $VTAB;
- default:
- return code;
- }
- }
- function parseIntAutoRadix(text) {
- const result = parseInt(text);
- if (isNaN(result)) {
- throw new Error('Invalid integer literal when parsing ' + text);
- }
- return result;
- }
- class SplitInterpolation {
- strings;
- expressions;
- offsets;
- constructor(strings, expressions, offsets) {
- this.strings = strings;
- this.expressions = expressions;
- this.offsets = offsets;
- }
- }
- class TemplateBindingParseResult {
- templateBindings;
- warnings;
- errors;
- constructor(templateBindings, warnings, errors) {
- this.templateBindings = templateBindings;
- this.warnings = warnings;
- this.errors = errors;
- }
- }
- class Parser {
- _lexer;
- errors = [];
- constructor(_lexer) {
- this._lexer = _lexer;
- }
- parseAction(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
- this._checkNoInterpolation(input, location, interpolationConfig);
- const sourceToLex = this._stripComments(input);
- const tokens = this._lexer.tokenize(sourceToLex);
- const ast = new _ParseAST(input, location, absoluteOffset, tokens, 1 /* ParseFlags.Action */, this.errors, 0).parseChain();
- return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
- }
- parseBinding(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
- const ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig);
- return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
- }
- checkSimpleExpression(ast) {
- const checker = new SimpleExpressionChecker();
- ast.visit(checker);
- return checker.errors;
- }
- // Host bindings parsed here
- parseSimpleBinding(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
- const ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig);
- const errors = this.checkSimpleExpression(ast);
- if (errors.length > 0) {
- this._reportError(`Host binding expression cannot contain ${errors.join(' ')}`, input, location);
- }
- return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
- }
- _reportError(message, input, errLocation, ctxLocation) {
- this.errors.push(new ParserError(message, input, errLocation, ctxLocation));
- }
- _parseBindingAst(input, location, absoluteOffset, interpolationConfig) {
- this._checkNoInterpolation(input, location, interpolationConfig);
- const sourceToLex = this._stripComments(input);
- const tokens = this._lexer.tokenize(sourceToLex);
- return new _ParseAST(input, location, absoluteOffset, tokens, 0 /* ParseFlags.None */, this.errors, 0).parseChain();
- }
- /**
- * Parse microsyntax template expression and return a list of bindings or
- * parsing errors in case the given expression is invalid.
- *
- * For example,
- * ```html
- * <div *ngFor="let item of items">
- * ^ ^ absoluteValueOffset for `templateValue`
- * absoluteKeyOffset for `templateKey`
- * ```
- * contains three bindings:
- * 1. ngFor -> null
- * 2. item -> NgForOfContext.$implicit
- * 3. ngForOf -> items
- *
- * This is apparent from the de-sugared template:
- * ```html
- * <ng-template ngFor let-item [ngForOf]="items">
- * ```
- *
- * @param templateKey name of directive, without the * prefix. For example: ngIf, ngFor
- * @param templateValue RHS of the microsyntax attribute
- * @param templateUrl template filename if it's external, component filename if it's inline
- * @param absoluteKeyOffset start of the `templateKey`
- * @param absoluteValueOffset start of the `templateValue`
- */
- parseTemplateBindings(templateKey, templateValue, templateUrl, absoluteKeyOffset, absoluteValueOffset) {
- const tokens = this._lexer.tokenize(templateValue);
- const parser = new _ParseAST(templateValue, templateUrl, absoluteValueOffset, tokens, 0 /* ParseFlags.None */, this.errors, 0 /* relative offset */);
- return parser.parseTemplateBindings({
- source: templateKey,
- span: new AbsoluteSourceSpan(absoluteKeyOffset, absoluteKeyOffset + templateKey.length),
- });
- }
- parseInterpolation(input, location, absoluteOffset, interpolatedTokens, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
- const { strings, expressions, offsets } = this.splitInterpolation(input, location, interpolatedTokens, interpolationConfig);
- if (expressions.length === 0)
- return null;
- const expressionNodes = [];
- for (let i = 0; i < expressions.length; ++i) {
- const expressionText = expressions[i].text;
- const sourceToLex = this._stripComments(expressionText);
- const tokens = this._lexer.tokenize(sourceToLex);
- const ast = new _ParseAST(input, location, absoluteOffset, tokens, 0 /* ParseFlags.None */, this.errors, offsets[i]).parseChain();
- expressionNodes.push(ast);
- }
- return this.createInterpolationAst(strings.map((s) => s.text), expressionNodes, input, location, absoluteOffset);
- }
- /**
- * Similar to `parseInterpolation`, but treats the provided string as a single expression
- * element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
- * This is used for parsing the switch expression in ICUs.
- */
- parseInterpolationExpression(expression, location, absoluteOffset) {
- const sourceToLex = this._stripComments(expression);
- const tokens = this._lexer.tokenize(sourceToLex);
- const ast = new _ParseAST(expression, location, absoluteOffset, tokens, 0 /* ParseFlags.None */, this.errors, 0).parseChain();
- const strings = ['', '']; // The prefix and suffix strings are both empty
- return this.createInterpolationAst(strings, [ast], expression, location, absoluteOffset);
- }
- createInterpolationAst(strings, expressions, input, location, absoluteOffset) {
- const span = new ParseSpan(0, input.length);
- const interpolation = new Interpolation$1(span, span.toAbsolute(absoluteOffset), strings, expressions);
- return new ASTWithSource(interpolation, input, location, absoluteOffset, this.errors);
- }
- /**
- * Splits a string of text into "raw" text segments and expressions present in interpolations in
- * the string.
- * Returns `null` if there are no interpolations, otherwise a
- * `SplitInterpolation` with splits that look like
- * <raw text> <expression> <raw text> ... <raw text> <expression> <raw text>
- */
- splitInterpolation(input, location, interpolatedTokens, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
- const strings = [];
- const expressions = [];
- const offsets = [];
- const inputToTemplateIndexMap = interpolatedTokens
- ? getIndexMapForOriginalTemplate(interpolatedTokens)
- : null;
- let i = 0;
- let atInterpolation = false;
- let extendLastString = false;
- let { start: interpStart, end: interpEnd } = interpolationConfig;
- while (i < input.length) {
- if (!atInterpolation) {
- // parse until starting {{
- const start = i;
- i = input.indexOf(interpStart, i);
- if (i === -1) {
- i = input.length;
- }
- const text = input.substring(start, i);
- strings.push({ text, start, end: i });
- atInterpolation = true;
- }
- else {
- // parse from starting {{ to ending }} while ignoring content inside quotes.
- const fullStart = i;
- const exprStart = fullStart + interpStart.length;
- const exprEnd = this._getInterpolationEndIndex(input, interpEnd, exprStart);
- if (exprEnd === -1) {
- // Could not find the end of the interpolation; do not parse an expression.
- // Instead we should extend the content on the last raw string.
- atInterpolation = false;
- extendLastString = true;
- break;
- }
- const fullEnd = exprEnd + interpEnd.length;
- const text = input.substring(exprStart, exprEnd);
- if (text.trim().length === 0) {
- this._reportError('Blank expressions are not allowed in interpolated strings', input, `at column ${i} in`, location);
- }
- expressions.push({ text, start: fullStart, end: fullEnd });
- const startInOriginalTemplate = inputToTemplateIndexMap?.get(fullStart) ?? fullStart;
- const offset = startInOriginalTemplate + interpStart.length;
- offsets.push(offset);
- i = fullEnd;
- atInterpolation = false;
- }
- }
- if (!atInterpolation) {
- // If we are now at a text section, add the remaining content as a raw string.
- if (extendLastString) {
- const piece = strings[strings.length - 1];
- piece.text += input.substring(i);
- piece.end = input.length;
- }
- else {
- strings.push({ text: input.substring(i), start: i, end: input.length });
- }
- }
- return new SplitInterpolation(strings, expressions, offsets);
- }
- wrapLiteralPrimitive(input, location, absoluteOffset) {
- const span = new ParseSpan(0, input == null ? 0 : input.length);
- return new ASTWithSource(new LiteralPrimitive(span, span.toAbsolute(absoluteOffset), input), input, location, absoluteOffset, this.errors);
- }
- _stripComments(input) {
- const i = this._commentStart(input);
- return i != null ? input.substring(0, i) : input;
- }
- _commentStart(input) {
- let outerQuote = null;
- for (let i = 0; i < input.length - 1; i++) {
- const char = input.charCodeAt(i);
- const nextChar = input.charCodeAt(i + 1);
- if (char === $SLASH && nextChar == $SLASH && outerQuote == null)
- return i;
- if (outerQuote === char) {
- outerQuote = null;
- }
- else if (outerQuote == null && isQuote(char)) {
- outerQuote = char;
- }
- }
- return null;
- }
- _checkNoInterpolation(input, location, { start, end }) {
- let startIndex = -1;
- let endIndex = -1;
- for (const charIndex of this._forEachUnquotedChar(input, 0)) {
- if (startIndex === -1) {
- if (input.startsWith(start)) {
- startIndex = charIndex;
- }
- }
- else {
- endIndex = this._getInterpolationEndIndex(input, end, charIndex);
- if (endIndex > -1) {
- break;
- }
- }
- }
- if (startIndex > -1 && endIndex > -1) {
- this._reportError(`Got interpolation (${start}${end}) where expression was expected`, input, `at column ${startIndex} in`, location);
- }
- }
- /**
- * Finds the index of the end of an interpolation expression
- * while ignoring comments and quoted content.
- */
- _getInterpolationEndIndex(input, expressionEnd, start) {
- for (const charIndex of this._forEachUnquotedChar(input, start)) {
- if (input.startsWith(expressionEnd, charIndex)) {
- return charIndex;
- }
- // Nothing else in the expression matters after we've
- // hit a comment so look directly for the end token.
- if (input.startsWith('//', charIndex)) {
- return input.indexOf(expressionEnd, charIndex);
- }
- }
- return -1;
- }
- /**
- * Generator used to iterate over the character indexes of a string that are outside of quotes.
- * @param input String to loop through.
- * @param start Index within the string at which to start.
- */
- *_forEachUnquotedChar(input, start) {
- let currentQuote = null;
- let escapeCount = 0;
- for (let i = start; i < input.length; i++) {
- const char = input[i];
- // Skip the characters inside quotes. Note that we only care about the outer-most
- // quotes matching up and we need to account for escape characters.
- if (isQuote(input.charCodeAt(i)) &&
- (currentQuote === null || currentQuote === char) &&
- escapeCount % 2 === 0) {
- currentQuote = currentQuote === null ? char : null;
- }
- else if (currentQuote === null) {
- yield i;
- }
- escapeCount = char === '\\' ? escapeCount + 1 : 0;
- }
- }
- }
- /** Describes a stateful context an expression parser is in. */
- var ParseContextFlags;
- (function (ParseContextFlags) {
- ParseContextFlags[ParseContextFlags["None"] = 0] = "None";
- /**
- * A Writable context is one in which a value may be written to an lvalue.
- * For example, after we see a property access, we may expect a write to the
- * property via the "=" operator.
- * prop
- * ^ possible "=" after
- */
- ParseContextFlags[ParseContextFlags["Writable"] = 1] = "Writable";
- })(ParseContextFlags || (ParseContextFlags = {}));
- class _ParseAST {
- input;
- location;
- absoluteOffset;
- tokens;
- parseFlags;
- errors;
- offset;
- rparensExpected = 0;
- rbracketsExpected = 0;
- rbracesExpected = 0;
- context = ParseContextFlags.None;
- // Cache of expression start and input indeces to the absolute source span they map to, used to
- // prevent creating superfluous source spans in `sourceSpan`.
- // A serial of the expression start and input index is used for mapping because both are stateful
- // and may change for subsequent expressions visited by the parser.
- sourceSpanCache = new Map();
- index = 0;
- constructor(input, location, absoluteOffset, tokens, parseFlags, errors, offset) {
- this.input = input;
- this.location = location;
- this.absoluteOffset = absoluteOffset;
- this.tokens = tokens;
- this.parseFlags = parseFlags;
- this.errors = errors;
- this.offset = offset;
- }
- peek(offset) {
- const i = this.index + offset;
- return i < this.tokens.length ? this.tokens[i] : EOF;
- }
- get next() {
- return this.peek(0);
- }
- /** Whether all the parser input has been processed. */
- get atEOF() {
- return this.index >= this.tokens.length;
- }
- /**
- * Index of the next token to be processed, or the end of the last token if all have been
- * processed.
- */
- get inputIndex() {
- return this.atEOF ? this.currentEndIndex : this.next.index + this.offset;
- }
- /**
- * End index of the last processed token, or the start of the first token if none have been
- * processed.
- */
- get currentEndIndex() {
- if (this.index > 0) {
- const curToken = this.peek(-1);
- return curToken.end + this.offset;
- }
- // No tokens have been processed yet; return the next token's start or the length of the input
- // if there is no token.
- if (this.tokens.length === 0) {
- return this.input.length + this.offset;
- }
- return this.next.index + this.offset;
- }
- /**
- * Returns the absolute offset of the start of the current token.
- */
- get currentAbsoluteOffset() {
- return this.absoluteOffset + this.inputIndex;
- }
- /**
- * Retrieve a `ParseSpan` from `start` to the current position (or to `artificialEndIndex` if
- * provided).
- *
- * @param start Position from which the `ParseSpan` will start.
- * @param artificialEndIndex Optional ending index to be used if provided (and if greater than the
- * natural ending index)
- */
- span(start, artificialEndIndex) {
- let endIndex = this.currentEndIndex;
- if (artificialEndIndex !== undefined && artificialEndIndex > this.currentEndIndex) {
- endIndex = artificialEndIndex;
- }
- // In some unusual parsing scenarios (like when certain tokens are missing and an `EmptyExpr` is
- // being created), the current token may already be advanced beyond the `currentEndIndex`. This
- // appears to be a deep-seated parser bug.
- //
- // As a workaround for now, swap the start and end indices to ensure a valid `ParseSpan`.
- // TODO(alxhub): fix the bug upstream in the parser state, and remove this workaround.
- if (start > endIndex) {
- const tmp = endIndex;
- endIndex = start;
- start = tmp;
- }
- return new ParseSpan(start, endIndex);
- }
- sourceSpan(start, artificialEndIndex) {
- const serial = `${start}@${this.inputIndex}:${artificialEndIndex}`;
- if (!this.sourceSpanCache.has(serial)) {
- this.sourceSpanCache.set(serial, this.span(start, artificialEndIndex).toAbsolute(this.absoluteOffset));
- }
- return this.sourceSpanCache.get(serial);
- }
- advance() {
- this.index++;
- }
- /**
- * Executes a callback in the provided context.
- */
- withContext(context, cb) {
- this.context |= context;
- const ret = cb();
- this.context ^= context;
- return ret;
- }
- consumeOptionalCharacter(code) {
- if (this.next.isCharacter(code)) {
- this.advance();
- return true;
- }
- else {
- return false;
- }
- }
- peekKeywordLet() {
- return this.next.isKeywordLet();
- }
- peekKeywordAs() {
- return this.next.isKeywordAs();
- }
- /**
- * Consumes an expected character, otherwise emits an error about the missing expected character
- * and skips over the token stream until reaching a recoverable point.
- *
- * See `this.error` and `this.skip` for more details.
- */
- expectCharacter(code) {
- if (this.consumeOptionalCharacter(code))
- return;
- this.error(`Missing expected ${String.fromCharCode(code)}`);
- }
- consumeOptionalOperator(op) {
- if (this.next.isOperator(op)) {
- this.advance();
- return true;
- }
- else {
- return false;
- }
- }
- expectOperator(operator) {
- if (this.consumeOptionalOperator(operator))
- return;
- this.error(`Missing expected operator ${operator}`);
- }
- prettyPrintToken(tok) {
- return tok === EOF ? 'end of input' : `token ${tok}`;
- }
- expectIdentifierOrKeyword() {
- const n = this.next;
- if (!n.isIdentifier() && !n.isKeyword()) {
- if (n.isPrivateIdentifier()) {
- this._reportErrorForPrivateIdentifier(n, 'expected identifier or keyword');
- }
- else {
- this.error(`Unexpected ${this.prettyPrintToken(n)}, expected identifier or keyword`);
- }
- return null;
- }
- this.advance();
- return n.toString();
- }
- expectIdentifierOrKeywordOrString() {
- const n = this.next;
- if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
- if (n.isPrivateIdentifier()) {
- this._reportErrorForPrivateIdentifier(n, 'expected identifier, keyword or string');
- }
- else {
- this.error(`Unexpected ${this.prettyPrintToken(n)}, expected identifier, keyword, or string`);
- }
- return '';
- }
- this.advance();
- return n.toString();
- }
- parseChain() {
- const exprs = [];
- const start = this.inputIndex;
- while (this.index < this.tokens.length) {
- const expr = this.parsePipe();
- exprs.push(expr);
- if (this.consumeOptionalCharacter($SEMICOLON)) {
- if (!(this.parseFlags & 1 /* ParseFlags.Action */)) {
- this.error('Binding expression cannot contain chained expression');
- }
- while (this.consumeOptionalCharacter($SEMICOLON)) { } // read all semicolons
- }
- else if (this.index < this.tokens.length) {
- const errorIndex = this.index;
- this.error(`Unexpected token '${this.next}'`);
- // The `error` call above will skip ahead to the next recovery point in an attempt to
- // recover part of the expression, but that might be the token we started from which will
- // lead to an infinite loop. If that's the case, break the loop assuming that we can't
- // parse further.
- if (this.index === errorIndex) {
- break;
- }
- }
- }
- if (exprs.length === 0) {
- // We have no expressions so create an empty expression that spans the entire input length
- const artificialStart = this.offset;
- const artificialEnd = this.offset + this.input.length;
- return new EmptyExpr$1(this.span(artificialStart, artificialEnd), this.sourceSpan(artificialStart, artificialEnd));
- }
- if (exprs.length == 1)
- return exprs[0];
- return new Chain(this.span(start), this.sourceSpan(start), exprs);
- }
- parsePipe() {
- const start = this.inputIndex;
- let result = this.parseExpression();
- if (this.consumeOptionalOperator('|')) {
- if (this.parseFlags & 1 /* ParseFlags.Action */) {
- this.error(`Cannot have a pipe in an action expression`);
- }
- do {
- const nameStart = this.inputIndex;
- let nameId = this.expectIdentifierOrKeyword();
- let nameSpan;
- let fullSpanEnd = undefined;
- if (nameId !== null) {
- nameSpan = this.sourceSpan(nameStart);
- }
- else {
- // No valid identifier was found, so we'll assume an empty pipe name ('').
- nameId = '';
- // However, there may have been whitespace present between the pipe character and the next
- // token in the sequence (or the end of input). We want to track this whitespace so that
- // the `BindingPipe` we produce covers not just the pipe character, but any trailing
- // whitespace beyond it. Another way of thinking about this is that the zero-length name
- // is assumed to be at the end of any whitespace beyond the pipe character.
- //
- // Therefore, we push the end of the `ParseSpan` for this pipe all the way up to the
- // beginning of the next token, or until the end of input if the next token is EOF.
- fullSpanEnd = this.next.index !== -1 ? this.next.index : this.input.length + this.offset;
- // The `nameSpan` for an empty pipe name is zero-length at the end of any whitespace
- // beyond the pipe character.
- nameSpan = new ParseSpan(fullSpanEnd, fullSpanEnd).toAbsolute(this.absoluteOffset);
- }
- const args = [];
- while (this.consumeOptionalCharacter($COLON)) {
- args.push(this.parseExpression());
- // If there are additional expressions beyond the name, then the artificial end for the
- // name is no longer relevant.
- }
- result = new BindingPipe(this.span(start), this.sourceSpan(start, fullSpanEnd), result, nameId, args, nameSpan);
- } while (this.consumeOptionalOperator('|'));
- }
- return result;
- }
- parseExpression() {
- return this.parseConditional();
- }
- parseConditional() {
- const start = this.inputIndex;
- const result = this.parseLogicalOr();
- if (this.consumeOptionalOperator('?')) {
- const yes = this.parsePipe();
- let no;
- if (!this.consumeOptionalCharacter($COLON)) {
- const end = this.inputIndex;
- const expression = this.input.substring(start, end);
- this.error(`Conditional expression ${expression} requires all 3 expressions`);
- no = new EmptyExpr$1(this.span(start), this.sourceSpan(start));
- }
- else {
- no = this.parsePipe();
- }
- return new Conditional(this.span(start), this.sourceSpan(start), result, yes, no);
- }
- else {
- return result;
- }
- }
- parseLogicalOr() {
- // '||'
- const start = this.inputIndex;
- let result = this.parseLogicalAnd();
- while (this.consumeOptionalOperator('||')) {
- const right = this.parseLogicalAnd();
- result = new Binary(this.span(start), this.sourceSpan(start), '||', result, right);
- }
- return result;
- }
- parseLogicalAnd() {
- // '&&'
- const start = this.inputIndex;
- let result = this.parseNullishCoalescing();
- while (this.consumeOptionalOperator('&&')) {
- const right = this.parseNullishCoalescing();
- result = new Binary(this.span(start), this.sourceSpan(start), '&&', result, right);
- }
- return result;
- }
- parseNullishCoalescing() {
- // '??'
- const start = this.inputIndex;
- let result = this.parseEquality();
- while (this.consumeOptionalOperator('??')) {
- const right = this.parseEquality();
- result = new Binary(this.span(start), this.sourceSpan(start), '??', result, right);
- }
- return result;
- }
- parseEquality() {
- // '==','!=','===','!=='
- const start = this.inputIndex;
- let result = this.parseRelational();
- while (this.next.type == TokenType.Operator) {
- const operator = this.next.strValue;
- switch (operator) {
- case '==':
- case '===':
- case '!=':
- case '!==':
- this.advance();
- const right = this.parseRelational();
- result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
- continue;
- }
- break;
- }
- return result;
- }
- parseRelational() {
- // '<', '>', '<=', '>='
- const start = this.inputIndex;
- let result = this.parseAdditive();
- while (this.next.type == TokenType.Operator) {
- const operator = this.next.strValue;
- switch (operator) {
- case '<':
- case '>':
- case '<=':
- case '>=':
- this.advance();
- const right = this.parseAdditive();
- result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
- continue;
- }
- break;
- }
- return result;
- }
- parseAdditive() {
- // '+', '-'
- const start = this.inputIndex;
- let result = this.parseMultiplicative();
- while (this.next.type == TokenType.Operator) {
- const operator = this.next.strValue;
- switch (operator) {
- case '+':
- case '-':
- this.advance();
- let right = this.parseMultiplicative();
- result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
- continue;
- }
- break;
- }
- return result;
- }
- parseMultiplicative() {
- // '*', '%', '/'
- const start = this.inputIndex;
- let result = this.parsePrefix();
- while (this.next.type == TokenType.Operator) {
- const operator = this.next.strValue;
- switch (operator) {
- case '*':
- case '%':
- case '/':
- this.advance();
- let right = this.parsePrefix();
- result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
- continue;
- }
- break;
- }
- return result;
- }
- parsePrefix() {
- if (this.next.type == TokenType.Operator) {
- const start = this.inputIndex;
- const operator = this.next.strValue;
- let result;
- switch (operator) {
- case '+':
- this.advance();
- result = this.parsePrefix();
- return Unary.createPlus(this.span(start), this.sourceSpan(start), result);
- case '-':
- this.advance();
- result = this.parsePrefix();
- return Unary.createMinus(this.span(start), this.sourceSpan(start), result);
- case '!':
- this.advance();
- result = this.parsePrefix();
- return new PrefixNot(this.span(start), this.sourceSpan(start), result);
- }
- }
- else if (this.next.isKeywordTypeof()) {
- this.advance();
- const start = this.inputIndex;
- let result = this.parsePrefix();
- return new TypeofExpression(this.span(start), this.sourceSpan(start), result);
- }
- return this.parseCallChain();
- }
- parseCallChain() {
- const start = this.inputIndex;
- let result = this.parsePrimary();
- while (true) {
- if (this.consumeOptionalCharacter($PERIOD)) {
- result = this.parseAccessMember(result, start, false);
- }
- else if (this.consumeOptionalOperator('?.')) {
- if (this.consumeOptionalCharacter($LPAREN)) {
- result = this.parseCall(result, start, true);
- }
- else {
- result = this.consumeOptionalCharacter($LBRACKET)
- ? this.parseKeyedReadOrWrite(result, start, true)
- : this.parseAccessMember(result, start, true);
- }
- }
- else if (this.consumeOptionalCharacter($LBRACKET)) {
- result = this.parseKeyedReadOrWrite(result, start, false);
- }
- else if (this.consumeOptionalCharacter($LPAREN)) {
- result = this.parseCall(result, start, false);
- }
- else if (this.consumeOptionalOperator('!')) {
- result = new NonNullAssert(this.span(start), this.sourceSpan(start), result);
- }
- else {
- return result;
- }
- }
- }
- parsePrimary() {
- const start = this.inputIndex;
- if (this.consumeOptionalCharacter($LPAREN)) {
- this.rparensExpected++;
- const result = this.parsePipe();
- this.rparensExpected--;
- this.expectCharacter($RPAREN);
- return result;
- }
- else if (this.next.isKeywordNull()) {
- this.advance();
- return new LiteralPrimitive(this.span(start), this.sourceSpan(start), null);
- }
- else if (this.next.isKeywordUndefined()) {
- this.advance();
- return new LiteralPrimitive(this.span(start), this.sourceSpan(start), void 0);
- }
- else if (this.next.isKeywordTrue()) {
- this.advance();
- return new LiteralPrimitive(this.span(start), this.sourceSpan(start), true);
- }
- else if (this.next.isKeywordFalse()) {
- this.advance();
- return new LiteralPrimitive(this.span(start), this.sourceSpan(start), false);
- }
- else if (this.next.isKeywordThis()) {
- this.advance();
- return new ThisReceiver(this.span(start), this.sourceSpan(start));
- }
- else if (this.consumeOptionalCharacter($LBRACKET)) {
- this.rbracketsExpected++;
- const elements = this.parseExpressionList($RBRACKET);
- this.rbracketsExpected--;
- this.expectCharacter($RBRACKET);
- return new LiteralArray(this.span(start), this.sourceSpan(start), elements);
- }
- else if (this.next.isCharacter($LBRACE)) {
- return this.parseLiteralMap();
- }
- else if (this.next.isIdentifier()) {
- return this.parseAccessMember(new ImplicitReceiver(this.span(start), this.sourceSpan(start)), start, false);
- }
- else if (this.next.isNumber()) {
- const value = this.next.toNumber();
- this.advance();
- return new LiteralPrimitive(this.span(start), this.sourceSpan(start), value);
- }
- else if (this.next.isTemplateLiteralEnd()) {
- return this.parseNoInterpolationTemplateLiteral();
- }
- else if (this.next.isTemplateLiteralPart()) {
- return this.parseTemplateLiteral();
- }
- else if (this.next.isString() && this.next.kind === StringTokenKind.Plain) {
- const literalValue = this.next.toString();
- this.advance();
- return new LiteralPrimitive(this.span(start), this.sourceSpan(start), literalValue);
- }
- else if (this.next.isPrivateIdentifier()) {
- this._reportErrorForPrivateIdentifier(this.next, null);
- return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
- }
- else if (this.index >= this.tokens.length) {
- this.error(`Unexpected end of expression: ${this.input}`);
- return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
- }
- else {
- this.error(`Unexpected token ${this.next}`);
- return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
- }
- }
- parseExpressionList(terminator) {
- const result = [];
- do {
- if (!this.next.isCharacter(terminator)) {
- result.push(this.parsePipe());
- }
- else {
- break;
- }
- } while (this.consumeOptionalCharacter($COMMA));
- return result;
- }
- parseLiteralMap() {
- const keys = [];
- const values = [];
- const start = this.inputIndex;
- this.expectCharacter($LBRACE);
- if (!this.consumeOptionalCharacter($RBRACE)) {
- this.rbracesExpected++;
- do {
- const keyStart = this.inputIndex;
- const quoted = this.next.isString();
- const key = this.expectIdentifierOrKeywordOrString();
- const literalMapKey = { key, quoted };
- keys.push(literalMapKey);
- // Properties with quoted keys can't use the shorthand syntax.
- if (quoted) {
- this.expectCharacter($COLON);
- values.push(this.parsePipe());
- }
- else if (this.consumeOptionalCharacter($COLON)) {
- values.push(this.parsePipe());
- }
- else {
- literalMapKey.isShorthandInitialized = true;
- const span = this.span(keyStart);
- const sourceSpan = this.sourceSpan(keyStart);
- values.push(new PropertyRead(span, sourceSpan, sourceSpan, new ImplicitReceiver(span, sourceSpan), key));
- }
- } while (this.consumeOptionalCharacter($COMMA) &&
- !this.next.isCharacter($RBRACE));
- this.rbracesExpected--;
- this.expectCharacter($RBRACE);
- }
- return new LiteralMap(this.span(start), this.sourceSpan(start), keys, values);
- }
- parseAccessMember(readReceiver, start, isSafe) {
- const nameStart = this.inputIndex;
- const id = this.withContext(ParseContextFlags.Writable, () => {
- const id = this.expectIdentifierOrKeyword() ?? '';
- if (id.length === 0) {
- this.error(`Expected identifier for property access`, readReceiver.span.end);
- }
- return id;
- });
- const nameSpan = this.sourceSpan(nameStart);
- let receiver;
- if (isSafe) {
- if (this.consumeOptionalOperator('=')) {
- this.error("The '?.' operator cannot be used in the assignment");
- receiver = new EmptyExpr$1(this.span(start), this.sourceSpan(start));
- }
- else {
- receiver = new SafePropertyRead(this.span(start), this.sourceSpan(start), nameSpan, readReceiver, id);
- }
- }
- else {
- if (this.consumeOptionalOperator('=')) {
- if (!(this.parseFlags & 1 /* ParseFlags.Action */)) {
- this.error('Bindings cannot contain assignments');
- return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
- }
- const value = this.parseConditional();
- receiver = new PropertyWrite(this.span(start), this.sourceSpan(start), nameSpan, readReceiver, id, value);
- }
- else {
- receiver = new PropertyRead(this.span(start), this.sourceSpan(start), nameSpan, readReceiver, id);
- }
- }
- return receiver;
- }
- parseCall(receiver, start, isSafe) {
- const argumentStart = this.inputIndex;
- this.rparensExpected++;
- const args = this.parseCallArguments();
- const argumentSpan = this.span(argumentStart, this.inputIndex).toAbsolute(this.absoluteOffset);
- this.expectCharacter($RPAREN);
- this.rparensExpected--;
- const span = this.span(start);
- const sourceSpan = this.sourceSpan(start);
- return isSafe
- ? new SafeCall(span, sourceSpan, receiver, args, argumentSpan)
- : new Call(span, sourceSpan, receiver, args, argumentSpan);
- }
- parseCallArguments() {
- if (this.next.isCharacter($RPAREN))
- return [];
- const positionals = [];
- do {
- positionals.push(this.parsePipe());
- } while (this.consumeOptionalCharacter($COMMA));
- return positionals;
- }
- /**
- * Parses an identifier, a keyword, a string with an optional `-` in between,
- * and returns the string along with its absolute source span.
- */
- expectTemplateBindingKey() {
- let result = '';
- let operatorFound = false;
- const start = this.currentAbsoluteOffset;
- do {
- result += this.expectIdentifierOrKeywordOrString();
- operatorFound = this.consumeOptionalOperator('-');
- if (operatorFound) {
- result += '-';
- }
- } while (operatorFound);
- return {
- source: result,
- span: new AbsoluteSourceSpan(start, start + result.length),
- };
- }
- /**
- * Parse microsyntax template expression and return a list of bindings or
- * parsing errors in case the given expression is invalid.
- *
- * For example,
- * ```html
- * <div *ngFor="let item of items; index as i; trackBy: func">
- * ```
- * contains five bindings:
- * 1. ngFor -> null
- * 2. item -> NgForOfContext.$implicit
- * 3. ngForOf -> items
- * 4. i -> NgForOfContext.index
- * 5. ngForTrackBy -> func
- *
- * For a full description of the microsyntax grammar, see
- * https://gist.github.com/mhevery/d3530294cff2e4a1b3fe15ff75d08855
- *
- * @param templateKey name of the microsyntax directive, like ngIf, ngFor,
- * without the *, along with its absolute span.
- */
- parseTemplateBindings(templateKey) {
- const bindings = [];
- // The first binding is for the template key itself
- // In *ngFor="let item of items", key = "ngFor", value = null
- // In *ngIf="cond | pipe", key = "ngIf", value = "cond | pipe"
- bindings.push(...this.parseDirectiveKeywordBindings(templateKey));
- while (this.index < this.tokens.length) {
- // If it starts with 'let', then this must be variable declaration
- const letBinding = this.parseLetBinding();
- if (letBinding) {
- bindings.push(letBinding);
- }
- else {
- // Two possible cases here, either `value "as" key` or
- // "directive-keyword expression". We don't know which case, but both
- // "value" and "directive-keyword" are template binding key, so consume
- // the key first.
- const key = this.expectTemplateBindingKey();
- // Peek at the next token, if it is "as" then this must be variable
- // declaration.
- const binding = this.parseAsBinding(key);
- if (binding) {
- bindings.push(binding);
- }
- else {
- // Otherwise the key must be a directive keyword, like "of". Transform
- // the key to actual key. Eg. of -> ngForOf, trackBy -> ngForTrackBy
- key.source =
- templateKey.source + key.source.charAt(0).toUpperCase() + key.source.substring(1);
- bindings.push(...this.parseDirectiveKeywordBindings(key));
- }
- }
- this.consumeStatementTerminator();
- }
- return new TemplateBindingParseResult(bindings, [] /* warnings */, this.errors);
- }
- parseKeyedReadOrWrite(receiver, start, isSafe) {
- return this.withContext(ParseContextFlags.Writable, () => {
- this.rbracketsExpected++;
- const key = this.parsePipe();
- if (key instanceof EmptyExpr$1) {
- this.error(`Key access cannot be empty`);
- }
- this.rbracketsExpected--;
- this.expectCharacter($RBRACKET);
- if (this.consumeOptionalOperator('=')) {
- if (isSafe) {
- this.error("The '?.' operator cannot be used in the assignment");
- }
- else {
- const value = this.parseConditional();
- return new KeyedWrite(this.span(start), this.sourceSpan(start), receiver, key, value);
- }
- }
- else {
- return isSafe
- ? new SafeKeyedRead(this.span(start), this.sourceSpan(start), receiver, key)
- : new KeyedRead(this.span(start), this.sourceSpan(start), receiver, key);
- }
- return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
- });
- }
- /**
- * Parse a directive keyword, followed by a mandatory expression.
- * For example, "of items", "trackBy: func".
- * The bindings are: ngForOf -> items, ngForTrackBy -> func
- * There could be an optional "as" binding that follows the expression.
- * For example,
- * ```
- * *ngFor="let item of items | slice:0:1 as collection".
- * ^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
- * keyword bound target optional 'as' binding
- * ```
- *
- * @param key binding key, for example, ngFor, ngIf, ngForOf, along with its
- * absolute span.
- */
- parseDirectiveKeywordBindings(key) {
- const bindings = [];
- this.consumeOptionalCharacter($COLON); // trackBy: trackByFunction
- const value = this.getDirectiveBoundTarget();
- let spanEnd = this.currentAbsoluteOffset;
- // The binding could optionally be followed by "as". For example,
- // *ngIf="cond | pipe as x". In this case, the key in the "as" binding
- // is "x" and the value is the template key itself ("ngIf"). Note that the
- // 'key' in the current context now becomes the "value" in the next binding.
- const asBinding = this.parseAsBinding(key);
- if (!asBinding) {
- this.consumeStatementTerminator();
- spanEnd = this.currentAbsoluteOffset;
- }
- const sourceSpan = new AbsoluteSourceSpan(key.span.start, spanEnd);
- bindings.push(new ExpressionBinding(sourceSpan, key, value));
- if (asBinding) {
- bindings.push(asBinding);
- }
- return bindings;
- }
- /**
- * Return the expression AST for the bound target of a directive keyword
- * binding. For example,
- * ```
- * *ngIf="condition | pipe"
- * ^^^^^^^^^^^^^^^^ bound target for "ngIf"
- * *ngFor="let item of items"
- * ^^^^^ bound target for "ngForOf"
- * ```
- */
- getDirectiveBoundTarget() {
- if (this.next === EOF || this.peekKeywordAs() || this.peekKeywordLet()) {
- return null;
- }
- const ast = this.parsePipe(); // example: "condition | async"
- const { start, end } = ast.span;
- const value = this.input.substring(start, end);
- return new ASTWithSource(ast, value, this.location, this.absoluteOffset + start, this.errors);
- }
- /**
- * Return the binding for a variable declared using `as`. Note that the order
- * of the key-value pair in this declaration is reversed. For example,
- * ```
- * *ngFor="let item of items; index as i"
- * ^^^^^ ^
- * value key
- * ```
- *
- * @param value name of the value in the declaration, "ngIf" in the example
- * above, along with its absolute span.
- */
- parseAsBinding(value) {
- if (!this.peekKeywordAs()) {
- return null;
- }
- this.advance(); // consume the 'as' keyword
- const key = this.expectTemplateBindingKey();
- this.consumeStatementTerminator();
- const sourceSpan = new AbsoluteSourceSpan(value.span.start, this.currentAbsoluteOffset);
- return new VariableBinding(sourceSpan, key, value);
- }
- /**
- * Return the binding for a variable declared using `let`. For example,
- * ```
- * *ngFor="let item of items; let i=index;"
- * ^^^^^^^^ ^^^^^^^^^^^
- * ```
- * In the first binding, `item` is bound to `NgForOfContext.$implicit`.
- * In the second binding, `i` is bound to `NgForOfContext.index`.
- */
- parseLetBinding() {
- if (!this.peekKeywordLet()) {
- return null;
- }
- const spanStart = this.currentAbsoluteOffset;
- this.advance(); // consume the 'let' keyword
- const key = this.expectTemplateBindingKey();
- let value = null;
- if (this.consumeOptionalOperator('=')) {
- value = this.expectTemplateBindingKey();
- }
- this.consumeStatementTerminator();
- const sourceSpan = new AbsoluteSourceSpan(spanStart, this.currentAbsoluteOffset);
- return new VariableBinding(sourceSpan, key, value);
- }
- parseNoInterpolationTemplateLiteral() {
- const text = this.next.strValue;
- const start = this.inputIndex;
- this.advance();
- const span = this.span(start);
- const sourceSpan = this.sourceSpan(start);
- return new TemplateLiteral(span, sourceSpan, [new TemplateLiteralElement(span, sourceSpan, text)], []);
- }
- parseTemplateLiteral() {
- const start = this.inputIndex;
- const elements = [];
- const expressions = [];
- while (this.next !== EOF) {
- const token = this.next;
- if (token.isTemplateLiteralPart() || token.isTemplateLiteralEnd()) {
- const partStart = this.inputIndex;
- this.advance();
- elements.push(new TemplateLiteralElement(this.span(partStart), this.sourceSpan(partStart), token.strValue));
- if (token.isTemplateLiteralEnd()) {
- break;
- }
- }
- else if (token.isTemplateLiteralInterpolationStart()) {
- this.advance();
- const expression = this.parsePipe();
- if (expression instanceof EmptyExpr$1) {
- this.error('Template literal interpolation cannot be empty');
- }
- else {
- expressions.push(expression);
- }
- }
- else {
- this.advance();
- }
- }
- return new TemplateLiteral(this.span(start), this.sourceSpan(start), elements, expressions);
- }
- /**
- * Consume the optional statement terminator: semicolon or comma.
- */
- consumeStatementTerminator() {
- this.consumeOptionalCharacter($SEMICOLON) || this.consumeOptionalCharacter($COMMA);
- }
- /**
- * Records an error and skips over the token stream until reaching a recoverable point. See
- * `this.skip` for more details on token skipping.
- */
- error(message, index = null) {
- this.errors.push(new ParserError(message, this.input, this.locationText(index), this.location));
- this.skip();
- }
- locationText(index = null) {
- if (index == null)
- index = this.index;
- return index < this.tokens.length
- ? `at column ${this.tokens[index].index + 1} in`
- : `at the end of the expression`;
- }
- /**
- * Records an error for an unexpected private identifier being discovered.
- * @param token Token representing a private identifier.
- * @param extraMessage Optional additional message being appended to the error.
- */
- _reportErrorForPrivateIdentifier(token, extraMessage) {
- let errorMessage = `Private identifiers are not supported. Unexpected private identifier: ${token}`;
- if (extraMessage !== null) {
- errorMessage += `, ${extraMessage}`;
- }
- this.error(errorMessage);
- }
- /**
- * Error recovery should skip tokens until it encounters a recovery point.
- *
- * The following are treated as unconditional recovery points:
- * - end of input
- * - ';' (parseChain() is always the root production, and it expects a ';')
- * - '|' (since pipes may be chained and each pipe expression may be treated independently)
- *
- * The following are conditional recovery points:
- * - ')', '}', ']' if one of calling productions is expecting one of these symbols
- * - This allows skip() to recover from errors such as '(a.) + 1' allowing more of the AST to
- * be retained (it doesn't skip any tokens as the ')' is retained because of the '(' begins
- * an '(' <expr> ')' production).
- * The recovery points of grouping symbols must be conditional as they must be skipped if
- * none of the calling productions are not expecting the closing token else we will never
- * make progress in the case of an extraneous group closing symbol (such as a stray ')').
- * That is, we skip a closing symbol if we are not in a grouping production.
- * - '=' in a `Writable` context
- * - In this context, we are able to recover after seeing the `=` operator, which
- * signals the presence of an independent rvalue expression following the `=` operator.
- *
- * If a production expects one of these token it increments the corresponding nesting count,
- * and then decrements it just prior to checking if the token is in the input.
- */
- skip() {
- let n = this.next;
- while (this.index < this.tokens.length &&
- !n.isCharacter($SEMICOLON) &&
- !n.isOperator('|') &&
- (this.rparensExpected <= 0 || !n.isCharacter($RPAREN)) &&
- (this.rbracesExpected <= 0 || !n.isCharacter($RBRACE)) &&
- (this.rbracketsExpected <= 0 || !n.isCharacter($RBRACKET)) &&
- (!(this.context & ParseContextFlags.Writable) || !n.isOperator('='))) {
- if (this.next.isError()) {
- this.errors.push(new ParserError(this.next.toString(), this.input, this.locationText(), this.location));
- }
- this.advance();
- n = this.next;
- }
- }
- }
- class SimpleExpressionChecker extends RecursiveAstVisitor {
- errors = [];
- visitPipe() {
- this.errors.push('pipes');
- }
- }
- /**
- * Computes the real offset in the original template for indexes in an interpolation.
- *
- * Because templates can have encoded HTML entities and the input passed to the parser at this stage
- * of the compiler is the _decoded_ value, we need to compute the real offset using the original
- * encoded values in the interpolated tokens. Note that this is only a special case handling for
- * `MlParserTokenType.ENCODED_ENTITY` token types. All other interpolated tokens are expected to
- * have parts which exactly match the input string for parsing the interpolation.
- *
- * @param interpolatedTokens The tokens for the interpolated value.
- *
- * @returns A map of index locations in the decoded template to indexes in the original template
- */
- function getIndexMapForOriginalTemplate(interpolatedTokens) {
- let offsetMap = new Map();
- let consumedInOriginalTemplate = 0;
- let consumedInInput = 0;
- let tokenIndex = 0;
- while (tokenIndex < interpolatedTokens.length) {
- const currentToken = interpolatedTokens[tokenIndex];
- if (currentToken.type === 9 /* MlParserTokenType.ENCODED_ENTITY */) {
- const [decoded, encoded] = currentToken.parts;
- consumedInOriginalTemplate += encoded.length;
- consumedInInput += decoded.length;
- }
- else {
- const lengthOfParts = currentToken.parts.reduce((sum, current) => sum + current.length, 0);
- consumedInInput += lengthOfParts;
- consumedInOriginalTemplate += lengthOfParts;
- }
- offsetMap.set(consumedInInput, consumedInOriginalTemplate);
- tokenIndex++;
- }
- return offsetMap;
- }
- /** Serializes the given AST into a normalized string format. */
- function serialize(expression) {
- return expression.visit(new SerializeExpressionVisitor());
- }
- class SerializeExpressionVisitor {
- visitUnary(ast, context) {
- return `${ast.operator}${ast.expr.visit(this, context)}`;
- }
- visitBinary(ast, context) {
- return `${ast.left.visit(this, context)} ${ast.operation} ${ast.right.visit(this, context)}`;
- }
- visitChain(ast, context) {
- return ast.expressions.map((e) => e.visit(this, context)).join('; ');
- }
- visitConditional(ast, context) {
- return `${ast.condition.visit(this, context)} ? ${ast.trueExp.visit(this, context)} : ${ast.falseExp.visit(this, context)}`;
- }
- visitThisReceiver() {
- return 'this';
- }
- visitImplicitReceiver() {
- return '';
- }
- visitInterpolation(ast, context) {
- return interleave(ast.strings, ast.expressions.map((e) => e.visit(this, context))).join('');
- }
- visitKeyedRead(ast, context) {
- return `${ast.receiver.visit(this, context)}[${ast.key.visit(this, context)}]`;
- }
- visitKeyedWrite(ast, context) {
- return `${ast.receiver.visit(this, context)}[${ast.key.visit(this, context)}] = ${ast.value.visit(this, context)}`;
- }
- visitLiteralArray(ast, context) {
- return `[${ast.expressions.map((e) => e.visit(this, context)).join(', ')}]`;
- }
- visitLiteralMap(ast, context) {
- return `{${zip(ast.keys.map((literal) => (literal.quoted ? `'${literal.key}'` : literal.key)), ast.values.map((value) => value.visit(this, context)))
- .map(([key, value]) => `${key}: ${value}`)
- .join(', ')}}`;
- }
- visitLiteralPrimitive(ast) {
- if (ast.value === null)
- return 'null';
- switch (typeof ast.value) {
- case 'number':
- case 'boolean':
- return ast.value.toString();
- case 'undefined':
- return 'undefined';
- case 'string':
- return `'${ast.value.replace(/'/g, `\\'`)}'`;
- default:
- throw new Error(`Unsupported primitive type: ${ast.value}`);
- }
- }
- visitPipe(ast, context) {
- return `${ast.exp.visit(this, context)} | ${ast.name}`;
- }
- visitPrefixNot(ast, context) {
- return `!${ast.expression.visit(this, context)}`;
- }
- visitNonNullAssert(ast, context) {
- return `${ast.expression.visit(this, context)}!`;
- }
- visitPropertyRead(ast, context) {
- if (ast.receiver instanceof ImplicitReceiver) {
- return ast.name;
- }
- else {
- return `${ast.receiver.visit(this, context)}.${ast.name}`;
- }
- }
- visitPropertyWrite(ast, context) {
- if (ast.receiver instanceof ImplicitReceiver) {
- return `${ast.name} = ${ast.value.visit(this, context)}`;
- }
- else {
- return `${ast.receiver.visit(this, context)}.${ast.name} = ${ast.value.visit(this, context)}`;
- }
- }
- visitSafePropertyRead(ast, context) {
- return `${ast.receiver.visit(this, context)}?.${ast.name}`;
- }
- visitSafeKeyedRead(ast, context) {
- return `${ast.receiver.visit(this, context)}?.[${ast.key.visit(this, context)}]`;
- }
- visitCall(ast, context) {
- return `${ast.receiver.visit(this, context)}(${ast.args
- .map((e) => e.visit(this, context))
- .join(', ')})`;
- }
- visitSafeCall(ast, context) {
- return `${ast.receiver.visit(this, context)}?.(${ast.args
- .map((e) => e.visit(this, context))
- .join(', ')})`;
- }
- visitTypeofExpression(ast, context) {
- return `typeof ${ast.expression.visit(this, context)}`;
- }
- visitASTWithSource(ast, context) {
- return ast.ast.visit(this, context);
- }
- visitTemplateLiteral(ast, context) {
- let result = '';
- for (let i = 0; i < ast.elements.length; i++) {
- result += ast.elements[i].visit(this, context);
- const expression = i < ast.expressions.length ? ast.expressions[i] : null;
- if (expression !== null) {
- result += '${' + expression.visit(this, context) + '}';
- }
- }
- return '`' + result + '`';
- }
- visitTemplateLiteralElement(ast, context) {
- return ast.text;
- }
- }
- /** Zips the two input arrays into a single array of pairs of elements at the same index. */
- function zip(left, right) {
- if (left.length !== right.length)
- throw new Error('Array lengths must match');
- return left.map((l, i) => [l, right[i]]);
- }
- /**
- * Interleaves the two arrays, starting with the first item on the left, then the first item
- * on the right, second item from the left, and so on. When the first array's items are exhausted,
- * the remaining items from the other array are included with no interleaving.
- */
- function interleave(left, right) {
- const result = [];
- for (let index = 0; index < Math.max(left.length, right.length); index++) {
- if (index < left.length)
- result.push(left[index]);
- if (index < right.length)
- result.push(right[index]);
- }
- return result;
- }
- // =================================================================================================
- // =================================================================================================
- // =========== S T O P - S T O P - S T O P - S T O P - S T O P - S T O P ===========
- // =================================================================================================
- // =================================================================================================
- //
- // DO NOT EDIT THIS LIST OF SECURITY SENSITIVE PROPERTIES WITHOUT A SECURITY REVIEW!
- // Reach out to mprobst for details.
- //
- // =================================================================================================
- /** Map from tagName|propertyName to SecurityContext. Properties applying to all tags use '*'. */
- let _SECURITY_SCHEMA;
- function SECURITY_SCHEMA() {
- if (!_SECURITY_SCHEMA) {
- _SECURITY_SCHEMA = {};
- // Case is insignificant below, all element and attribute names are lower-cased for lookup.
- registerContext(SecurityContext.HTML, ['iframe|srcdoc', '*|innerHTML', '*|outerHTML']);
- registerContext(SecurityContext.STYLE, ['*|style']);
- // NB: no SCRIPT contexts here, they are never allowed due to the parser stripping them.
- registerContext(SecurityContext.URL, [
- '*|formAction',
- 'area|href',
- 'area|ping',
- 'audio|src',
- 'a|href',
- 'a|ping',
- 'blockquote|cite',
- 'body|background',
- 'del|cite',
- 'form|action',
- 'img|src',
- 'input|src',
- 'ins|cite',
- 'q|cite',
- 'source|src',
- 'track|src',
- 'video|poster',
- 'video|src',
- ]);
- registerContext(SecurityContext.RESOURCE_URL, [
- 'applet|code',
- 'applet|codebase',
- 'base|href',
- 'embed|src',
- 'frame|src',
- 'head|profile',
- 'html|manifest',
- 'iframe|src',
- 'link|href',
- 'media|src',
- 'object|codebase',
- 'object|data',
- 'script|src',
- ]);
- }
- return _SECURITY_SCHEMA;
- }
- function registerContext(ctx, specs) {
- for (const spec of specs)
- _SECURITY_SCHEMA[spec.toLowerCase()] = ctx;
- }
- /**
- * The set of security-sensitive attributes of an `<iframe>` that *must* be
- * applied as a static attribute only. This ensures that all security-sensitive
- * attributes are taken into account while creating an instance of an `<iframe>`
- * at runtime.
- *
- * Note: avoid using this set directly, use the `isIframeSecuritySensitiveAttr` function
- * in the code instead.
- */
- const IFRAME_SECURITY_SENSITIVE_ATTRS = new Set([
- 'sandbox',
- 'allow',
- 'allowfullscreen',
- 'referrerpolicy',
- 'csp',
- 'fetchpriority',
- ]);
- /**
- * Checks whether a given attribute name might represent a security-sensitive
- * attribute of an <iframe>.
- */
- function isIframeSecuritySensitiveAttr(attrName) {
- // The `setAttribute` DOM API is case-insensitive, so we lowercase the value
- // before checking it against a known security-sensitive attributes.
- return IFRAME_SECURITY_SENSITIVE_ATTRS.has(attrName.toLowerCase());
- }
- class ElementSchemaRegistry {
- }
- const BOOLEAN = 'boolean';
- const NUMBER = 'number';
- const STRING = 'string';
- const OBJECT = 'object';
- /**
- * This array represents the DOM schema. It encodes inheritance, properties, and events.
- *
- * ## Overview
- *
- * Each line represents one kind of element. The `element_inheritance` and properties are joined
- * using `element_inheritance|properties` syntax.
- *
- * ## Element Inheritance
- *
- * The `element_inheritance` can be further subdivided as `element1,element2,...^parentElement`.
- * Here the individual elements are separated by `,` (commas). Every element in the list
- * has identical properties.
- *
- * An `element` may inherit additional properties from `parentElement` If no `^parentElement` is
- * specified then `""` (blank) element is assumed.
- *
- * NOTE: The blank element inherits from root `[Element]` element, the super element of all
- * elements.
- *
- * NOTE an element prefix such as `:svg:` has no special meaning to the schema.
- *
- * ## Properties
- *
- * Each element has a set of properties separated by `,` (commas). Each property can be prefixed
- * by a special character designating its type:
- *
- * - (no prefix): property is a string.
- * - `*`: property represents an event.
- * - `!`: property is a boolean.
- * - `#`: property is a number.
- * - `%`: property is an object.
- *
- * ## Query
- *
- * The class creates an internal squas representation which allows to easily answer the query of
- * if a given property exist on a given element.
- *
- * NOTE: We don't yet support querying for types or events.
- * NOTE: This schema is auto extracted from `schema_extractor.ts` located in the test folder,
- * see dom_element_schema_registry_spec.ts
- */
- // =================================================================================================
- // =================================================================================================
- // =========== S T O P - S T O P - S T O P - S T O P - S T O P - S T O P ===========
- // =================================================================================================
- // =================================================================================================
- //
- // DO NOT EDIT THIS DOM SCHEMA WITHOUT A SECURITY REVIEW!
- //
- // Newly added properties must be security reviewed and assigned an appropriate SecurityContext in
- // dom_security_schema.ts. Reach out to mprobst & rjamet for details.
- //
- // =================================================================================================
- const SCHEMA = [
- '[Element]|textContent,%ariaAtomic,%ariaAutoComplete,%ariaBusy,%ariaChecked,%ariaColCount,%ariaColIndex,%ariaColSpan,%ariaCurrent,%ariaDescription,%ariaDisabled,%ariaExpanded,%ariaHasPopup,%ariaHidden,%ariaKeyShortcuts,%ariaLabel,%ariaLevel,%ariaLive,%ariaModal,%ariaMultiLine,%ariaMultiSelectable,%ariaOrientation,%ariaPlaceholder,%ariaPosInSet,%ariaPressed,%ariaReadOnly,%ariaRelevant,%ariaRequired,%ariaRoleDescription,%ariaRowCount,%ariaRowIndex,%ariaRowSpan,%ariaSelected,%ariaSetSize,%ariaSort,%ariaValueMax,%ariaValueMin,%ariaValueNow,%ariaValueText,%classList,className,elementTiming,id,innerHTML,*beforecopy,*beforecut,*beforepaste,*fullscreenchange,*fullscreenerror,*search,*webkitfullscreenchange,*webkitfullscreenerror,outerHTML,%part,#scrollLeft,#scrollTop,slot' +
- /* added manually to avoid breaking changes */
- ',*message,*mozfullscreenchange,*mozfullscreenerror,*mozpointerlockchange,*mozpointerlockerror,*webglcontextcreationerror,*webglcontextlost,*webglcontextrestored',
- '[HTMLElement]^[Element]|accessKey,autocapitalize,!autofocus,contentEditable,dir,!draggable,enterKeyHint,!hidden,!inert,innerText,inputMode,lang,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,outerText,!spellcheck,%style,#tabIndex,title,!translate,virtualKeyboardPolicy',
- 'abbr,address,article,aside,b,bdi,bdo,cite,content,code,dd,dfn,dt,em,figcaption,figure,footer,header,hgroup,i,kbd,main,mark,nav,noscript,rb,rp,rt,rtc,ruby,s,samp,search,section,small,strong,sub,sup,u,var,wbr^[HTMLElement]|accessKey,autocapitalize,!autofocus,contentEditable,dir,!draggable,enterKeyHint,!hidden,innerText,inputMode,lang,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,outerText,!spellcheck,%style,#tabIndex,title,!translate,virtualKeyboardPolicy',
- 'media^[HTMLElement]|!autoplay,!controls,%controlsList,%crossOrigin,#currentTime,!defaultMuted,#defaultPlaybackRate,!disableRemotePlayback,!loop,!muted,*encrypted,*waitingforkey,#playbackRate,preload,!preservesPitch,src,%srcObject,#volume',
- ':svg:^[HTMLElement]|!autofocus,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,%style,#tabIndex',
- ':svg:graphics^:svg:|',
- ':svg:animation^:svg:|*begin,*end,*repeat',
- ':svg:geometry^:svg:|',
- ':svg:componentTransferFunction^:svg:|',
- ':svg:gradient^:svg:|',
- ':svg:textContent^:svg:graphics|',
- ':svg:textPositioning^:svg:textContent|',
- 'a^[HTMLElement]|charset,coords,download,hash,host,hostname,href,hreflang,name,password,pathname,ping,port,protocol,referrerPolicy,rel,%relList,rev,search,shape,target,text,type,username',
- 'area^[HTMLElement]|alt,coords,download,hash,host,hostname,href,!noHref,password,pathname,ping,port,protocol,referrerPolicy,rel,%relList,search,shape,target,username',
- 'audio^media|',
- 'br^[HTMLElement]|clear',
- 'base^[HTMLElement]|href,target',
- 'body^[HTMLElement]|aLink,background,bgColor,link,*afterprint,*beforeprint,*beforeunload,*blur,*error,*focus,*hashchange,*languagechange,*load,*message,*messageerror,*offline,*online,*pagehide,*pageshow,*popstate,*rejectionhandled,*resize,*scroll,*storage,*unhandledrejection,*unload,text,vLink',
- 'button^[HTMLElement]|!disabled,formAction,formEnctype,formMethod,!formNoValidate,formTarget,name,type,value',
- 'canvas^[HTMLElement]|#height,#width',
- 'content^[HTMLElement]|select',
- 'dl^[HTMLElement]|!compact',
- 'data^[HTMLElement]|value',
- 'datalist^[HTMLElement]|',
- 'details^[HTMLElement]|!open',
- 'dialog^[HTMLElement]|!open,returnValue',
- 'dir^[HTMLElement]|!compact',
- 'div^[HTMLElement]|align',
- 'embed^[HTMLElement]|align,height,name,src,type,width',
- 'fieldset^[HTMLElement]|!disabled,name',
- 'font^[HTMLElement]|color,face,size',
- 'form^[HTMLElement]|acceptCharset,action,autocomplete,encoding,enctype,method,name,!noValidate,target',
- 'frame^[HTMLElement]|frameBorder,longDesc,marginHeight,marginWidth,name,!noResize,scrolling,src',
- 'frameset^[HTMLElement]|cols,*afterprint,*beforeprint,*beforeunload,*blur,*error,*focus,*hashchange,*languagechange,*load,*message,*messageerror,*offline,*online,*pagehide,*pageshow,*popstate,*rejectionhandled,*resize,*scroll,*storage,*unhandledrejection,*unload,rows',
- 'hr^[HTMLElement]|align,color,!noShade,size,width',
- 'head^[HTMLElement]|',
- 'h1,h2,h3,h4,h5,h6^[HTMLElement]|align',
- 'html^[HTMLElement]|version',
- 'iframe^[HTMLElement]|align,allow,!allowFullscreen,!allowPaymentRequest,csp,frameBorder,height,loading,longDesc,marginHeight,marginWidth,name,referrerPolicy,%sandbox,scrolling,src,srcdoc,width',
- 'img^[HTMLElement]|align,alt,border,%crossOrigin,decoding,#height,#hspace,!isMap,loading,longDesc,lowsrc,name,referrerPolicy,sizes,src,srcset,useMap,#vspace,#width',
- 'input^[HTMLElement]|accept,align,alt,autocomplete,!checked,!defaultChecked,defaultValue,dirName,!disabled,%files,formAction,formEnctype,formMethod,!formNoValidate,formTarget,#height,!incremental,!indeterminate,max,#maxLength,min,#minLength,!multiple,name,pattern,placeholder,!readOnly,!required,selectionDirection,#selectionEnd,#selectionStart,#size,src,step,type,useMap,value,%valueAsDate,#valueAsNumber,#width',
- 'li^[HTMLElement]|type,#value',
- 'label^[HTMLElement]|htmlFor',
- 'legend^[HTMLElement]|align',
- 'link^[HTMLElement]|as,charset,%crossOrigin,!disabled,href,hreflang,imageSizes,imageSrcset,integrity,media,referrerPolicy,rel,%relList,rev,%sizes,target,type',
- 'map^[HTMLElement]|name',
- 'marquee^[HTMLElement]|behavior,bgColor,direction,height,#hspace,#loop,#scrollAmount,#scrollDelay,!trueSpeed,#vspace,width',
- 'menu^[HTMLElement]|!compact',
- 'meta^[HTMLElement]|content,httpEquiv,media,name,scheme',
- 'meter^[HTMLElement]|#high,#low,#max,#min,#optimum,#value',
- 'ins,del^[HTMLElement]|cite,dateTime',
- 'ol^[HTMLElement]|!compact,!reversed,#start,type',
- 'object^[HTMLElement]|align,archive,border,code,codeBase,codeType,data,!declare,height,#hspace,name,standby,type,useMap,#vspace,width',
- 'optgroup^[HTMLElement]|!disabled,label',
- 'option^[HTMLElement]|!defaultSelected,!disabled,label,!selected,text,value',
- 'output^[HTMLElement]|defaultValue,%htmlFor,name,value',
- 'p^[HTMLElement]|align',
- 'param^[HTMLElement]|name,type,value,valueType',
- 'picture^[HTMLElement]|',
- 'pre^[HTMLElement]|#width',
- 'progress^[HTMLElement]|#max,#value',
- 'q,blockquote,cite^[HTMLElement]|',
- 'script^[HTMLElement]|!async,charset,%crossOrigin,!defer,event,htmlFor,integrity,!noModule,%referrerPolicy,src,text,type',
- 'select^[HTMLElement]|autocomplete,!disabled,#length,!multiple,name,!required,#selectedIndex,#size,value',
- 'slot^[HTMLElement]|name',
- 'source^[HTMLElement]|#height,media,sizes,src,srcset,type,#width',
- 'span^[HTMLElement]|',
- 'style^[HTMLElement]|!disabled,media,type',
- 'search^[HTMLELement]|',
- 'caption^[HTMLElement]|align',
- 'th,td^[HTMLElement]|abbr,align,axis,bgColor,ch,chOff,#colSpan,headers,height,!noWrap,#rowSpan,scope,vAlign,width',
- 'col,colgroup^[HTMLElement]|align,ch,chOff,#span,vAlign,width',
- 'table^[HTMLElement]|align,bgColor,border,%caption,cellPadding,cellSpacing,frame,rules,summary,%tFoot,%tHead,width',
- 'tr^[HTMLElement]|align,bgColor,ch,chOff,vAlign',
- 'tfoot,thead,tbody^[HTMLElement]|align,ch,chOff,vAlign',
- 'template^[HTMLElement]|',
- 'textarea^[HTMLElement]|autocomplete,#cols,defaultValue,dirName,!disabled,#maxLength,#minLength,name,placeholder,!readOnly,!required,#rows,selectionDirection,#selectionEnd,#selectionStart,value,wrap',
- 'time^[HTMLElement]|dateTime',
- 'title^[HTMLElement]|text',
- 'track^[HTMLElement]|!default,kind,label,src,srclang',
- 'ul^[HTMLElement]|!compact,type',
- 'unknown^[HTMLElement]|',
- 'video^media|!disablePictureInPicture,#height,*enterpictureinpicture,*leavepictureinpicture,!playsInline,poster,#width',
- ':svg:a^:svg:graphics|',
- ':svg:animate^:svg:animation|',
- ':svg:animateMotion^:svg:animation|',
- ':svg:animateTransform^:svg:animation|',
- ':svg:circle^:svg:geometry|',
- ':svg:clipPath^:svg:graphics|',
- ':svg:defs^:svg:graphics|',
- ':svg:desc^:svg:|',
- ':svg:discard^:svg:|',
- ':svg:ellipse^:svg:geometry|',
- ':svg:feBlend^:svg:|',
- ':svg:feColorMatrix^:svg:|',
- ':svg:feComponentTransfer^:svg:|',
- ':svg:feComposite^:svg:|',
- ':svg:feConvolveMatrix^:svg:|',
- ':svg:feDiffuseLighting^:svg:|',
- ':svg:feDisplacementMap^:svg:|',
- ':svg:feDistantLight^:svg:|',
- ':svg:feDropShadow^:svg:|',
- ':svg:feFlood^:svg:|',
- ':svg:feFuncA^:svg:componentTransferFunction|',
- ':svg:feFuncB^:svg:componentTransferFunction|',
- ':svg:feFuncG^:svg:componentTransferFunction|',
- ':svg:feFuncR^:svg:componentTransferFunction|',
- ':svg:feGaussianBlur^:svg:|',
- ':svg:feImage^:svg:|',
- ':svg:feMerge^:svg:|',
- ':svg:feMergeNode^:svg:|',
- ':svg:feMorphology^:svg:|',
- ':svg:feOffset^:svg:|',
- ':svg:fePointLight^:svg:|',
- ':svg:feSpecularLighting^:svg:|',
- ':svg:feSpotLight^:svg:|',
- ':svg:feTile^:svg:|',
- ':svg:feTurbulence^:svg:|',
- ':svg:filter^:svg:|',
- ':svg:foreignObject^:svg:graphics|',
- ':svg:g^:svg:graphics|',
- ':svg:image^:svg:graphics|decoding',
- ':svg:line^:svg:geometry|',
- ':svg:linearGradient^:svg:gradient|',
- ':svg:mpath^:svg:|',
- ':svg:marker^:svg:|',
- ':svg:mask^:svg:|',
- ':svg:metadata^:svg:|',
- ':svg:path^:svg:geometry|',
- ':svg:pattern^:svg:|',
- ':svg:polygon^:svg:geometry|',
- ':svg:polyline^:svg:geometry|',
- ':svg:radialGradient^:svg:gradient|',
- ':svg:rect^:svg:geometry|',
- ':svg:svg^:svg:graphics|#currentScale,#zoomAndPan',
- ':svg:script^:svg:|type',
- ':svg:set^:svg:animation|',
- ':svg:stop^:svg:|',
- ':svg:style^:svg:|!disabled,media,title,type',
- ':svg:switch^:svg:graphics|',
- ':svg:symbol^:svg:|',
- ':svg:tspan^:svg:textPositioning|',
- ':svg:text^:svg:textPositioning|',
- ':svg:textPath^:svg:textContent|',
- ':svg:title^:svg:|',
- ':svg:use^:svg:graphics|',
- ':svg:view^:svg:|#zoomAndPan',
- 'data^[HTMLElement]|value',
- 'keygen^[HTMLElement]|!autofocus,challenge,!disabled,form,keytype,name',
- 'menuitem^[HTMLElement]|type,label,icon,!disabled,!checked,radiogroup,!default',
- 'summary^[HTMLElement]|',
- 'time^[HTMLElement]|dateTime',
- ':svg:cursor^:svg:|',
- ':math:^[HTMLElement]|!autofocus,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforeinput,*beforematch,*beforetoggle,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contentvisibilityautostatechange,*contextlost,*contextmenu,*contextrestored,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*scrollend,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,%style,#tabIndex',
- ':math:math^:math:|',
- ':math:maction^:math:|',
- ':math:menclose^:math:|',
- ':math:merror^:math:|',
- ':math:mfenced^:math:|',
- ':math:mfrac^:math:|',
- ':math:mi^:math:|',
- ':math:mmultiscripts^:math:|',
- ':math:mn^:math:|',
- ':math:mo^:math:|',
- ':math:mover^:math:|',
- ':math:mpadded^:math:|',
- ':math:mphantom^:math:|',
- ':math:mroot^:math:|',
- ':math:mrow^:math:|',
- ':math:ms^:math:|',
- ':math:mspace^:math:|',
- ':math:msqrt^:math:|',
- ':math:mstyle^:math:|',
- ':math:msub^:math:|',
- ':math:msubsup^:math:|',
- ':math:msup^:math:|',
- ':math:mtable^:math:|',
- ':math:mtd^:math:|',
- ':math:mtext^:math:|',
- ':math:mtr^:math:|',
- ':math:munder^:math:|',
- ':math:munderover^:math:|',
- ':math:semantics^:math:|',
- ];
- const _ATTR_TO_PROP = new Map(Object.entries({
- 'class': 'className',
- 'for': 'htmlFor',
- 'formaction': 'formAction',
- 'innerHtml': 'innerHTML',
- 'readonly': 'readOnly',
- 'tabindex': 'tabIndex',
- }));
- // Invert _ATTR_TO_PROP.
- const _PROP_TO_ATTR = Array.from(_ATTR_TO_PROP).reduce((inverted, [propertyName, attributeName]) => {
- inverted.set(propertyName, attributeName);
- return inverted;
- }, new Map());
- class DomElementSchemaRegistry extends ElementSchemaRegistry {
- _schema = new Map();
- // We don't allow binding to events for security reasons. Allowing event bindings would almost
- // certainly introduce bad XSS vulnerabilities. Instead, we store events in a separate schema.
- _eventSchema = new Map();
- constructor() {
- super();
- SCHEMA.forEach((encodedType) => {
- const type = new Map();
- const events = new Set();
- const [strType, strProperties] = encodedType.split('|');
- const properties = strProperties.split(',');
- const [typeNames, superName] = strType.split('^');
- typeNames.split(',').forEach((tag) => {
- this._schema.set(tag.toLowerCase(), type);
- this._eventSchema.set(tag.toLowerCase(), events);
- });
- const superType = superName && this._schema.get(superName.toLowerCase());
- if (superType) {
- for (const [prop, value] of superType) {
- type.set(prop, value);
- }
- for (const superEvent of this._eventSchema.get(superName.toLowerCase())) {
- events.add(superEvent);
- }
- }
- properties.forEach((property) => {
- if (property.length > 0) {
- switch (property[0]) {
- case '*':
- events.add(property.substring(1));
- break;
- case '!':
- type.set(property.substring(1), BOOLEAN);
- break;
- case '#':
- type.set(property.substring(1), NUMBER);
- break;
- case '%':
- type.set(property.substring(1), OBJECT);
- break;
- default:
- type.set(property, STRING);
- }
- }
- });
- });
- }
- hasProperty(tagName, propName, schemaMetas) {
- if (schemaMetas.some((schema) => schema.name === NO_ERRORS_SCHEMA.name)) {
- return true;
- }
- if (tagName.indexOf('-') > -1) {
- if (isNgContainer(tagName) || isNgContent(tagName)) {
- return false;
- }
- if (schemaMetas.some((schema) => schema.name === CUSTOM_ELEMENTS_SCHEMA.name)) {
- // Can't tell now as we don't know which properties a custom element will get
- // once it is instantiated
- return true;
- }
- }
- const elementProperties = this._schema.get(tagName.toLowerCase()) || this._schema.get('unknown');
- return elementProperties.has(propName);
- }
- hasElement(tagName, schemaMetas) {
- if (schemaMetas.some((schema) => schema.name === NO_ERRORS_SCHEMA.name)) {
- return true;
- }
- if (tagName.indexOf('-') > -1) {
- if (isNgContainer(tagName) || isNgContent(tagName)) {
- return true;
- }
- if (schemaMetas.some((schema) => schema.name === CUSTOM_ELEMENTS_SCHEMA.name)) {
- // Allow any custom elements
- return true;
- }
- }
- return this._schema.has(tagName.toLowerCase());
- }
- /**
- * securityContext returns the security context for the given property on the given DOM tag.
- *
- * Tag and property name are statically known and cannot change at runtime, i.e. it is not
- * possible to bind a value into a changing attribute or tag name.
- *
- * The filtering is based on a list of allowed tags|attributes. All attributes in the schema
- * above are assumed to have the 'NONE' security context, i.e. that they are safe inert
- * string values. Only specific well known attack vectors are assigned their appropriate context.
- */
- securityContext(tagName, propName, isAttribute) {
- if (isAttribute) {
- // NB: For security purposes, use the mapped property name, not the attribute name.
- propName = this.getMappedPropName(propName);
- }
- // Make sure comparisons are case insensitive, so that case differences between attribute and
- // property names do not have a security impact.
- tagName = tagName.toLowerCase();
- propName = propName.toLowerCase();
- let ctx = SECURITY_SCHEMA()[tagName + '|' + propName];
- if (ctx) {
- return ctx;
- }
- ctx = SECURITY_SCHEMA()['*|' + propName];
- return ctx ? ctx : SecurityContext.NONE;
- }
- getMappedPropName(propName) {
- return _ATTR_TO_PROP.get(propName) ?? propName;
- }
- getDefaultComponentElementName() {
- return 'ng-component';
- }
- validateProperty(name) {
- if (name.toLowerCase().startsWith('on')) {
- const msg = `Binding to event property '${name}' is disallowed for security reasons, ` +
- `please use (${name.slice(2)})=...` +
- `\nIf '${name}' is a directive input, make sure the directive is imported by the` +
- ` current module.`;
- return { error: true, msg: msg };
- }
- else {
- return { error: false };
- }
- }
- validateAttribute(name) {
- if (name.toLowerCase().startsWith('on')) {
- const msg = `Binding to event attribute '${name}' is disallowed for security reasons, ` +
- `please use (${name.slice(2)})=...`;
- return { error: true, msg: msg };
- }
- else {
- return { error: false };
- }
- }
- allKnownElementNames() {
- return Array.from(this._schema.keys());
- }
- allKnownAttributesOfElement(tagName) {
- const elementProperties = this._schema.get(tagName.toLowerCase()) || this._schema.get('unknown');
- // Convert properties to attributes.
- return Array.from(elementProperties.keys()).map((prop) => _PROP_TO_ATTR.get(prop) ?? prop);
- }
- allKnownEventsOfElement(tagName) {
- return Array.from(this._eventSchema.get(tagName.toLowerCase()) ?? []);
- }
- normalizeAnimationStyleProperty(propName) {
- return dashCaseToCamelCase(propName);
- }
- normalizeAnimationStyleValue(camelCaseProp, userProvidedProp, val) {
- let unit = '';
- const strVal = val.toString().trim();
- let errorMsg = null;
- if (_isPixelDimensionStyle(camelCaseProp) && val !== 0 && val !== '0') {
- if (typeof val === 'number') {
- unit = 'px';
- }
- else {
- const valAndSuffixMatch = val.match(/^[+-]?[\d\.]+([a-z]*)$/);
- if (valAndSuffixMatch && valAndSuffixMatch[1].length == 0) {
- errorMsg = `Please provide a CSS unit value for ${userProvidedProp}:${val}`;
- }
- }
- }
- return { error: errorMsg, value: strVal + unit };
- }
- }
- function _isPixelDimensionStyle(prop) {
- switch (prop) {
- case 'width':
- case 'height':
- case 'minWidth':
- case 'minHeight':
- case 'maxWidth':
- case 'maxHeight':
- case 'left':
- case 'top':
- case 'bottom':
- case 'right':
- case 'fontSize':
- case 'outlineWidth':
- case 'outlineOffset':
- case 'paddingTop':
- case 'paddingLeft':
- case 'paddingBottom':
- case 'paddingRight':
- case 'marginTop':
- case 'marginLeft':
- case 'marginBottom':
- case 'marginRight':
- case 'borderRadius':
- case 'borderWidth':
- case 'borderTopWidth':
- case 'borderLeftWidth':
- case 'borderRightWidth':
- case 'borderBottomWidth':
- case 'textIndent':
- return true;
- default:
- return false;
- }
- }
- class HtmlTagDefinition {
- closedByChildren = {};
- contentType;
- closedByParent = false;
- implicitNamespacePrefix;
- isVoid;
- ignoreFirstLf;
- canSelfClose;
- preventNamespaceInheritance;
- constructor({ closedByChildren, implicitNamespacePrefix, contentType = exports.TagContentType.PARSABLE_DATA, closedByParent = false, isVoid = false, ignoreFirstLf = false, preventNamespaceInheritance = false, canSelfClose = false, } = {}) {
- if (closedByChildren && closedByChildren.length > 0) {
- closedByChildren.forEach((tagName) => (this.closedByChildren[tagName] = true));
- }
- this.isVoid = isVoid;
- this.closedByParent = closedByParent || isVoid;
- this.implicitNamespacePrefix = implicitNamespacePrefix || null;
- this.contentType = contentType;
- this.ignoreFirstLf = ignoreFirstLf;
- this.preventNamespaceInheritance = preventNamespaceInheritance;
- this.canSelfClose = canSelfClose ?? isVoid;
- }
- isClosedByChild(name) {
- return this.isVoid || name.toLowerCase() in this.closedByChildren;
- }
- getContentType(prefix) {
- if (typeof this.contentType === 'object') {
- const overrideType = prefix === undefined ? undefined : this.contentType[prefix];
- return overrideType ?? this.contentType.default;
- }
- return this.contentType;
- }
- }
- let DEFAULT_TAG_DEFINITION;
- // see https://www.w3.org/TR/html51/syntax.html#optional-tags
- // This implementation does not fully conform to the HTML5 spec.
- let TAG_DEFINITIONS;
- function getHtmlTagDefinition(tagName) {
- if (!TAG_DEFINITIONS) {
- DEFAULT_TAG_DEFINITION = new HtmlTagDefinition({ canSelfClose: true });
- TAG_DEFINITIONS = Object.assign(Object.create(null), {
- 'base': new HtmlTagDefinition({ isVoid: true }),
- 'meta': new HtmlTagDefinition({ isVoid: true }),
- 'area': new HtmlTagDefinition({ isVoid: true }),
- 'embed': new HtmlTagDefinition({ isVoid: true }),
- 'link': new HtmlTagDefinition({ isVoid: true }),
- 'img': new HtmlTagDefinition({ isVoid: true }),
- 'input': new HtmlTagDefinition({ isVoid: true }),
- 'param': new HtmlTagDefinition({ isVoid: true }),
- 'hr': new HtmlTagDefinition({ isVoid: true }),
- 'br': new HtmlTagDefinition({ isVoid: true }),
- 'source': new HtmlTagDefinition({ isVoid: true }),
- 'track': new HtmlTagDefinition({ isVoid: true }),
- 'wbr': new HtmlTagDefinition({ isVoid: true }),
- 'p': new HtmlTagDefinition({
- closedByChildren: [
- 'address',
- 'article',
- 'aside',
- 'blockquote',
- 'div',
- 'dl',
- 'fieldset',
- 'footer',
- 'form',
- 'h1',
- 'h2',
- 'h3',
- 'h4',
- 'h5',
- 'h6',
- 'header',
- 'hgroup',
- 'hr',
- 'main',
- 'nav',
- 'ol',
- 'p',
- 'pre',
- 'section',
- 'table',
- 'ul',
- ],
- closedByParent: true,
- }),
- 'thead': new HtmlTagDefinition({ closedByChildren: ['tbody', 'tfoot'] }),
- 'tbody': new HtmlTagDefinition({ closedByChildren: ['tbody', 'tfoot'], closedByParent: true }),
- 'tfoot': new HtmlTagDefinition({ closedByChildren: ['tbody'], closedByParent: true }),
- 'tr': new HtmlTagDefinition({ closedByChildren: ['tr'], closedByParent: true }),
- 'td': new HtmlTagDefinition({ closedByChildren: ['td', 'th'], closedByParent: true }),
- 'th': new HtmlTagDefinition({ closedByChildren: ['td', 'th'], closedByParent: true }),
- 'col': new HtmlTagDefinition({ isVoid: true }),
- 'svg': new HtmlTagDefinition({ implicitNamespacePrefix: 'svg' }),
- 'foreignObject': new HtmlTagDefinition({
- // Usually the implicit namespace here would be redundant since it will be inherited from
- // the parent `svg`, but we have to do it for `foreignObject`, because the way the parser
- // works is that the parent node of an end tag is its own start tag which means that
- // the `preventNamespaceInheritance` on `foreignObject` would have it default to the
- // implicit namespace which is `html`, unless specified otherwise.
- implicitNamespacePrefix: 'svg',
- // We want to prevent children of foreignObject from inheriting its namespace, because
- // the point of the element is to allow nodes from other namespaces to be inserted.
- preventNamespaceInheritance: true,
- }),
- 'math': new HtmlTagDefinition({ implicitNamespacePrefix: 'math' }),
- 'li': new HtmlTagDefinition({ closedByChildren: ['li'], closedByParent: true }),
- 'dt': new HtmlTagDefinition({ closedByChildren: ['dt', 'dd'] }),
- 'dd': new HtmlTagDefinition({ closedByChildren: ['dt', 'dd'], closedByParent: true }),
- 'rb': new HtmlTagDefinition({
- closedByChildren: ['rb', 'rt', 'rtc', 'rp'],
- closedByParent: true,
- }),
- 'rt': new HtmlTagDefinition({
- closedByChildren: ['rb', 'rt', 'rtc', 'rp'],
- closedByParent: true,
- }),
- 'rtc': new HtmlTagDefinition({ closedByChildren: ['rb', 'rtc', 'rp'], closedByParent: true }),
- 'rp': new HtmlTagDefinition({
- closedByChildren: ['rb', 'rt', 'rtc', 'rp'],
- closedByParent: true,
- }),
- 'optgroup': new HtmlTagDefinition({ closedByChildren: ['optgroup'], closedByParent: true }),
- 'option': new HtmlTagDefinition({
- closedByChildren: ['option', 'optgroup'],
- closedByParent: true,
- }),
- 'pre': new HtmlTagDefinition({ ignoreFirstLf: true }),
- 'listing': new HtmlTagDefinition({ ignoreFirstLf: true }),
- 'style': new HtmlTagDefinition({ contentType: exports.TagContentType.RAW_TEXT }),
- 'script': new HtmlTagDefinition({ contentType: exports.TagContentType.RAW_TEXT }),
- 'title': new HtmlTagDefinition({
- // The browser supports two separate `title` tags which have to use
- // a different content type: `HTMLTitleElement` and `SVGTitleElement`
- contentType: {
- default: exports.TagContentType.ESCAPABLE_RAW_TEXT,
- svg: exports.TagContentType.PARSABLE_DATA,
- },
- }),
- 'textarea': new HtmlTagDefinition({
- contentType: exports.TagContentType.ESCAPABLE_RAW_TEXT,
- ignoreFirstLf: true,
- }),
- });
- new DomElementSchemaRegistry().allKnownElementNames().forEach((knownTagName) => {
- if (!TAG_DEFINITIONS[knownTagName] && getNsPrefix(knownTagName) === null) {
- TAG_DEFINITIONS[knownTagName] = new HtmlTagDefinition({ canSelfClose: false });
- }
- });
- }
- // We have to make both a case-sensitive and a case-insensitive lookup, because
- // HTML tag names are case insensitive, whereas some SVG tags are case sensitive.
- return (TAG_DEFINITIONS[tagName] ?? TAG_DEFINITIONS[tagName.toLowerCase()] ?? DEFAULT_TAG_DEFINITION);
- }
- const TAG_TO_PLACEHOLDER_NAMES = {
- 'A': 'LINK',
- 'B': 'BOLD_TEXT',
- 'BR': 'LINE_BREAK',
- 'EM': 'EMPHASISED_TEXT',
- 'H1': 'HEADING_LEVEL1',
- 'H2': 'HEADING_LEVEL2',
- 'H3': 'HEADING_LEVEL3',
- 'H4': 'HEADING_LEVEL4',
- 'H5': 'HEADING_LEVEL5',
- 'H6': 'HEADING_LEVEL6',
- 'HR': 'HORIZONTAL_RULE',
- 'I': 'ITALIC_TEXT',
- 'LI': 'LIST_ITEM',
- 'LINK': 'MEDIA_LINK',
- 'OL': 'ORDERED_LIST',
- 'P': 'PARAGRAPH',
- 'Q': 'QUOTATION',
- 'S': 'STRIKETHROUGH_TEXT',
- 'SMALL': 'SMALL_TEXT',
- 'SUB': 'SUBSTRIPT',
- 'SUP': 'SUPERSCRIPT',
- 'TBODY': 'TABLE_BODY',
- 'TD': 'TABLE_CELL',
- 'TFOOT': 'TABLE_FOOTER',
- 'TH': 'TABLE_HEADER_CELL',
- 'THEAD': 'TABLE_HEADER',
- 'TR': 'TABLE_ROW',
- 'TT': 'MONOSPACED_TEXT',
- 'U': 'UNDERLINED_TEXT',
- 'UL': 'UNORDERED_LIST',
- };
- /**
- * Creates unique names for placeholder with different content.
- *
- * Returns the same placeholder name when the content is identical.
- */
- class PlaceholderRegistry {
- // Count the occurrence of the base name top generate a unique name
- _placeHolderNameCounts = {};
- // Maps signature to placeholder names
- _signatureToName = {};
- getStartTagPlaceholderName(tag, attrs, isVoid) {
- const signature = this._hashTag(tag, attrs, isVoid);
- if (this._signatureToName[signature]) {
- return this._signatureToName[signature];
- }
- const upperTag = tag.toUpperCase();
- const baseName = TAG_TO_PLACEHOLDER_NAMES[upperTag] || `TAG_${upperTag}`;
- const name = this._generateUniqueName(isVoid ? baseName : `START_${baseName}`);
- this._signatureToName[signature] = name;
- return name;
- }
- getCloseTagPlaceholderName(tag) {
- const signature = this._hashClosingTag(tag);
- if (this._signatureToName[signature]) {
- return this._signatureToName[signature];
- }
- const upperTag = tag.toUpperCase();
- const baseName = TAG_TO_PLACEHOLDER_NAMES[upperTag] || `TAG_${upperTag}`;
- const name = this._generateUniqueName(`CLOSE_${baseName}`);
- this._signatureToName[signature] = name;
- return name;
- }
- getPlaceholderName(name, content) {
- const upperName = name.toUpperCase();
- const signature = `PH: ${upperName}=${content}`;
- if (this._signatureToName[signature]) {
- return this._signatureToName[signature];
- }
- const uniqueName = this._generateUniqueName(upperName);
- this._signatureToName[signature] = uniqueName;
- return uniqueName;
- }
- getUniquePlaceholder(name) {
- return this._generateUniqueName(name.toUpperCase());
- }
- getStartBlockPlaceholderName(name, parameters) {
- const signature = this._hashBlock(name, parameters);
- if (this._signatureToName[signature]) {
- return this._signatureToName[signature];
- }
- const placeholder = this._generateUniqueName(`START_BLOCK_${this._toSnakeCase(name)}`);
- this._signatureToName[signature] = placeholder;
- return placeholder;
- }
- getCloseBlockPlaceholderName(name) {
- const signature = this._hashClosingBlock(name);
- if (this._signatureToName[signature]) {
- return this._signatureToName[signature];
- }
- const placeholder = this._generateUniqueName(`CLOSE_BLOCK_${this._toSnakeCase(name)}`);
- this._signatureToName[signature] = placeholder;
- return placeholder;
- }
- // Generate a hash for a tag - does not take attribute order into account
- _hashTag(tag, attrs, isVoid) {
- const start = `<${tag}`;
- const strAttrs = Object.keys(attrs)
- .sort()
- .map((name) => ` ${name}=${attrs[name]}`)
- .join('');
- const end = isVoid ? '/>' : `></${tag}>`;
- return start + strAttrs + end;
- }
- _hashClosingTag(tag) {
- return this._hashTag(`/${tag}`, {}, false);
- }
- _hashBlock(name, parameters) {
- const params = parameters.length === 0 ? '' : ` (${parameters.sort().join('; ')})`;
- return `@${name}${params} {}`;
- }
- _hashClosingBlock(name) {
- return this._hashBlock(`close_${name}`, []);
- }
- _toSnakeCase(name) {
- return name.toUpperCase().replace(/[^A-Z0-9]/g, '_');
- }
- _generateUniqueName(base) {
- const seen = this._placeHolderNameCounts.hasOwnProperty(base);
- if (!seen) {
- this._placeHolderNameCounts[base] = 1;
- return base;
- }
- const id = this._placeHolderNameCounts[base];
- this._placeHolderNameCounts[base] = id + 1;
- return `${base}_${id}`;
- }
- }
- const _expParser = new Parser(new Lexer());
- /**
- * Returns a function converting html nodes to an i18n Message given an interpolationConfig
- */
- function createI18nMessageFactory(interpolationConfig, containerBlocks, retainEmptyTokens, preserveExpressionWhitespace) {
- const visitor = new _I18nVisitor(_expParser, interpolationConfig, containerBlocks, retainEmptyTokens, preserveExpressionWhitespace);
- return (nodes, meaning, description, customId, visitNodeFn) => visitor.toI18nMessage(nodes, meaning, description, customId, visitNodeFn);
- }
- function noopVisitNodeFn(_html, i18n) {
- return i18n;
- }
- class _I18nVisitor {
- _expressionParser;
- _interpolationConfig;
- _containerBlocks;
- _retainEmptyTokens;
- _preserveExpressionWhitespace;
- constructor(_expressionParser, _interpolationConfig, _containerBlocks, _retainEmptyTokens, _preserveExpressionWhitespace) {
- this._expressionParser = _expressionParser;
- this._interpolationConfig = _interpolationConfig;
- this._containerBlocks = _containerBlocks;
- this._retainEmptyTokens = _retainEmptyTokens;
- this._preserveExpressionWhitespace = _preserveExpressionWhitespace;
- }
- toI18nMessage(nodes, meaning = '', description = '', customId = '', visitNodeFn) {
- const context = {
- isIcu: nodes.length == 1 && nodes[0] instanceof Expansion,
- icuDepth: 0,
- placeholderRegistry: new PlaceholderRegistry(),
- placeholderToContent: {},
- placeholderToMessage: {},
- visitNodeFn: visitNodeFn || noopVisitNodeFn,
- };
- const i18nodes = visitAll(this, nodes, context);
- return new Message(i18nodes, context.placeholderToContent, context.placeholderToMessage, meaning, description, customId);
- }
- visitElement(el, context) {
- const children = visitAll(this, el.children, context);
- const attrs = {};
- el.attrs.forEach((attr) => {
- // Do not visit the attributes, translatable ones are top-level ASTs
- attrs[attr.name] = attr.value;
- });
- const isVoid = getHtmlTagDefinition(el.name).isVoid;
- const startPhName = context.placeholderRegistry.getStartTagPlaceholderName(el.name, attrs, isVoid);
- context.placeholderToContent[startPhName] = {
- text: el.startSourceSpan.toString(),
- sourceSpan: el.startSourceSpan,
- };
- let closePhName = '';
- if (!isVoid) {
- closePhName = context.placeholderRegistry.getCloseTagPlaceholderName(el.name);
- context.placeholderToContent[closePhName] = {
- text: `</${el.name}>`,
- sourceSpan: el.endSourceSpan ?? el.sourceSpan,
- };
- }
- const node = new TagPlaceholder(el.name, attrs, startPhName, closePhName, children, isVoid, el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
- return context.visitNodeFn(el, node);
- }
- visitAttribute(attribute, context) {
- const node = attribute.valueTokens === undefined || attribute.valueTokens.length === 1
- ? new Text$2(attribute.value, attribute.valueSpan || attribute.sourceSpan)
- : this._visitTextWithInterpolation(attribute.valueTokens, attribute.valueSpan || attribute.sourceSpan, context, attribute.i18n);
- return context.visitNodeFn(attribute, node);
- }
- visitText(text, context) {
- const node = text.tokens.length === 1
- ? new Text$2(text.value, text.sourceSpan)
- : this._visitTextWithInterpolation(text.tokens, text.sourceSpan, context, text.i18n);
- return context.visitNodeFn(text, node);
- }
- visitComment(comment, context) {
- return null;
- }
- visitExpansion(icu, context) {
- context.icuDepth++;
- const i18nIcuCases = {};
- const i18nIcu = new Icu(icu.switchValue, icu.type, i18nIcuCases, icu.sourceSpan);
- icu.cases.forEach((caze) => {
- i18nIcuCases[caze.value] = new Container(caze.expression.map((node) => node.visit(this, context)), caze.expSourceSpan);
- });
- context.icuDepth--;
- if (context.isIcu || context.icuDepth > 0) {
- // Returns an ICU node when:
- // - the message (vs a part of the message) is an ICU message, or
- // - the ICU message is nested.
- const expPh = context.placeholderRegistry.getUniquePlaceholder(`VAR_${icu.type}`);
- i18nIcu.expressionPlaceholder = expPh;
- context.placeholderToContent[expPh] = {
- text: icu.switchValue,
- sourceSpan: icu.switchValueSourceSpan,
- };
- return context.visitNodeFn(icu, i18nIcu);
- }
- // Else returns a placeholder
- // ICU placeholders should not be replaced with their original content but with the their
- // translations.
- // TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
- const phName = context.placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
- context.placeholderToMessage[phName] = this.toI18nMessage([icu], '', '', '', undefined);
- const node = new IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
- return context.visitNodeFn(icu, node);
- }
- visitExpansionCase(_icuCase, _context) {
- throw new Error('Unreachable code');
- }
- visitBlock(block, context) {
- const children = visitAll(this, block.children, context);
- if (this._containerBlocks.has(block.name)) {
- return new Container(children, block.sourceSpan);
- }
- const parameters = block.parameters.map((param) => param.expression);
- const startPhName = context.placeholderRegistry.getStartBlockPlaceholderName(block.name, parameters);
- const closePhName = context.placeholderRegistry.getCloseBlockPlaceholderName(block.name);
- context.placeholderToContent[startPhName] = {
- text: block.startSourceSpan.toString(),
- sourceSpan: block.startSourceSpan,
- };
- context.placeholderToContent[closePhName] = {
- text: block.endSourceSpan ? block.endSourceSpan.toString() : '}',
- sourceSpan: block.endSourceSpan ?? block.sourceSpan,
- };
- const node = new BlockPlaceholder(block.name, parameters, startPhName, closePhName, children, block.sourceSpan, block.startSourceSpan, block.endSourceSpan);
- return context.visitNodeFn(block, node);
- }
- visitBlockParameter(_parameter, _context) {
- throw new Error('Unreachable code');
- }
- visitLetDeclaration(decl, context) {
- return null;
- }
- /**
- * Convert, text and interpolated tokens up into text and placeholder pieces.
- *
- * @param tokens The text and interpolated tokens.
- * @param sourceSpan The span of the whole of the `text` string.
- * @param context The current context of the visitor, used to compute and store placeholders.
- * @param previousI18n Any i18n metadata associated with this `text` from a previous pass.
- */
- _visitTextWithInterpolation(tokens, sourceSpan, context, previousI18n) {
- // Return a sequence of `Text` and `Placeholder` nodes grouped in a `Container`.
- const nodes = [];
- // We will only create a container if there are actually interpolations,
- // so this flag tracks that.
- let hasInterpolation = false;
- for (const token of tokens) {
- switch (token.type) {
- case 8 /* TokenType.INTERPOLATION */:
- case 17 /* TokenType.ATTR_VALUE_INTERPOLATION */:
- hasInterpolation = true;
- const [startMarker, expression, endMarker] = token.parts;
- const baseName = extractPlaceholderName(expression) || 'INTERPOLATION';
- const phName = context.placeholderRegistry.getPlaceholderName(baseName, expression);
- if (this._preserveExpressionWhitespace) {
- context.placeholderToContent[phName] = {
- text: token.parts.join(''),
- sourceSpan: token.sourceSpan,
- };
- nodes.push(new Placeholder(expression, phName, token.sourceSpan));
- }
- else {
- const normalized = this.normalizeExpression(token);
- context.placeholderToContent[phName] = {
- text: `${startMarker}${normalized}${endMarker}`,
- sourceSpan: token.sourceSpan,
- };
- nodes.push(new Placeholder(normalized, phName, token.sourceSpan));
- }
- break;
- default:
- // Try to merge text tokens with previous tokens. We do this even for all tokens
- // when `retainEmptyTokens == true` because whitespace tokens may have non-zero
- // length, but will be trimmed by `WhitespaceVisitor` in one extraction pass and
- // be considered "empty" there. Therefore a whitespace token with
- // `retainEmptyTokens === true` should be treated like an empty token and either
- // retained or merged into the previous node. Since extraction does two passes with
- // different trimming behavior, the second pass needs to have identical node count
- // to reuse source spans, so we need this check to get the same answer when both
- // trimming and not trimming.
- if (token.parts[0].length > 0 || this._retainEmptyTokens) {
- // This token is text or an encoded entity.
- // If it is following on from a previous text node then merge it into that node
- // Otherwise, if it is following an interpolation, then add a new node.
- const previous = nodes[nodes.length - 1];
- if (previous instanceof Text$2) {
- previous.value += token.parts[0];
- previous.sourceSpan = new ParseSourceSpan(previous.sourceSpan.start, token.sourceSpan.end, previous.sourceSpan.fullStart, previous.sourceSpan.details);
- }
- else {
- nodes.push(new Text$2(token.parts[0], token.sourceSpan));
- }
- }
- else {
- // Retain empty tokens to avoid breaking dropping entire nodes such that source
- // spans should not be reusable across multiple parses of a template. We *should*
- // do this all the time, however we need to maintain backwards compatibility
- // with existing message IDs so we can't do it by default and should only enable
- // this when removing significant whitespace.
- if (this._retainEmptyTokens) {
- nodes.push(new Text$2(token.parts[0], token.sourceSpan));
- }
- }
- break;
- }
- }
- if (hasInterpolation) {
- // Whitespace removal may have invalidated the interpolation source-spans.
- reusePreviousSourceSpans(nodes, previousI18n);
- return new Container(nodes, sourceSpan);
- }
- else {
- return nodes[0];
- }
- }
- // Normalize expression whitespace by parsing and re-serializing it. This makes
- // message IDs more durable to insignificant whitespace changes.
- normalizeExpression(token) {
- const expression = token.parts[1];
- const expr = this._expressionParser.parseBinding(expression,
- /* location */ token.sourceSpan.start.toString(),
- /* absoluteOffset */ token.sourceSpan.start.offset, this._interpolationConfig);
- return serialize(expr);
- }
- }
- /**
- * Re-use the source-spans from `previousI18n` metadata for the `nodes`.
- *
- * Whitespace removal can invalidate the source-spans of interpolation nodes, so we
- * reuse the source-span stored from a previous pass before the whitespace was removed.
- *
- * @param nodes The `Text` and `Placeholder` nodes to be processed.
- * @param previousI18n Any i18n metadata for these `nodes` stored from a previous pass.
- */
- function reusePreviousSourceSpans(nodes, previousI18n) {
- if (previousI18n instanceof Message) {
- // The `previousI18n` is an i18n `Message`, so we are processing an `Attribute` with i18n
- // metadata. The `Message` should consist only of a single `Container` that contains the
- // parts (`Text` and `Placeholder`) to process.
- assertSingleContainerMessage(previousI18n);
- previousI18n = previousI18n.nodes[0];
- }
- if (previousI18n instanceof Container) {
- // The `previousI18n` is a `Container`, which means that this is a second i18n extraction pass
- // after whitespace has been removed from the AST nodes.
- assertEquivalentNodes(previousI18n.children, nodes);
- // Reuse the source-spans from the first pass.
- for (let i = 0; i < nodes.length; i++) {
- nodes[i].sourceSpan = previousI18n.children[i].sourceSpan;
- }
- }
- }
- /**
- * Asserts that the `message` contains exactly one `Container` node.
- */
- function assertSingleContainerMessage(message) {
- const nodes = message.nodes;
- if (nodes.length !== 1 || !(nodes[0] instanceof Container)) {
- throw new Error('Unexpected previous i18n message - expected it to consist of only a single `Container` node.');
- }
- }
- /**
- * Asserts that the `previousNodes` and `node` collections have the same number of elements and
- * corresponding elements have the same node type.
- */
- function assertEquivalentNodes(previousNodes, nodes) {
- if (previousNodes.length !== nodes.length) {
- throw new Error(`
- The number of i18n message children changed between first and second pass.
- First pass (${previousNodes.length} tokens):
- ${previousNodes.map((node) => `"${node.sourceSpan.toString()}"`).join('\n')}
- Second pass (${nodes.length} tokens):
- ${nodes.map((node) => `"${node.sourceSpan.toString()}"`).join('\n')}
- `.trim());
- }
- if (previousNodes.some((node, i) => nodes[i].constructor !== node.constructor)) {
- throw new Error('The types of the i18n message children changed between first and second pass.');
- }
- }
- const _CUSTOM_PH_EXP = /\/\/[\s\S]*i18n[\s\S]*\([\s\S]*ph[\s\S]*=[\s\S]*("|')([\s\S]*?)\1[\s\S]*\)/g;
- function extractPlaceholderName(input) {
- return input.split(_CUSTOM_PH_EXP)[2];
- }
- /**
- * An i18n error.
- */
- class I18nError extends ParseError {
- constructor(span, msg) {
- super(span, msg);
- }
- }
- /**
- * Set of tagName|propertyName corresponding to Trusted Types sinks. Properties applying to all
- * tags use '*'.
- *
- * Extracted from, and should be kept in sync with
- * https://w3c.github.io/webappsec-trusted-types/dist/spec/#integrations
- */
- const TRUSTED_TYPES_SINKS = new Set([
- // NOTE: All strings in this set *must* be lowercase!
- // TrustedHTML
- 'iframe|srcdoc',
- '*|innerhtml',
- '*|outerhtml',
- // NB: no TrustedScript here, as the corresponding tags are stripped by the compiler.
- // TrustedScriptURL
- 'embed|src',
- 'object|codebase',
- 'object|data',
- ]);
- /**
- * isTrustedTypesSink returns true if the given property on the given DOM tag is a Trusted Types
- * sink. In that case, use `ElementSchemaRegistry.securityContext` to determine which particular
- * Trusted Type is required for values passed to the sink:
- * - SecurityContext.HTML corresponds to TrustedHTML
- * - SecurityContext.RESOURCE_URL corresponds to TrustedScriptURL
- */
- function isTrustedTypesSink(tagName, propName) {
- // Make sure comparisons are case insensitive, so that case differences between attribute and
- // property names do not have a security impact.
- tagName = tagName.toLowerCase();
- propName = propName.toLowerCase();
- return (TRUSTED_TYPES_SINKS.has(tagName + '|' + propName) || TRUSTED_TYPES_SINKS.has('*|' + propName));
- }
- const setI18nRefs = (originalNodeMap) => {
- return (trimmedNode, i18nNode) => {
- // We need to set i18n properties on the original, untrimmed AST nodes. The i18n nodes needs to
- // use the trimmed content for message IDs to make messages more stable to whitespace changes.
- // But we don't want to actually trim the content, so we can't use the trimmed HTML AST for
- // general code gen. Instead we map the trimmed HTML AST back to the original AST and then
- // attach the i18n nodes so we get trimmed i18n nodes on the original (untrimmed) HTML AST.
- const originalNode = originalNodeMap.get(trimmedNode) ?? trimmedNode;
- if (originalNode instanceof NodeWithI18n) {
- if (i18nNode instanceof IcuPlaceholder && originalNode.i18n instanceof Message) {
- // This html node represents an ICU but this is a second processing pass, and the legacy id
- // was computed in the previous pass and stored in the `i18n` property as a message.
- // We are about to wipe out that property so capture the previous message to be reused when
- // generating the message for this ICU later. See `_generateI18nMessage()`.
- i18nNode.previousMessage = originalNode.i18n;
- }
- originalNode.i18n = i18nNode;
- }
- return i18nNode;
- };
- };
- /**
- * This visitor walks over HTML parse tree and converts information stored in
- * i18n-related attributes ("i18n" and "i18n-*") into i18n meta object that is
- * stored with other element's and attribute's information.
- */
- class I18nMetaVisitor {
- interpolationConfig;
- keepI18nAttrs;
- enableI18nLegacyMessageIdFormat;
- containerBlocks;
- preserveSignificantWhitespace;
- retainEmptyTokens;
- // whether visited nodes contain i18n information
- hasI18nMeta = false;
- _errors = [];
- constructor(interpolationConfig = DEFAULT_INTERPOLATION_CONFIG, keepI18nAttrs = false, enableI18nLegacyMessageIdFormat = false, containerBlocks = DEFAULT_CONTAINER_BLOCKS, preserveSignificantWhitespace = true,
- // When dropping significant whitespace we need to retain empty tokens or
- // else we won't be able to reuse source spans because empty tokens would be
- // removed and cause a mismatch. Unfortunately this still needs to be
- // configurable and sometimes needs to be set independently in order to make
- // sure the number of nodes don't change between parses, even when
- // `preserveSignificantWhitespace` changes.
- retainEmptyTokens = !preserveSignificantWhitespace) {
- this.interpolationConfig = interpolationConfig;
- this.keepI18nAttrs = keepI18nAttrs;
- this.enableI18nLegacyMessageIdFormat = enableI18nLegacyMessageIdFormat;
- this.containerBlocks = containerBlocks;
- this.preserveSignificantWhitespace = preserveSignificantWhitespace;
- this.retainEmptyTokens = retainEmptyTokens;
- }
- _generateI18nMessage(nodes, meta = '', visitNodeFn) {
- const { meaning, description, customId } = this._parseMetadata(meta);
- const createI18nMessage = createI18nMessageFactory(this.interpolationConfig, this.containerBlocks, this.retainEmptyTokens,
- /* preserveExpressionWhitespace */ this.preserveSignificantWhitespace);
- const message = createI18nMessage(nodes, meaning, description, customId, visitNodeFn);
- this._setMessageId(message, meta);
- this._setLegacyIds(message, meta);
- return message;
- }
- visitAllWithErrors(nodes) {
- const result = nodes.map((node) => node.visit(this, null));
- return new ParseTreeResult(result, this._errors);
- }
- visitElement(element) {
- let message = undefined;
- if (hasI18nAttrs(element)) {
- this.hasI18nMeta = true;
- const attrs = [];
- const attrsMeta = {};
- for (const attr of element.attrs) {
- if (attr.name === I18N_ATTR) {
- // root 'i18n' node attribute
- const i18n = element.i18n || attr.value;
- // Generate a new AST with whitespace trimmed, but also generate a map
- // to correlate each new node to its original so we can apply i18n
- // information to the original node based on the trimmed content.
- //
- // `WhitespaceVisitor` removes *insignificant* whitespace as well as
- // significant whitespace. Enabling this visitor should be conditional
- // on `preserveWhitespace` rather than `preserveSignificantWhitespace`,
- // however this would be a breaking change for existing behavior where
- // `preserveWhitespace` was not respected correctly when generating
- // message IDs. This is really a bug but one we need to keep to maintain
- // backwards compatibility.
- const originalNodeMap = new Map();
- const trimmedNodes = this.preserveSignificantWhitespace
- ? element.children
- : visitAllWithSiblings(new WhitespaceVisitor(false /* preserveSignificantWhitespace */, originalNodeMap), element.children);
- message = this._generateI18nMessage(trimmedNodes, i18n, setI18nRefs(originalNodeMap));
- if (message.nodes.length === 0) {
- // Ignore the message if it is empty.
- message = undefined;
- }
- // Store the message on the element
- element.i18n = message;
- }
- else if (attr.name.startsWith(I18N_ATTR_PREFIX)) {
- // 'i18n-*' attributes
- const name = attr.name.slice(I18N_ATTR_PREFIX.length);
- if (isTrustedTypesSink(element.name, name)) {
- this._reportError(attr, `Translating attribute '${name}' is disallowed for security reasons.`);
- }
- else {
- attrsMeta[name] = attr.value;
- }
- }
- else {
- // non-i18n attributes
- attrs.push(attr);
- }
- }
- // set i18n meta for attributes
- if (Object.keys(attrsMeta).length) {
- for (const attr of attrs) {
- const meta = attrsMeta[attr.name];
- // do not create translation for empty attributes
- if (meta !== undefined && attr.value) {
- attr.i18n = this._generateI18nMessage([attr], attr.i18n || meta);
- }
- }
- }
- if (!this.keepI18nAttrs) {
- // update element's attributes,
- // keeping only non-i18n related ones
- element.attrs = attrs;
- }
- }
- visitAll(this, element.children, message);
- return element;
- }
- visitExpansion(expansion, currentMessage) {
- let message;
- const meta = expansion.i18n;
- this.hasI18nMeta = true;
- if (meta instanceof IcuPlaceholder) {
- // set ICU placeholder name (e.g. "ICU_1"),
- // generated while processing root element contents,
- // so we can reference it when we output translation
- const name = meta.name;
- message = this._generateI18nMessage([expansion], meta);
- const icu = icuFromI18nMessage(message);
- icu.name = name;
- if (currentMessage !== null) {
- // Also update the placeholderToMessage map with this new message
- currentMessage.placeholderToMessage[name] = message;
- }
- }
- else {
- // ICU is a top level message, try to use metadata from container element if provided via
- // `context` argument. Note: context may not be available for standalone ICUs (without
- // wrapping element), so fallback to ICU metadata in this case.
- message = this._generateI18nMessage([expansion], currentMessage || meta);
- }
- expansion.i18n = message;
- return expansion;
- }
- visitText(text) {
- return text;
- }
- visitAttribute(attribute) {
- return attribute;
- }
- visitComment(comment) {
- return comment;
- }
- visitExpansionCase(expansionCase) {
- return expansionCase;
- }
- visitBlock(block, context) {
- visitAll(this, block.children, context);
- return block;
- }
- visitBlockParameter(parameter, context) {
- return parameter;
- }
- visitLetDeclaration(decl, context) {
- return decl;
- }
- /**
- * Parse the general form `meta` passed into extract the explicit metadata needed to create a
- * `Message`.
- *
- * There are three possibilities for the `meta` variable
- * 1) a string from an `i18n` template attribute: parse it to extract the metadata values.
- * 2) a `Message` from a previous processing pass: reuse the metadata values in the message.
- * 4) other: ignore this and just process the message metadata as normal
- *
- * @param meta the bucket that holds information about the message
- * @returns the parsed metadata.
- */
- _parseMetadata(meta) {
- return typeof meta === 'string'
- ? parseI18nMeta(meta)
- : meta instanceof Message
- ? meta
- : {};
- }
- /**
- * Generate (or restore) message id if not specified already.
- */
- _setMessageId(message, meta) {
- if (!message.id) {
- message.id = (meta instanceof Message && meta.id) || decimalDigest(message);
- }
- }
- /**
- * Update the `message` with a `legacyId` if necessary.
- *
- * @param message the message whose legacy id should be set
- * @param meta information about the message being processed
- */
- _setLegacyIds(message, meta) {
- if (this.enableI18nLegacyMessageIdFormat) {
- message.legacyIds = [computeDigest(message), computeDecimalDigest(message)];
- }
- else if (typeof meta !== 'string') {
- // This occurs if we are doing the 2nd pass after whitespace removal (see `parseTemplate()` in
- // `packages/compiler/src/render3/view/template.ts`).
- // In that case we want to reuse the legacy message generated in the 1st pass (see
- // `setI18nRefs()`).
- const previousMessage = meta instanceof Message
- ? meta
- : meta instanceof IcuPlaceholder
- ? meta.previousMessage
- : undefined;
- message.legacyIds = previousMessage ? previousMessage.legacyIds : [];
- }
- }
- _reportError(node, msg) {
- this._errors.push(new I18nError(node.sourceSpan, msg));
- }
- }
- /** I18n separators for metadata **/
- const I18N_MEANING_SEPARATOR = '|';
- const I18N_ID_SEPARATOR = '@@';
- /**
- * Parses i18n metas like:
- * - "@@id",
- * - "description[@@id]",
- * - "meaning|description[@@id]"
- * and returns an object with parsed output.
- *
- * @param meta String that represents i18n meta
- * @returns Object with id, meaning and description fields
- */
- function parseI18nMeta(meta = '') {
- let customId;
- let meaning;
- let description;
- meta = meta.trim();
- if (meta) {
- const idIndex = meta.indexOf(I18N_ID_SEPARATOR);
- const descIndex = meta.indexOf(I18N_MEANING_SEPARATOR);
- let meaningAndDesc;
- [meaningAndDesc, customId] =
- idIndex > -1 ? [meta.slice(0, idIndex), meta.slice(idIndex + 2)] : [meta, ''];
- [meaning, description] =
- descIndex > -1
- ? [meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)]
- : ['', meaningAndDesc];
- }
- return { customId, meaning, description };
- }
- // Converts i18n meta information for a message (id, description, meaning)
- // to a JsDoc statement formatted as expected by the Closure compiler.
- function i18nMetaToJSDoc(meta) {
- const tags = [];
- if (meta.description) {
- tags.push({ tagName: "desc" /* o.JSDocTagName.Desc */, text: meta.description });
- }
- else {
- // Suppress the JSCompiler warning that a `@desc` was not given for this message.
- tags.push({ tagName: "suppress" /* o.JSDocTagName.Suppress */, text: '{msgDescriptions}' });
- }
- if (meta.meaning) {
- tags.push({ tagName: "meaning" /* o.JSDocTagName.Meaning */, text: meta.meaning });
- }
- return jsDocComment(tags);
- }
- /** Closure uses `goog.getMsg(message)` to lookup translations */
- const GOOG_GET_MSG = 'goog.getMsg';
- /**
- * Generates a `goog.getMsg()` statement and reassignment. The template:
- *
- * ```html
- * <div i18n>Sent from {{ sender }} to <span class="receiver">{{ receiver }}</span></div>
- * ```
- *
- * Generates:
- *
- * ```ts
- * const MSG_FOO = goog.getMsg(
- * // Message template.
- * 'Sent from {$interpolation} to {$startTagSpan}{$interpolation_1}{$closeTagSpan}.',
- * // Placeholder values, set to magic strings which get replaced by the Angular runtime.
- * {
- * 'interpolation': '\uFFFD0\uFFFD',
- * 'startTagSpan': '\uFFFD1\uFFFD',
- * 'interpolation_1': '\uFFFD2\uFFFD',
- * 'closeTagSpan': '\uFFFD3\uFFFD',
- * },
- * // Options bag.
- * {
- * // Maps each placeholder to the original Angular source code which generates it's value.
- * original_code: {
- * 'interpolation': '{{ sender }}',
- * 'startTagSpan': '<span class="receiver">',
- * 'interpolation_1': '{{ receiver }}',
- * 'closeTagSpan': '</span>',
- * },
- * },
- * );
- * const I18N_0 = MSG_FOO;
- * ```
- */
- function createGoogleGetMsgStatements(variable$1, message, closureVar, placeholderValues) {
- const messageString = serializeI18nMessageForGetMsg(message);
- const args = [literal$1(messageString)];
- if (Object.keys(placeholderValues).length) {
- // Message template parameters containing the magic strings replaced by the Angular runtime with
- // real data, e.g. `{'interpolation': '\uFFFD0\uFFFD'}`.
- args.push(mapLiteral(formatI18nPlaceholderNamesInMap(placeholderValues, true /* useCamelCase */), true /* quoted */));
- // Message options object, which contains original source code for placeholders (as they are
- // present in a template, e.g.
- // `{original_code: {'interpolation': '{{ name }}', 'startTagSpan': '<span>'}}`.
- args.push(mapLiteral({
- original_code: literalMap(Object.keys(placeholderValues).map((param) => ({
- key: formatI18nPlaceholderName(param),
- quoted: true,
- value: message.placeholders[param]
- ? // Get source span for typical placeholder if it exists.
- literal$1(message.placeholders[param].sourceSpan.toString())
- : // Otherwise must be an ICU expression, get it's source span.
- literal$1(message.placeholderToMessage[param].nodes
- .map((node) => node.sourceSpan.toString())
- .join('')),
- }))),
- }));
- }
- // /**
- // * @desc description of message
- // * @meaning meaning of message
- // */
- // const MSG_... = goog.getMsg(..);
- // I18N_X = MSG_...;
- const googGetMsgStmt = closureVar.set(variable(GOOG_GET_MSG).callFn(args)).toConstDecl();
- googGetMsgStmt.addLeadingComment(i18nMetaToJSDoc(message));
- const i18nAssignmentStmt = new ExpressionStatement(variable$1.set(closureVar));
- return [googGetMsgStmt, i18nAssignmentStmt];
- }
- /**
- * This visitor walks over i18n tree and generates its string representation, including ICUs and
- * placeholders in `{$placeholder}` (for plain messages) or `{PLACEHOLDER}` (inside ICUs) format.
- */
- class GetMsgSerializerVisitor {
- formatPh(value) {
- return `{$${formatI18nPlaceholderName(value)}}`;
- }
- visitText(text) {
- return text.value;
- }
- visitContainer(container) {
- return container.children.map((child) => child.visit(this)).join('');
- }
- visitIcu(icu) {
- return serializeIcuNode(icu);
- }
- visitTagPlaceholder(ph) {
- return ph.isVoid
- ? this.formatPh(ph.startName)
- : `${this.formatPh(ph.startName)}${ph.children
- .map((child) => child.visit(this))
- .join('')}${this.formatPh(ph.closeName)}`;
- }
- visitPlaceholder(ph) {
- return this.formatPh(ph.name);
- }
- visitBlockPlaceholder(ph) {
- return `${this.formatPh(ph.startName)}${ph.children
- .map((child) => child.visit(this))
- .join('')}${this.formatPh(ph.closeName)}`;
- }
- visitIcuPlaceholder(ph, context) {
- return this.formatPh(ph.name);
- }
- }
- const serializerVisitor = new GetMsgSerializerVisitor();
- function serializeI18nMessageForGetMsg(message) {
- return message.nodes.map((node) => node.visit(serializerVisitor, null)).join('');
- }
- function createLocalizeStatements(variable, message, params) {
- const { messageParts, placeHolders } = serializeI18nMessageForLocalize(message);
- const sourceSpan = getSourceSpan(message);
- const expressions = placeHolders.map((ph) => params[ph.text]);
- const localizedString$1 = localizedString(message, messageParts, placeHolders, expressions, sourceSpan);
- const variableInitialization = variable.set(localizedString$1);
- return [new ExpressionStatement(variableInitialization)];
- }
- /**
- * This visitor walks over an i18n tree, capturing literal strings and placeholders.
- *
- * The result can be used for generating the `$localize` tagged template literals.
- */
- class LocalizeSerializerVisitor {
- placeholderToMessage;
- pieces;
- constructor(placeholderToMessage, pieces) {
- this.placeholderToMessage = placeholderToMessage;
- this.pieces = pieces;
- }
- visitText(text) {
- if (this.pieces[this.pieces.length - 1] instanceof LiteralPiece) {
- // Two literal pieces in a row means that there was some comment node in-between.
- this.pieces[this.pieces.length - 1].text += text.value;
- }
- else {
- const sourceSpan = new ParseSourceSpan(text.sourceSpan.fullStart, text.sourceSpan.end, text.sourceSpan.fullStart, text.sourceSpan.details);
- this.pieces.push(new LiteralPiece(text.value, sourceSpan));
- }
- }
- visitContainer(container) {
- container.children.forEach((child) => child.visit(this));
- }
- visitIcu(icu) {
- this.pieces.push(new LiteralPiece(serializeIcuNode(icu), icu.sourceSpan));
- }
- visitTagPlaceholder(ph) {
- this.pieces.push(this.createPlaceholderPiece(ph.startName, ph.startSourceSpan ?? ph.sourceSpan));
- if (!ph.isVoid) {
- ph.children.forEach((child) => child.visit(this));
- this.pieces.push(this.createPlaceholderPiece(ph.closeName, ph.endSourceSpan ?? ph.sourceSpan));
- }
- }
- visitPlaceholder(ph) {
- this.pieces.push(this.createPlaceholderPiece(ph.name, ph.sourceSpan));
- }
- visitBlockPlaceholder(ph) {
- this.pieces.push(this.createPlaceholderPiece(ph.startName, ph.startSourceSpan ?? ph.sourceSpan));
- ph.children.forEach((child) => child.visit(this));
- this.pieces.push(this.createPlaceholderPiece(ph.closeName, ph.endSourceSpan ?? ph.sourceSpan));
- }
- visitIcuPlaceholder(ph) {
- this.pieces.push(this.createPlaceholderPiece(ph.name, ph.sourceSpan, this.placeholderToMessage[ph.name]));
- }
- createPlaceholderPiece(name, sourceSpan, associatedMessage) {
- return new PlaceholderPiece(formatI18nPlaceholderName(name, /* useCamelCase */ false), sourceSpan, associatedMessage);
- }
- }
- /**
- * Serialize an i18n message into two arrays: messageParts and placeholders.
- *
- * These arrays will be used to generate `$localize` tagged template literals.
- *
- * @param message The message to be serialized.
- * @returns an object containing the messageParts and placeholders.
- */
- function serializeI18nMessageForLocalize(message) {
- const pieces = [];
- const serializerVisitor = new LocalizeSerializerVisitor(message.placeholderToMessage, pieces);
- message.nodes.forEach((node) => node.visit(serializerVisitor));
- return processMessagePieces(pieces);
- }
- function getSourceSpan(message) {
- const startNode = message.nodes[0];
- const endNode = message.nodes[message.nodes.length - 1];
- return new ParseSourceSpan(startNode.sourceSpan.fullStart, endNode.sourceSpan.end, startNode.sourceSpan.fullStart, startNode.sourceSpan.details);
- }
- /**
- * Convert the list of serialized MessagePieces into two arrays.
- *
- * One contains the literal string pieces and the other the placeholders that will be replaced by
- * expressions when rendering `$localize` tagged template literals.
- *
- * @param pieces The pieces to process.
- * @returns an object containing the messageParts and placeholders.
- */
- function processMessagePieces(pieces) {
- const messageParts = [];
- const placeHolders = [];
- if (pieces[0] instanceof PlaceholderPiece) {
- // The first piece was a placeholder so we need to add an initial empty message part.
- messageParts.push(createEmptyMessagePart(pieces[0].sourceSpan.start));
- }
- for (let i = 0; i < pieces.length; i++) {
- const part = pieces[i];
- if (part instanceof LiteralPiece) {
- messageParts.push(part);
- }
- else {
- placeHolders.push(part);
- if (pieces[i - 1] instanceof PlaceholderPiece) {
- // There were two placeholders in a row, so we need to add an empty message part.
- messageParts.push(createEmptyMessagePart(pieces[i - 1].sourceSpan.end));
- }
- }
- }
- if (pieces[pieces.length - 1] instanceof PlaceholderPiece) {
- // The last piece was a placeholder so we need to add a final empty message part.
- messageParts.push(createEmptyMessagePart(pieces[pieces.length - 1].sourceSpan.end));
- }
- return { messageParts, placeHolders };
- }
- function createEmptyMessagePart(location) {
- return new LiteralPiece('', new ParseSourceSpan(location, location));
- }
- /** Name of the global variable that is used to determine if we use Closure translations or not */
- const NG_I18N_CLOSURE_MODE = 'ngI18nClosureMode';
- /**
- * Prefix for non-`goog.getMsg` i18n-related vars.
- * Note: the prefix uses lowercase characters intentionally due to a Closure behavior that
- * considers variables like `I18N_0` as constants and throws an error when their value changes.
- */
- const TRANSLATION_VAR_PREFIX = 'i18n_';
- /** Prefix of ICU expressions for post processing */
- const I18N_ICU_MAPPING_PREFIX = 'I18N_EXP_';
- /**
- * The escape sequence used for message param values.
- */
- const ESCAPE = '\uFFFD';
- /* Closure variables holding messages must be named `MSG_[A-Z0-9]+` */
- const CLOSURE_TRANSLATION_VAR_PREFIX = 'MSG_';
- /**
- * Generates a prefix for translation const name.
- *
- * @param extra Additional local prefix that should be injected into translation var name
- * @returns Complete translation const prefix
- */
- function getTranslationConstPrefix(extra) {
- return `${CLOSURE_TRANSLATION_VAR_PREFIX}${extra}`.toUpperCase();
- }
- /**
- * Generate AST to declare a variable. E.g. `var I18N_1;`.
- * @param variable the name of the variable to declare.
- */
- function declareI18nVariable(variable) {
- return new DeclareVarStmt(variable.name, undefined, INFERRED_TYPE, undefined, variable.sourceSpan);
- }
- /**
- * Lifts i18n properties into the consts array.
- * TODO: Can we use `ConstCollectedExpr`?
- * TODO: The way the various attributes are linked together is very complex. Perhaps we could
- * simplify the process, maybe by combining the context and message ops?
- */
- function collectI18nConsts(job) {
- const fileBasedI18nSuffix = job.relativeContextFilePath.replace(/[^A-Za-z0-9]/g, '_').toUpperCase() + '_';
- // Step One: Build up various lookup maps we need to collect all the consts.
- // Context Xref -> Extracted Attribute Ops
- const extractedAttributesByI18nContext = new Map();
- // Element/ElementStart Xref -> I18n Attributes config op
- const i18nAttributesByElement = new Map();
- // Element/ElementStart Xref -> All I18n Expression ops for attrs on that target
- const i18nExpressionsByElement = new Map();
- // I18n Message Xref -> I18n Message Op (TODO: use a central op map)
- const messages = new Map();
- for (const unit of job.units) {
- for (const op of unit.ops()) {
- if (op.kind === OpKind.ExtractedAttribute && op.i18nContext !== null) {
- const attributes = extractedAttributesByI18nContext.get(op.i18nContext) ?? [];
- attributes.push(op);
- extractedAttributesByI18nContext.set(op.i18nContext, attributes);
- }
- else if (op.kind === OpKind.I18nAttributes) {
- i18nAttributesByElement.set(op.target, op);
- }
- else if (op.kind === OpKind.I18nExpression &&
- op.usage === I18nExpressionFor.I18nAttribute) {
- const expressions = i18nExpressionsByElement.get(op.target) ?? [];
- expressions.push(op);
- i18nExpressionsByElement.set(op.target, expressions);
- }
- else if (op.kind === OpKind.I18nMessage) {
- messages.set(op.xref, op);
- }
- }
- }
- // Step Two: Serialize the extracted i18n messages for root i18n blocks and i18n attributes into
- // the const array.
- //
- // Also, each i18n message will have a variable expression that can refer to its
- // value. Store these expressions in the appropriate place:
- // 1. For normal i18n content, it also goes in the const array. We save the const index to use
- // later.
- // 2. For extracted attributes, it becomes the value of the extracted attribute instruction.
- // 3. For i18n bindings, it will go in a separate const array instruction below; for now, we just
- // save it.
- const i18nValuesByContext = new Map();
- const messageConstIndices = new Map();
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.I18nMessage) {
- if (op.messagePlaceholder === null) {
- const { mainVar, statements } = collectMessage(job, fileBasedI18nSuffix, messages, op);
- if (op.i18nBlock !== null) {
- // This is a regular i18n message with a corresponding i18n block. Collect it into the
- // const array.
- const i18nConst = job.addConst(mainVar, statements);
- messageConstIndices.set(op.i18nBlock, i18nConst);
- }
- else {
- // This is an i18n attribute. Extract the initializers into the const pool.
- job.constsInitializers.push(...statements);
- // Save the i18n variable value for later.
- i18nValuesByContext.set(op.i18nContext, mainVar);
- // This i18n message may correspond to an individual extracted attribute. If so, The
- // value of that attribute is updated to read the extracted i18n variable.
- const attributesForMessage = extractedAttributesByI18nContext.get(op.i18nContext);
- if (attributesForMessage !== undefined) {
- for (const attr of attributesForMessage) {
- attr.expression = mainVar.clone();
- }
- }
- }
- }
- OpList.remove(op);
- }
- }
- }
- // Step Three: Serialize I18nAttributes configurations into the const array. Each I18nAttributes
- // instruction has a config array, which contains k-v pairs describing each binding name, and the
- // i18n variable that provides the value.
- for (const unit of job.units) {
- for (const elem of unit.create) {
- if (isElementOrContainerOp(elem)) {
- const i18nAttributes = i18nAttributesByElement.get(elem.xref);
- if (i18nAttributes === undefined) {
- // This element is not associated with an i18n attributes configuration instruction.
- continue;
- }
- let i18nExpressions = i18nExpressionsByElement.get(elem.xref);
- if (i18nExpressions === undefined) {
- // Unused i18nAttributes should have already been removed.
- // TODO: Should the removal of those dead instructions be merged with this phase?
- throw new Error('AssertionError: Could not find any i18n expressions associated with an I18nAttributes instruction');
- }
- // Find expressions for all the unique property names, removing duplicates.
- const seenPropertyNames = new Set();
- i18nExpressions = i18nExpressions.filter((i18nExpr) => {
- const seen = seenPropertyNames.has(i18nExpr.name);
- seenPropertyNames.add(i18nExpr.name);
- return !seen;
- });
- const i18nAttributeConfig = i18nExpressions.flatMap((i18nExpr) => {
- const i18nExprValue = i18nValuesByContext.get(i18nExpr.context);
- if (i18nExprValue === undefined) {
- throw new Error("AssertionError: Could not find i18n expression's value");
- }
- return [literal$1(i18nExpr.name), i18nExprValue];
- });
- i18nAttributes.i18nAttributesConfig = job.addConst(new LiteralArrayExpr(i18nAttributeConfig));
- }
- }
- }
- // Step Four: Propagate the extracted const index into i18n ops that messages were extracted from.
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.I18nStart) {
- const msgIndex = messageConstIndices.get(op.root);
- if (msgIndex === undefined) {
- throw new Error('AssertionError: Could not find corresponding i18n block index for an i18n message op; was an i18n message incorrectly assumed to correspond to an attribute?');
- }
- op.messageIndex = msgIndex;
- }
- }
- }
- }
- /**
- * Collects the given message into a set of statements that can be added to the const array.
- * This will recursively collect any sub-messages referenced from the parent message as well.
- */
- function collectMessage(job, fileBasedI18nSuffix, messages, messageOp) {
- // Recursively collect any sub-messages, record each sub-message's main variable under its
- // placeholder so that we can add them to the params for the parent message. It is possible
- // that multiple sub-messages will share the same placeholder, so we need to track an array of
- // variables for each placeholder.
- const statements = [];
- const subMessagePlaceholders = new Map();
- for (const subMessageId of messageOp.subMessages) {
- const subMessage = messages.get(subMessageId);
- const { mainVar: subMessageVar, statements: subMessageStatements } = collectMessage(job, fileBasedI18nSuffix, messages, subMessage);
- statements.push(...subMessageStatements);
- const subMessages = subMessagePlaceholders.get(subMessage.messagePlaceholder) ?? [];
- subMessages.push(subMessageVar);
- subMessagePlaceholders.set(subMessage.messagePlaceholder, subMessages);
- }
- addSubMessageParams(messageOp, subMessagePlaceholders);
- // Sort the params for consistency with TemaplateDefinitionBuilder output.
- messageOp.params = new Map([...messageOp.params.entries()].sort());
- const mainVar = variable(job.pool.uniqueName(TRANSLATION_VAR_PREFIX));
- // Closure Compiler requires const names to start with `MSG_` but disallows any other
- // const to start with `MSG_`. We define a variable starting with `MSG_` just for the
- // `goog.getMsg` call
- const closureVar = i18nGenerateClosureVar(job.pool, messageOp.message.id, fileBasedI18nSuffix, job.i18nUseExternalIds);
- let transformFn = undefined;
- // If nescessary, add a post-processing step and resolve any placeholder params that are
- // set in post-processing.
- if (messageOp.needsPostprocessing || messageOp.postprocessingParams.size > 0) {
- // Sort the post-processing params for consistency with TemaplateDefinitionBuilder output.
- const postprocessingParams = Object.fromEntries([...messageOp.postprocessingParams.entries()].sort());
- const formattedPostprocessingParams = formatI18nPlaceholderNamesInMap(postprocessingParams,
- /* useCamelCase */ false);
- const extraTransformFnParams = [];
- if (messageOp.postprocessingParams.size > 0) {
- extraTransformFnParams.push(mapLiteral(formattedPostprocessingParams, /* quoted */ true));
- }
- transformFn = (expr) => importExpr(Identifiers.i18nPostprocess).callFn([expr, ...extraTransformFnParams]);
- }
- // Add the message's statements
- statements.push(...getTranslationDeclStmts(messageOp.message, mainVar, closureVar, messageOp.params, transformFn));
- return { mainVar, statements };
- }
- /**
- * Adds the given subMessage placeholders to the given message op.
- *
- * If a placeholder only corresponds to a single sub-message variable, we just set that variable
- * as the param value. However, if the placeholder corresponds to multiple sub-message
- * variables, we need to add a special placeholder value that is handled by the post-processing
- * step. We then add the array of variables as a post-processing param.
- */
- function addSubMessageParams(messageOp, subMessagePlaceholders) {
- for (const [placeholder, subMessages] of subMessagePlaceholders) {
- if (subMessages.length === 1) {
- messageOp.params.set(placeholder, subMessages[0]);
- }
- else {
- messageOp.params.set(placeholder, literal$1(`${ESCAPE}${I18N_ICU_MAPPING_PREFIX}${placeholder}${ESCAPE}`));
- messageOp.postprocessingParams.set(placeholder, literalArr(subMessages));
- }
- }
- }
- /**
- * Generate statements that define a given translation message.
- *
- * ```ts
- * var I18N_1;
- * if (typeof ngI18nClosureMode !== undefined && ngI18nClosureMode) {
- * var MSG_EXTERNAL_XXX = goog.getMsg(
- * "Some message with {$interpolation}!",
- * { "interpolation": "\uFFFD0\uFFFD" }
- * );
- * I18N_1 = MSG_EXTERNAL_XXX;
- * }
- * else {
- * I18N_1 = $localize`Some message with ${'\uFFFD0\uFFFD'}!`;
- * }
- * ```
- *
- * @param message The original i18n AST message node
- * @param variable The variable that will be assigned the translation, e.g. `I18N_1`.
- * @param closureVar The variable for Closure `goog.getMsg` calls, e.g. `MSG_EXTERNAL_XXX`.
- * @param params Object mapping placeholder names to their values (e.g.
- * `{ "interpolation": "\uFFFD0\uFFFD" }`).
- * @param transformFn Optional transformation function that will be applied to the translation
- * (e.g.
- * post-processing).
- * @returns An array of statements that defined a given translation.
- */
- function getTranslationDeclStmts(message, variable, closureVar, params, transformFn) {
- const paramsObject = Object.fromEntries(params);
- const statements = [
- declareI18nVariable(variable),
- ifStmt(createClosureModeGuard(), createGoogleGetMsgStatements(variable, message, closureVar, paramsObject), createLocalizeStatements(variable, message, formatI18nPlaceholderNamesInMap(paramsObject, /* useCamelCase */ false))),
- ];
- if (transformFn) {
- statements.push(new ExpressionStatement(variable.set(transformFn(variable))));
- }
- return statements;
- }
- /**
- * Create the expression that will be used to guard the closure mode block
- * It is equivalent to:
- *
- * ```ts
- * typeof ngI18nClosureMode !== undefined && ngI18nClosureMode
- * ```
- */
- function createClosureModeGuard() {
- return typeofExpr(variable(NG_I18N_CLOSURE_MODE))
- .notIdentical(literal$1('undefined', STRING_TYPE))
- .and(variable(NG_I18N_CLOSURE_MODE));
- }
- /**
- * Generates vars with Closure-specific names for i18n blocks (i.e. `MSG_XXX`).
- */
- function i18nGenerateClosureVar(pool, messageId, fileBasedI18nSuffix, useExternalIds) {
- let name;
- const suffix = fileBasedI18nSuffix;
- if (useExternalIds) {
- const prefix = getTranslationConstPrefix(`EXTERNAL_`);
- const uniqueSuffix = pool.uniqueName(suffix);
- name = `${prefix}${sanitizeIdentifier(messageId)}$$${uniqueSuffix}`;
- }
- else {
- const prefix = getTranslationConstPrefix(suffix);
- name = pool.uniqueName(prefix);
- }
- return variable(name);
- }
- /**
- * Removes text nodes within i18n blocks since they are already hardcoded into the i18n message.
- * Also, replaces interpolations on these text nodes with i18n expressions of the non-text portions,
- * which will be applied later.
- */
- function convertI18nText(job) {
- for (const unit of job.units) {
- // Remove all text nodes within i18n blocks, their content is already captured in the i18n
- // message.
- let currentI18n = null;
- let currentIcu = null;
- const textNodeI18nBlocks = new Map();
- const textNodeIcus = new Map();
- const icuPlaceholderByText = new Map();
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nStart:
- if (op.context === null) {
- throw Error('I18n op should have its context set.');
- }
- currentI18n = op;
- break;
- case OpKind.I18nEnd:
- currentI18n = null;
- break;
- case OpKind.IcuStart:
- if (op.context === null) {
- throw Error('Icu op should have its context set.');
- }
- currentIcu = op;
- break;
- case OpKind.IcuEnd:
- currentIcu = null;
- break;
- case OpKind.Text:
- if (currentI18n !== null) {
- textNodeI18nBlocks.set(op.xref, currentI18n);
- textNodeIcus.set(op.xref, currentIcu);
- if (op.icuPlaceholder !== null) {
- // Create an op to represent the ICU placeholder. Initially set its static text to the
- // value of the text op, though this may be overwritten later if this text op is a
- // placeholder for an interpolation.
- const icuPlaceholderOp = createIcuPlaceholderOp(job.allocateXrefId(), op.icuPlaceholder, [op.initialValue]);
- OpList.replace(op, icuPlaceholderOp);
- icuPlaceholderByText.set(op.xref, icuPlaceholderOp);
- }
- else {
- // Otherwise just remove the text op, since its value is already accounted for in the
- // translated message.
- OpList.remove(op);
- }
- }
- break;
- }
- }
- // Update any interpolations to the removed text, and instead represent them as a series of i18n
- // expressions that we then apply.
- for (const op of unit.update) {
- switch (op.kind) {
- case OpKind.InterpolateText:
- if (!textNodeI18nBlocks.has(op.target)) {
- continue;
- }
- const i18nOp = textNodeI18nBlocks.get(op.target);
- const icuOp = textNodeIcus.get(op.target);
- const icuPlaceholder = icuPlaceholderByText.get(op.target);
- const contextId = icuOp ? icuOp.context : i18nOp.context;
- const resolutionTime = icuOp
- ? I18nParamResolutionTime.Postproccessing
- : I18nParamResolutionTime.Creation;
- const ops = [];
- for (let i = 0; i < op.interpolation.expressions.length; i++) {
- const expr = op.interpolation.expressions[i];
- // For now, this i18nExpression depends on the slot context of the enclosing i18n block.
- // Later, we will modify this, and advance to a different point.
- ops.push(createI18nExpressionOp(contextId, i18nOp.xref, i18nOp.xref, i18nOp.handle, expr, icuPlaceholder?.xref ?? null, op.interpolation.i18nPlaceholders[i] ?? null, resolutionTime, I18nExpressionFor.I18nText, '', expr.sourceSpan ?? op.sourceSpan));
- }
- OpList.replaceWithMany(op, ops);
- // If this interpolation is part of an ICU placeholder, add the strings and expressions to
- // the placeholder.
- if (icuPlaceholder !== undefined) {
- icuPlaceholder.strings = op.interpolation.strings;
- }
- break;
- }
- }
- }
- }
- /**
- * Lifts local reference declarations on element-like structures within each view into an entry in
- * the `consts` array for the whole component.
- */
- function liftLocalRefs(job) {
- for (const unit of job.units) {
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.ElementStart:
- case OpKind.Template:
- if (!Array.isArray(op.localRefs)) {
- throw new Error(`AssertionError: expected localRefs to be an array still`);
- }
- op.numSlotsUsed += op.localRefs.length;
- if (op.localRefs.length > 0) {
- const localRefs = serializeLocalRefs(op.localRefs);
- op.localRefs = job.addConst(localRefs);
- }
- else {
- op.localRefs = null;
- }
- break;
- }
- }
- }
- }
- function serializeLocalRefs(refs) {
- const constRefs = [];
- for (const ref of refs) {
- constRefs.push(literal$1(ref.name), literal$1(ref.target));
- }
- return literalArr(constRefs);
- }
- /**
- * Change namespaces between HTML, SVG and MathML, depending on the next element.
- */
- function emitNamespaceChanges(job) {
- for (const unit of job.units) {
- let activeNamespace = Namespace.HTML;
- for (const op of unit.create) {
- if (op.kind !== OpKind.ElementStart) {
- continue;
- }
- if (op.namespace !== activeNamespace) {
- OpList.insertBefore(createNamespaceOp(op.namespace), op);
- activeNamespace = op.namespace;
- }
- }
- }
- }
- /**
- * Parses string representation of a style and converts it into object literal.
- *
- * @param value string representation of style as used in the `style` attribute in HTML.
- * Example: `color: red; height: auto`.
- * @returns An array of style property name and value pairs, e.g. `['color', 'red', 'height',
- * 'auto']`
- */
- function parse(value) {
- // we use a string array here instead of a string map
- // because a string-map is not guaranteed to retain the
- // order of the entries whereas a string array can be
- // constructed in a [key, value, key, value] format.
- const styles = [];
- let i = 0;
- let parenDepth = 0;
- let quote = 0 /* Char.QuoteNone */;
- let valueStart = 0;
- let propStart = 0;
- let currentProp = null;
- while (i < value.length) {
- const token = value.charCodeAt(i++);
- switch (token) {
- case 40 /* Char.OpenParen */:
- parenDepth++;
- break;
- case 41 /* Char.CloseParen */:
- parenDepth--;
- break;
- case 39 /* Char.QuoteSingle */:
- // valueStart needs to be there since prop values don't
- // have quotes in CSS
- if (quote === 0 /* Char.QuoteNone */) {
- quote = 39 /* Char.QuoteSingle */;
- }
- else if (quote === 39 /* Char.QuoteSingle */ && value.charCodeAt(i - 1) !== 92 /* Char.BackSlash */) {
- quote = 0 /* Char.QuoteNone */;
- }
- break;
- case 34 /* Char.QuoteDouble */:
- // same logic as above
- if (quote === 0 /* Char.QuoteNone */) {
- quote = 34 /* Char.QuoteDouble */;
- }
- else if (quote === 34 /* Char.QuoteDouble */ && value.charCodeAt(i - 1) !== 92 /* Char.BackSlash */) {
- quote = 0 /* Char.QuoteNone */;
- }
- break;
- case 58 /* Char.Colon */:
- if (!currentProp && parenDepth === 0 && quote === 0 /* Char.QuoteNone */) {
- // TODO: Do not hyphenate CSS custom property names like: `--intentionallyCamelCase`
- currentProp = hyphenate(value.substring(propStart, i - 1).trim());
- valueStart = i;
- }
- break;
- case 59 /* Char.Semicolon */:
- if (currentProp && valueStart > 0 && parenDepth === 0 && quote === 0 /* Char.QuoteNone */) {
- const styleVal = value.substring(valueStart, i - 1).trim();
- styles.push(currentProp, styleVal);
- propStart = i;
- valueStart = 0;
- currentProp = null;
- }
- break;
- }
- }
- if (currentProp && valueStart) {
- const styleVal = value.slice(valueStart).trim();
- styles.push(currentProp, styleVal);
- }
- return styles;
- }
- function hyphenate(value) {
- return value
- .replace(/[a-z][A-Z]/g, (v) => {
- return v.charAt(0) + '-' + v.charAt(1);
- })
- .toLowerCase();
- }
- /**
- * Parses extracted style and class attributes into separate ExtractedAttributeOps per style or
- * class property.
- */
- function parseExtractedStyles(job) {
- const elements = new Map();
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (isElementOrContainerOp(op)) {
- elements.set(op.xref, op);
- }
- }
- }
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.ExtractedAttribute &&
- op.bindingKind === BindingKind.Attribute &&
- isStringLiteral(op.expression)) {
- const target = elements.get(op.target);
- if (target !== undefined &&
- target.kind === OpKind.Template &&
- target.templateKind === TemplateKind.Structural) {
- // TemplateDefinitionBuilder will not apply class and style bindings to structural
- // directives; instead, it will leave them as attributes.
- // (It's not clear what that would mean, anyway -- classes and styles on a structural
- // element should probably be a parse error.)
- // TODO: We may be able to remove this once Template Pipeline is the default.
- continue;
- }
- if (op.name === 'style') {
- const parsedStyles = parse(op.expression.value);
- for (let i = 0; i < parsedStyles.length - 1; i += 2) {
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.StyleProperty, null, parsedStyles[i], literal$1(parsedStyles[i + 1]), null, null, SecurityContext.STYLE), op);
- }
- OpList.remove(op);
- }
- else if (op.name === 'class') {
- const parsedClasses = op.expression.value.trim().split(/\s+/g);
- for (const parsedClass of parsedClasses) {
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.ClassName, null, parsedClass, null, null, null, SecurityContext.NONE), op);
- }
- OpList.remove(op);
- }
- }
- }
- }
- }
- /**
- * Generate names for functions and variables across all views.
- *
- * This includes propagating those names into any `ir.ReadVariableExpr`s of those variables, so that
- * the reads can be emitted correctly.
- */
- function nameFunctionsAndVariables(job) {
- addNamesToView(job.root, job.componentName, { index: 0 }, job.compatibility === CompatibilityMode.TemplateDefinitionBuilder);
- }
- function addNamesToView(unit, baseName, state, compatibility) {
- if (unit.fnName === null) {
- // Ensure unique names for view units. This is necessary because there might be multiple
- // components with same names in the context of the same pool. Only add the suffix
- // if really needed.
- unit.fnName = unit.job.pool.uniqueName(sanitizeIdentifier(`${baseName}_${unit.job.fnSuffix}`),
- /* alwaysIncludeSuffix */ false);
- }
- // Keep track of the names we assign to variables in the view. We'll need to propagate these
- // into reads of those variables afterwards.
- const varNames = new Map();
- for (const op of unit.ops()) {
- switch (op.kind) {
- case OpKind.Property:
- case OpKind.HostProperty:
- if (op.isAnimationTrigger) {
- op.name = '@' + op.name;
- }
- break;
- case OpKind.Listener:
- if (op.handlerFnName !== null) {
- break;
- }
- if (!op.hostListener && op.targetSlot.slot === null) {
- throw new Error(`Expected a slot to be assigned`);
- }
- let animation = '';
- if (op.isAnimationListener) {
- op.name = `@${op.name}.${op.animationPhase}`;
- animation = 'animation';
- }
- if (op.hostListener) {
- op.handlerFnName = `${baseName}_${animation}${op.name}_HostBindingHandler`;
- }
- else {
- op.handlerFnName = `${unit.fnName}_${op.tag.replace('-', '_')}_${animation}${op.name}_${op.targetSlot.slot}_listener`;
- }
- op.handlerFnName = sanitizeIdentifier(op.handlerFnName);
- break;
- case OpKind.TwoWayListener:
- if (op.handlerFnName !== null) {
- break;
- }
- if (op.targetSlot.slot === null) {
- throw new Error(`Expected a slot to be assigned`);
- }
- op.handlerFnName = sanitizeIdentifier(`${unit.fnName}_${op.tag.replace('-', '_')}_${op.name}_${op.targetSlot.slot}_listener`);
- break;
- case OpKind.Variable:
- varNames.set(op.xref, getVariableName(unit, op.variable, state));
- break;
- case OpKind.RepeaterCreate:
- if (!(unit instanceof ViewCompilationUnit)) {
- throw new Error(`AssertionError: must be compiling a component`);
- }
- if (op.handle.slot === null) {
- throw new Error(`Expected slot to be assigned`);
- }
- if (op.emptyView !== null) {
- const emptyView = unit.job.views.get(op.emptyView);
- // Repeater empty view function is at slot +2 (metadata is in the first slot).
- addNamesToView(emptyView, `${baseName}_${op.functionNameSuffix}Empty_${op.handle.slot + 2}`, state, compatibility);
- }
- // Repeater primary view function is at slot +1 (metadata is in the first slot).
- addNamesToView(unit.job.views.get(op.xref), `${baseName}_${op.functionNameSuffix}_${op.handle.slot + 1}`, state, compatibility);
- break;
- case OpKind.Projection:
- if (!(unit instanceof ViewCompilationUnit)) {
- throw new Error(`AssertionError: must be compiling a component`);
- }
- if (op.handle.slot === null) {
- throw new Error(`Expected slot to be assigned`);
- }
- if (op.fallbackView !== null) {
- const fallbackView = unit.job.views.get(op.fallbackView);
- addNamesToView(fallbackView, `${baseName}_ProjectionFallback_${op.handle.slot}`, state, compatibility);
- }
- break;
- case OpKind.Template:
- if (!(unit instanceof ViewCompilationUnit)) {
- throw new Error(`AssertionError: must be compiling a component`);
- }
- const childView = unit.job.views.get(op.xref);
- if (op.handle.slot === null) {
- throw new Error(`Expected slot to be assigned`);
- }
- const suffix = op.functionNameSuffix.length === 0 ? '' : `_${op.functionNameSuffix}`;
- addNamesToView(childView, `${baseName}${suffix}_${op.handle.slot}`, state, compatibility);
- break;
- case OpKind.StyleProp:
- op.name = normalizeStylePropName(op.name);
- if (compatibility) {
- op.name = stripImportant(op.name);
- }
- break;
- case OpKind.ClassProp:
- if (compatibility) {
- op.name = stripImportant(op.name);
- }
- break;
- }
- }
- // Having named all variables declared in the view, now we can push those names into the
- // `ir.ReadVariableExpr` expressions which represent reads of those variables.
- for (const op of unit.ops()) {
- visitExpressionsInOp(op, (expr) => {
- if (!(expr instanceof ReadVariableExpr) || expr.name !== null) {
- return;
- }
- if (!varNames.has(expr.xref)) {
- throw new Error(`Variable ${expr.xref} not yet named`);
- }
- expr.name = varNames.get(expr.xref);
- });
- }
- }
- function getVariableName(unit, variable, state) {
- if (variable.name === null) {
- switch (variable.kind) {
- case SemanticVariableKind.Context:
- variable.name = `ctx_r${state.index++}`;
- break;
- case SemanticVariableKind.Identifier:
- if (unit.job.compatibility === CompatibilityMode.TemplateDefinitionBuilder) {
- // TODO: Prefix increment and `_r` are for compatibility with the old naming scheme.
- // This has the potential to cause collisions when `ctx` is the identifier, so we need a
- // special check for that as well.
- const compatPrefix = variable.identifier === 'ctx' ? 'i' : '';
- variable.name = `${variable.identifier}_${compatPrefix}r${++state.index}`;
- }
- else {
- variable.name = `${variable.identifier}_i${state.index++}`;
- }
- break;
- default:
- // TODO: Prefix increment for compatibility only.
- variable.name = `_r${++state.index}`;
- break;
- }
- }
- return variable.name;
- }
- /**
- * Normalizes a style prop name by hyphenating it (unless its a CSS variable).
- */
- function normalizeStylePropName(name) {
- return name.startsWith('--') ? name : hyphenate(name);
- }
- /**
- * Strips `!important` out of the given style or class name.
- */
- function stripImportant(name) {
- const importantIndex = name.indexOf('!important');
- if (importantIndex > -1) {
- return name.substring(0, importantIndex);
- }
- return name;
- }
- /**
- * Merges logically sequential `NextContextExpr` operations.
- *
- * `NextContextExpr` can be referenced repeatedly, "popping" the runtime's context stack each time.
- * When two such expressions appear back-to-back, it's possible to merge them together into a single
- * `NextContextExpr` that steps multiple contexts. This merging is possible if all conditions are
- * met:
- *
- * * The result of the `NextContextExpr` that's folded into the subsequent one is not stored (that
- * is, the call is purely side-effectful).
- * * No operations in between them uses the implicit context.
- */
- function mergeNextContextExpressions(job) {
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
- mergeNextContextsInOps(op.handlerOps);
- }
- }
- mergeNextContextsInOps(unit.update);
- }
- }
- function mergeNextContextsInOps(ops) {
- for (const op of ops) {
- // Look for a candidate operation to maybe merge.
- if (op.kind !== OpKind.Statement ||
- !(op.statement instanceof ExpressionStatement) ||
- !(op.statement.expr instanceof NextContextExpr)) {
- continue;
- }
- const mergeSteps = op.statement.expr.steps;
- // Try to merge this `ir.NextContextExpr`.
- let tryToMerge = true;
- for (let candidate = op.next; candidate.kind !== OpKind.ListEnd && tryToMerge; candidate = candidate.next) {
- visitExpressionsInOp(candidate, (expr, flags) => {
- if (!isIrExpression(expr)) {
- return expr;
- }
- if (!tryToMerge) {
- // Either we've already merged, or failed to merge.
- return;
- }
- if (flags & VisitorContextFlag.InChildOperation) {
- // We cannot merge into child operations.
- return;
- }
- switch (expr.kind) {
- case ExpressionKind.NextContext:
- // Merge the previous `ir.NextContextExpr` into this one.
- expr.steps += mergeSteps;
- OpList.remove(op);
- tryToMerge = false;
- break;
- case ExpressionKind.GetCurrentView:
- case ExpressionKind.Reference:
- case ExpressionKind.ContextLetReference:
- // Can't merge past a dependency on the context.
- tryToMerge = false;
- break;
- }
- return;
- });
- }
- }
- }
- const CONTAINER_TAG = 'ng-container';
- /**
- * Replace an `Element` or `ElementStart` whose tag is `ng-container` with a specific op.
- */
- function generateNgContainerOps(job) {
- for (const unit of job.units) {
- const updatedElementXrefs = new Set();
- for (const op of unit.create) {
- if (op.kind === OpKind.ElementStart && op.tag === CONTAINER_TAG) {
- // Transmute the `ElementStart` instruction to `ContainerStart`.
- op.kind = OpKind.ContainerStart;
- updatedElementXrefs.add(op.xref);
- }
- if (op.kind === OpKind.ElementEnd && updatedElementXrefs.has(op.xref)) {
- // This `ElementEnd` is associated with an `ElementStart` we already transmuted.
- op.kind = OpKind.ContainerEnd;
- }
- }
- }
- }
- /**
- * Looks up an element in the given map by xref ID.
- */
- function lookupElement(elements, xref) {
- const el = elements.get(xref);
- if (el === undefined) {
- throw new Error('All attributes should have an element-like target.');
- }
- return el;
- }
- /**
- * When a container is marked with `ngNonBindable`, the non-bindable characteristic also applies to
- * all descendants of that container. Therefore, we must emit `disableBindings` and `enableBindings`
- * instructions for every such container.
- */
- function disableBindings$1(job) {
- const elements = new Map();
- for (const view of job.units) {
- for (const op of view.create) {
- if (!isElementOrContainerOp(op)) {
- continue;
- }
- elements.set(op.xref, op);
- }
- }
- for (const unit of job.units) {
- for (const op of unit.create) {
- if ((op.kind === OpKind.ElementStart || op.kind === OpKind.ContainerStart) &&
- op.nonBindable) {
- OpList.insertAfter(createDisableBindingsOp(op.xref), op);
- }
- if ((op.kind === OpKind.ElementEnd || op.kind === OpKind.ContainerEnd) &&
- lookupElement(elements, op.xref).nonBindable) {
- OpList.insertBefore(createEnableBindingsOp(op.xref), op);
- }
- }
- }
- }
- /**
- * Nullish coalescing expressions such as `a ?? b` have different semantics in Angular templates as
- * compared to JavaScript. In particular, they default to `null` instead of `undefined`. Therefore,
- * we replace them with ternary expressions, assigning temporaries as needed to avoid re-evaluating
- * the same sub-expression multiple times.
- */
- function generateNullishCoalesceExpressions(job) {
- for (const unit of job.units) {
- for (const op of unit.ops()) {
- transformExpressionsInOp(op, (expr) => {
- if (!(expr instanceof BinaryOperatorExpr) ||
- expr.operator !== BinaryOperator.NullishCoalesce) {
- return expr;
- }
- const assignment = new AssignTemporaryExpr(expr.lhs.clone(), job.allocateXrefId());
- const read = new ReadTemporaryExpr(assignment.xref);
- // TODO: When not in compatibility mode for TemplateDefinitionBuilder, we can just emit
- // `t != null` instead of including an undefined check as well.
- return new ConditionalExpr(new BinaryOperatorExpr(BinaryOperator.And, new BinaryOperatorExpr(BinaryOperator.NotIdentical, assignment, NULL_EXPR), new BinaryOperatorExpr(BinaryOperator.NotIdentical, read, new LiteralExpr(undefined))), read.clone(), expr.rhs);
- }, VisitorContextFlag.None);
- }
- }
- }
- function kindTest(kind) {
- return (op) => op.kind === kind;
- }
- function kindWithInterpolationTest(kind, interpolation) {
- return (op) => {
- return op.kind === kind && interpolation === op.expression instanceof Interpolation;
- };
- }
- function basicListenerKindTest(op) {
- return ((op.kind === OpKind.Listener && !(op.hostListener && op.isAnimationListener)) ||
- op.kind === OpKind.TwoWayListener);
- }
- function nonInterpolationPropertyKindTest(op) {
- return ((op.kind === OpKind.Property || op.kind === OpKind.TwoWayProperty) &&
- !(op.expression instanceof Interpolation));
- }
- /**
- * Defines the groups based on `OpKind` that ops will be divided into, for the various create
- * op kinds. Ops will be collected into groups, then optionally transformed, before recombining
- * the groups in the order defined here.
- */
- const CREATE_ORDERING = [
- { test: (op) => op.kind === OpKind.Listener && op.hostListener && op.isAnimationListener },
- { test: basicListenerKindTest },
- ];
- /**
- * Defines the groups based on `OpKind` that ops will be divided into, for the various update
- * op kinds.
- */
- const UPDATE_ORDERING = [
- { test: kindTest(OpKind.StyleMap), transform: keepLast },
- { test: kindTest(OpKind.ClassMap), transform: keepLast },
- { test: kindTest(OpKind.StyleProp) },
- { test: kindTest(OpKind.ClassProp) },
- { test: kindWithInterpolationTest(OpKind.Attribute, true) },
- { test: kindWithInterpolationTest(OpKind.Property, true) },
- { test: nonInterpolationPropertyKindTest },
- { test: kindWithInterpolationTest(OpKind.Attribute, false) },
- ];
- /**
- * Host bindings have their own update ordering.
- */
- const UPDATE_HOST_ORDERING = [
- { test: kindWithInterpolationTest(OpKind.HostProperty, true) },
- { test: kindWithInterpolationTest(OpKind.HostProperty, false) },
- { test: kindTest(OpKind.Attribute) },
- { test: kindTest(OpKind.StyleMap), transform: keepLast },
- { test: kindTest(OpKind.ClassMap), transform: keepLast },
- { test: kindTest(OpKind.StyleProp) },
- { test: kindTest(OpKind.ClassProp) },
- ];
- /**
- * The set of all op kinds we handle in the reordering phase.
- */
- const handledOpKinds = new Set([
- OpKind.Listener,
- OpKind.TwoWayListener,
- OpKind.StyleMap,
- OpKind.ClassMap,
- OpKind.StyleProp,
- OpKind.ClassProp,
- OpKind.Property,
- OpKind.TwoWayProperty,
- OpKind.HostProperty,
- OpKind.Attribute,
- ]);
- /**
- * Many type of operations have ordering constraints that must be respected. For example, a
- * `ClassMap` instruction must be ordered after a `StyleMap` instruction, in order to have
- * predictable semantics that match TemplateDefinitionBuilder and don't break applications.
- */
- function orderOps(job) {
- for (const unit of job.units) {
- // First, we pull out ops that need to be ordered. Then, when we encounter an op that shouldn't
- // be reordered, put the ones we've pulled so far back in the correct order. Finally, if we
- // still have ops pulled at the end, put them back in the correct order.
- // Create mode:
- orderWithin(unit.create, CREATE_ORDERING);
- // Update mode:
- const ordering = unit.job.kind === CompilationJobKind.Host ? UPDATE_HOST_ORDERING : UPDATE_ORDERING;
- orderWithin(unit.update, ordering);
- }
- }
- /**
- * Order all the ops within the specified group.
- */
- function orderWithin(opList, ordering) {
- let opsToOrder = [];
- // Only reorder ops that target the same xref; do not mix ops that target different xrefs.
- let firstTargetInGroup = null;
- for (const op of opList) {
- const currentTarget = hasDependsOnSlotContextTrait(op) ? op.target : null;
- if (!handledOpKinds.has(op.kind) ||
- (currentTarget !== firstTargetInGroup &&
- firstTargetInGroup !== null &&
- currentTarget !== null)) {
- OpList.insertBefore(reorder(opsToOrder, ordering), op);
- opsToOrder = [];
- firstTargetInGroup = null;
- }
- if (handledOpKinds.has(op.kind)) {
- opsToOrder.push(op);
- OpList.remove(op);
- firstTargetInGroup = currentTarget ?? firstTargetInGroup;
- }
- }
- opList.push(reorder(opsToOrder, ordering));
- }
- /**
- * Reorders the given list of ops according to the ordering defined by `ORDERING`.
- */
- function reorder(ops, ordering) {
- // Break the ops list into groups based on OpKind.
- const groups = Array.from(ordering, () => new Array());
- for (const op of ops) {
- const groupIndex = ordering.findIndex((o) => o.test(op));
- groups[groupIndex].push(op);
- }
- // Reassemble the groups into a single list, in the correct order.
- return groups.flatMap((group, i) => {
- const transform = ordering[i].transform;
- return transform ? transform(group) : group;
- });
- }
- /**
- * Keeps only the last op in a list of ops.
- */
- function keepLast(ops) {
- return ops.slice(ops.length - 1);
- }
- /**
- * Attributes of `ng-content` named 'select' are specifically removed, because they control which
- * content matches as a property of the `projection`, and are not a plain attribute.
- */
- function removeContentSelectors(job) {
- for (const unit of job.units) {
- const elements = createOpXrefMap(unit);
- for (const op of unit.ops()) {
- switch (op.kind) {
- case OpKind.Binding:
- const target = lookupInXrefMap(elements, op.target);
- if (isSelectAttribute(op.name) && target.kind === OpKind.Projection) {
- OpList.remove(op);
- }
- break;
- }
- }
- }
- }
- function isSelectAttribute(name) {
- return name.toLowerCase() === 'select';
- }
- /**
- * Looks up an element in the given map by xref ID.
- */
- function lookupInXrefMap(map, xref) {
- const el = map.get(xref);
- if (el === undefined) {
- throw new Error('All attributes should have an slottable target.');
- }
- return el;
- }
- /**
- * This phase generates pipe creation instructions. We do this based on the pipe bindings found in
- * the update block, in the order we see them.
- *
- * When not in compatibility mode, we can simply group all these creation instructions together, to
- * maximize chaining opportunities.
- */
- function createPipes(job) {
- for (const unit of job.units) {
- processPipeBindingsInView(unit);
- }
- }
- function processPipeBindingsInView(unit) {
- for (const updateOp of unit.update) {
- visitExpressionsInOp(updateOp, (expr, flags) => {
- if (!isIrExpression(expr)) {
- return;
- }
- if (expr.kind !== ExpressionKind.PipeBinding) {
- return;
- }
- if (flags & VisitorContextFlag.InChildOperation) {
- throw new Error(`AssertionError: pipe bindings should not appear in child expressions`);
- }
- if (unit.job.compatibility) {
- // TODO: We can delete this cast and check once compatibility mode is removed.
- const slotHandle = updateOp.target;
- if (slotHandle == undefined) {
- throw new Error(`AssertionError: expected slot handle to be assigned for pipe creation`);
- }
- addPipeToCreationBlock(unit, updateOp.target, expr);
- }
- else {
- // When not in compatibility mode, we just add the pipe to the end of the create block. This
- // is not only simpler and faster, but allows more chaining opportunities for other
- // instructions.
- unit.create.push(createPipeOp(expr.target, expr.targetSlot, expr.name));
- }
- });
- }
- }
- function addPipeToCreationBlock(unit, afterTargetXref, binding) {
- // Find the appropriate point to insert the Pipe creation operation.
- // We're looking for `afterTargetXref` (and also want to insert after any other pipe operations
- // which might be beyond it).
- for (let op = unit.create.head.next; op.kind !== OpKind.ListEnd; op = op.next) {
- if (!hasConsumesSlotTrait(op)) {
- continue;
- }
- if (op.xref !== afterTargetXref) {
- continue;
- }
- // We've found a tentative insertion point; however, we also want to skip past any _other_ pipe
- // operations present.
- while (op.next.kind === OpKind.Pipe) {
- op = op.next;
- }
- const pipe = createPipeOp(binding.target, binding.targetSlot, binding.name);
- OpList.insertBefore(pipe, op.next);
- // This completes adding the pipe to the creation block.
- return;
- }
- // At this point, we've failed to add the pipe to the creation block.
- throw new Error(`AssertionError: unable to find insertion point for pipe ${binding.name}`);
- }
- /**
- * Pipes that accept more than 4 arguments are variadic, and are handled with a different runtime
- * instruction.
- */
- function createVariadicPipes(job) {
- for (const unit of job.units) {
- for (const op of unit.update) {
- transformExpressionsInOp(op, (expr) => {
- if (!(expr instanceof PipeBindingExpr)) {
- return expr;
- }
- // Pipes are variadic if they have more than 4 arguments.
- if (expr.args.length <= 4) {
- return expr;
- }
- return new PipeBindingVariadicExpr(expr.target, expr.targetSlot, expr.name, literalArr(expr.args), expr.args.length);
- }, VisitorContextFlag.None);
- }
- }
- }
- /**
- * Propagate i18n blocks down through child templates that act as placeholders in the root i18n
- * message. Specifically, perform an in-order traversal of all the views, and add i18nStart/i18nEnd
- * op pairs into descending views. Also, assign an increasing sub-template index to each
- * descending view.
- */
- function propagateI18nBlocks(job) {
- propagateI18nBlocksToTemplates(job.root, 0);
- }
- /**
- * Propagates i18n ops in the given view through to any child views recursively.
- */
- function propagateI18nBlocksToTemplates(unit, subTemplateIndex) {
- let i18nBlock = null;
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nStart:
- op.subTemplateIndex = subTemplateIndex === 0 ? null : subTemplateIndex;
- i18nBlock = op;
- break;
- case OpKind.I18nEnd:
- // When we exit a root-level i18n block, reset the sub-template index counter.
- if (i18nBlock.subTemplateIndex === null) {
- subTemplateIndex = 0;
- }
- i18nBlock = null;
- break;
- case OpKind.Template:
- subTemplateIndex = propagateI18nBlocksForView(unit.job.views.get(op.xref), i18nBlock, op.i18nPlaceholder, subTemplateIndex);
- break;
- case OpKind.RepeaterCreate:
- // Propagate i18n blocks to the @for template.
- const forView = unit.job.views.get(op.xref);
- subTemplateIndex = propagateI18nBlocksForView(forView, i18nBlock, op.i18nPlaceholder, subTemplateIndex);
- // Then if there's an @empty template, propagate the i18n blocks for it as well.
- if (op.emptyView !== null) {
- subTemplateIndex = propagateI18nBlocksForView(unit.job.views.get(op.emptyView), i18nBlock, op.emptyI18nPlaceholder, subTemplateIndex);
- }
- break;
- }
- }
- return subTemplateIndex;
- }
- /**
- * Propagate i18n blocks for a view.
- */
- function propagateI18nBlocksForView(view, i18nBlock, i18nPlaceholder, subTemplateIndex) {
- // We found an <ng-template> inside an i18n block; increment the sub-template counter and
- // wrap the template's view in a child i18n block.
- if (i18nPlaceholder !== undefined) {
- if (i18nBlock === null) {
- throw Error('Expected template with i18n placeholder to be in an i18n block.');
- }
- subTemplateIndex++;
- wrapTemplateWithI18n(view, i18nBlock);
- }
- // Continue traversing inside the template's view.
- return propagateI18nBlocksToTemplates(view, subTemplateIndex);
- }
- /**
- * Wraps a template view with i18n start and end ops.
- */
- function wrapTemplateWithI18n(unit, parentI18n) {
- // Only add i18n ops if they have not already been propagated to this template.
- if (unit.create.head.next?.kind !== OpKind.I18nStart) {
- const id = unit.job.allocateXrefId();
- OpList.insertAfter(
- // Nested ng-template i18n start/end ops should not receive source spans.
- createI18nStartOp(id, parentI18n.message, parentI18n.root, null), unit.create.head);
- OpList.insertBefore(createI18nEndOp(id, null), unit.create.tail);
- }
- }
- function extractPureFunctions(job) {
- for (const view of job.units) {
- for (const op of view.ops()) {
- visitExpressionsInOp(op, (expr) => {
- if (!(expr instanceof PureFunctionExpr) || expr.body === null) {
- return;
- }
- const constantDef = new PureFunctionConstant(expr.args.length);
- expr.fn = job.pool.getSharedConstant(constantDef, expr.body);
- expr.body = null;
- });
- }
- }
- }
- class PureFunctionConstant extends GenericKeyFn {
- numArgs;
- constructor(numArgs) {
- super();
- this.numArgs = numArgs;
- }
- keyOf(expr) {
- if (expr instanceof PureFunctionParameterExpr) {
- return `param(${expr.index})`;
- }
- else {
- return super.keyOf(expr);
- }
- }
- // TODO: Use the new pool method `getSharedFunctionReference`
- toSharedConstantDeclaration(declName, keyExpr) {
- const fnParams = [];
- for (let idx = 0; idx < this.numArgs; idx++) {
- fnParams.push(new FnParam('a' + idx));
- }
- // We will never visit `ir.PureFunctionParameterExpr`s that don't belong to us, because this
- // transform runs inside another visitor which will visit nested pure functions before this one.
- const returnExpr = transformExpressionsInExpression(keyExpr, (expr) => {
- if (!(expr instanceof PureFunctionParameterExpr)) {
- return expr;
- }
- return variable('a' + expr.index);
- }, VisitorContextFlag.None);
- return new DeclareVarStmt(declName, new ArrowFunctionExpr(fnParams, returnExpr), undefined, exports.StmtModifier.Final);
- }
- }
- function generatePureLiteralStructures(job) {
- for (const unit of job.units) {
- for (const op of unit.update) {
- transformExpressionsInOp(op, (expr, flags) => {
- if (flags & VisitorContextFlag.InChildOperation) {
- return expr;
- }
- if (expr instanceof LiteralArrayExpr) {
- return transformLiteralArray(expr);
- }
- else if (expr instanceof LiteralMapExpr) {
- return transformLiteralMap(expr);
- }
- return expr;
- }, VisitorContextFlag.None);
- }
- }
- }
- function transformLiteralArray(expr) {
- const derivedEntries = [];
- const nonConstantArgs = [];
- for (const entry of expr.entries) {
- if (entry.isConstant()) {
- derivedEntries.push(entry);
- }
- else {
- const idx = nonConstantArgs.length;
- nonConstantArgs.push(entry);
- derivedEntries.push(new PureFunctionParameterExpr(idx));
- }
- }
- return new PureFunctionExpr(literalArr(derivedEntries), nonConstantArgs);
- }
- function transformLiteralMap(expr) {
- let derivedEntries = [];
- const nonConstantArgs = [];
- for (const entry of expr.entries) {
- if (entry.value.isConstant()) {
- derivedEntries.push(entry);
- }
- else {
- const idx = nonConstantArgs.length;
- nonConstantArgs.push(entry.value);
- derivedEntries.push(new LiteralMapEntry(entry.key, new PureFunctionParameterExpr(idx), entry.quoted));
- }
- }
- return new PureFunctionExpr(literalMap(derivedEntries), nonConstantArgs);
- }
- // This file contains helpers for generating calls to Ivy instructions. In particular, each
- // instruction type is represented as a function, which may select a specific instruction variant
- // depending on the exact arguments.
- function element(slot, tag, constIndex, localRefIndex, sourceSpan) {
- return elementOrContainerBase(Identifiers.element, slot, tag, constIndex, localRefIndex, sourceSpan);
- }
- function elementStart(slot, tag, constIndex, localRefIndex, sourceSpan) {
- return elementOrContainerBase(Identifiers.elementStart, slot, tag, constIndex, localRefIndex, sourceSpan);
- }
- function elementOrContainerBase(instruction, slot, tag, constIndex, localRefIndex, sourceSpan) {
- const args = [literal$1(slot)];
- if (tag !== null) {
- args.push(literal$1(tag));
- }
- if (localRefIndex !== null) {
- args.push(literal$1(constIndex), // might be null, but that's okay.
- literal$1(localRefIndex));
- }
- else if (constIndex !== null) {
- args.push(literal$1(constIndex));
- }
- return call(instruction, args, sourceSpan);
- }
- function elementEnd(sourceSpan) {
- return call(Identifiers.elementEnd, [], sourceSpan);
- }
- function elementContainerStart(slot, constIndex, localRefIndex, sourceSpan) {
- return elementOrContainerBase(Identifiers.elementContainerStart, slot,
- /* tag */ null, constIndex, localRefIndex, sourceSpan);
- }
- function elementContainer(slot, constIndex, localRefIndex, sourceSpan) {
- return elementOrContainerBase(Identifiers.elementContainer, slot,
- /* tag */ null, constIndex, localRefIndex, sourceSpan);
- }
- function elementContainerEnd() {
- return call(Identifiers.elementContainerEnd, [], null);
- }
- function template(slot, templateFnRef, decls, vars, tag, constIndex, localRefs, sourceSpan) {
- const args = [
- literal$1(slot),
- templateFnRef,
- literal$1(decls),
- literal$1(vars),
- literal$1(tag),
- literal$1(constIndex),
- ];
- if (localRefs !== null) {
- args.push(literal$1(localRefs));
- args.push(importExpr(Identifiers.templateRefExtractor));
- }
- while (args[args.length - 1].isEquivalent(NULL_EXPR)) {
- args.pop();
- }
- return call(Identifiers.templateCreate, args, sourceSpan);
- }
- function disableBindings() {
- return call(Identifiers.disableBindings, [], null);
- }
- function enableBindings() {
- return call(Identifiers.enableBindings, [], null);
- }
- function listener(name, handlerFn, eventTargetResolver, syntheticHost, sourceSpan) {
- const args = [literal$1(name), handlerFn];
- if (eventTargetResolver !== null) {
- args.push(literal$1(false)); // `useCapture` flag, defaults to `false`
- args.push(importExpr(eventTargetResolver));
- }
- return call(syntheticHost ? Identifiers.syntheticHostListener : Identifiers.listener, args, sourceSpan);
- }
- function twoWayBindingSet(target, value) {
- return importExpr(Identifiers.twoWayBindingSet).callFn([target, value]);
- }
- function twoWayListener(name, handlerFn, sourceSpan) {
- return call(Identifiers.twoWayListener, [literal$1(name), handlerFn], sourceSpan);
- }
- function pipe(slot, name) {
- return call(Identifiers.pipe, [literal$1(slot), literal$1(name)], null);
- }
- function namespaceHTML() {
- return call(Identifiers.namespaceHTML, [], null);
- }
- function namespaceSVG() {
- return call(Identifiers.namespaceSVG, [], null);
- }
- function namespaceMath() {
- return call(Identifiers.namespaceMathML, [], null);
- }
- function advance(delta, sourceSpan) {
- return call(Identifiers.advance, delta > 1 ? [literal$1(delta)] : [], sourceSpan);
- }
- function reference(slot) {
- return importExpr(Identifiers.reference).callFn([literal$1(slot)]);
- }
- function nextContext(steps) {
- return importExpr(Identifiers.nextContext).callFn(steps === 1 ? [] : [literal$1(steps)]);
- }
- function getCurrentView() {
- return importExpr(Identifiers.getCurrentView).callFn([]);
- }
- function restoreView(savedView) {
- return importExpr(Identifiers.restoreView).callFn([savedView]);
- }
- function resetView(returnValue) {
- return importExpr(Identifiers.resetView).callFn([returnValue]);
- }
- function text(slot, initialValue, sourceSpan) {
- const args = [literal$1(slot, null)];
- if (initialValue !== '') {
- args.push(literal$1(initialValue));
- }
- return call(Identifiers.text, args, sourceSpan);
- }
- function defer(selfSlot, primarySlot, dependencyResolverFn, loadingSlot, placeholderSlot, errorSlot, loadingConfig, placeholderConfig, enableTimerScheduling, sourceSpan, flags) {
- const args = [
- literal$1(selfSlot),
- literal$1(primarySlot),
- dependencyResolverFn ?? literal$1(null),
- literal$1(loadingSlot),
- literal$1(placeholderSlot),
- literal$1(errorSlot),
- loadingConfig ?? literal$1(null),
- placeholderConfig ?? literal$1(null),
- enableTimerScheduling ? importExpr(Identifiers.deferEnableTimerScheduling) : literal$1(null),
- literal$1(flags),
- ];
- let expr;
- while ((expr = args[args.length - 1]) !== null &&
- expr instanceof LiteralExpr &&
- expr.value === null) {
- args.pop();
- }
- return call(Identifiers.defer, args, sourceSpan);
- }
- const deferTriggerToR3TriggerInstructionsMap = new Map([
- [
- DeferTriggerKind.Idle,
- {
- ["none" /* ir.DeferOpModifierKind.NONE */]: Identifiers.deferOnIdle,
- ["prefetch" /* ir.DeferOpModifierKind.PREFETCH */]: Identifiers.deferPrefetchOnIdle,
- ["hydrate" /* ir.DeferOpModifierKind.HYDRATE */]: Identifiers.deferHydrateOnIdle,
- },
- ],
- [
- DeferTriggerKind.Immediate,
- {
- ["none" /* ir.DeferOpModifierKind.NONE */]: Identifiers.deferOnImmediate,
- ["prefetch" /* ir.DeferOpModifierKind.PREFETCH */]: Identifiers.deferPrefetchOnImmediate,
- ["hydrate" /* ir.DeferOpModifierKind.HYDRATE */]: Identifiers.deferHydrateOnImmediate,
- },
- ],
- [
- DeferTriggerKind.Timer,
- {
- ["none" /* ir.DeferOpModifierKind.NONE */]: Identifiers.deferOnTimer,
- ["prefetch" /* ir.DeferOpModifierKind.PREFETCH */]: Identifiers.deferPrefetchOnTimer,
- ["hydrate" /* ir.DeferOpModifierKind.HYDRATE */]: Identifiers.deferHydrateOnTimer,
- },
- ],
- [
- DeferTriggerKind.Hover,
- {
- ["none" /* ir.DeferOpModifierKind.NONE */]: Identifiers.deferOnHover,
- ["prefetch" /* ir.DeferOpModifierKind.PREFETCH */]: Identifiers.deferPrefetchOnHover,
- ["hydrate" /* ir.DeferOpModifierKind.HYDRATE */]: Identifiers.deferHydrateOnHover,
- },
- ],
- [
- DeferTriggerKind.Interaction,
- {
- ["none" /* ir.DeferOpModifierKind.NONE */]: Identifiers.deferOnInteraction,
- ["prefetch" /* ir.DeferOpModifierKind.PREFETCH */]: Identifiers.deferPrefetchOnInteraction,
- ["hydrate" /* ir.DeferOpModifierKind.HYDRATE */]: Identifiers.deferHydrateOnInteraction,
- },
- ],
- [
- DeferTriggerKind.Viewport,
- {
- ["none" /* ir.DeferOpModifierKind.NONE */]: Identifiers.deferOnViewport,
- ["prefetch" /* ir.DeferOpModifierKind.PREFETCH */]: Identifiers.deferPrefetchOnViewport,
- ["hydrate" /* ir.DeferOpModifierKind.HYDRATE */]: Identifiers.deferHydrateOnViewport,
- },
- ],
- [
- DeferTriggerKind.Never,
- {
- ["none" /* ir.DeferOpModifierKind.NONE */]: Identifiers.deferHydrateNever,
- ["prefetch" /* ir.DeferOpModifierKind.PREFETCH */]: Identifiers.deferHydrateNever,
- ["hydrate" /* ir.DeferOpModifierKind.HYDRATE */]: Identifiers.deferHydrateNever,
- },
- ],
- ]);
- function deferOn(trigger, args, modifier, sourceSpan) {
- const instructionToCall = deferTriggerToR3TriggerInstructionsMap.get(trigger)?.[modifier];
- if (instructionToCall === undefined) {
- throw new Error(`Unable to determine instruction for trigger ${trigger}`);
- }
- return call(instructionToCall, args.map((a) => literal$1(a)), sourceSpan);
- }
- function projectionDef(def) {
- return call(Identifiers.projectionDef, def ? [def] : [], null);
- }
- function projection(slot, projectionSlotIndex, attributes, fallbackFnName, fallbackDecls, fallbackVars, sourceSpan) {
- const args = [literal$1(slot)];
- if (projectionSlotIndex !== 0 || attributes !== null || fallbackFnName !== null) {
- args.push(literal$1(projectionSlotIndex));
- if (attributes !== null) {
- args.push(attributes);
- }
- if (fallbackFnName !== null) {
- if (attributes === null) {
- args.push(literal$1(null));
- }
- args.push(variable(fallbackFnName), literal$1(fallbackDecls), literal$1(fallbackVars));
- }
- }
- return call(Identifiers.projection, args, sourceSpan);
- }
- function i18nStart(slot, constIndex, subTemplateIndex, sourceSpan) {
- const args = [literal$1(slot), literal$1(constIndex)];
- if (subTemplateIndex !== null) {
- args.push(literal$1(subTemplateIndex));
- }
- return call(Identifiers.i18nStart, args, sourceSpan);
- }
- function repeaterCreate(slot, viewFnName, decls, vars, tag, constIndex, trackByFn, trackByUsesComponentInstance, emptyViewFnName, emptyDecls, emptyVars, emptyTag, emptyConstIndex, sourceSpan) {
- const args = [
- literal$1(slot),
- variable(viewFnName),
- literal$1(decls),
- literal$1(vars),
- literal$1(tag),
- literal$1(constIndex),
- trackByFn,
- ];
- if (trackByUsesComponentInstance || emptyViewFnName !== null) {
- args.push(literal$1(trackByUsesComponentInstance));
- if (emptyViewFnName !== null) {
- args.push(variable(emptyViewFnName), literal$1(emptyDecls), literal$1(emptyVars));
- if (emptyTag !== null || emptyConstIndex !== null) {
- args.push(literal$1(emptyTag));
- }
- if (emptyConstIndex !== null) {
- args.push(literal$1(emptyConstIndex));
- }
- }
- }
- return call(Identifiers.repeaterCreate, args, sourceSpan);
- }
- function repeater(collection, sourceSpan) {
- return call(Identifiers.repeater, [collection], sourceSpan);
- }
- function deferWhen(modifier, expr, sourceSpan) {
- if (modifier === "prefetch" /* ir.DeferOpModifierKind.PREFETCH */) {
- return call(Identifiers.deferPrefetchWhen, [expr], sourceSpan);
- }
- else if (modifier === "hydrate" /* ir.DeferOpModifierKind.HYDRATE */) {
- return call(Identifiers.deferHydrateWhen, [expr], sourceSpan);
- }
- return call(Identifiers.deferWhen, [expr], sourceSpan);
- }
- function declareLet(slot, sourceSpan) {
- return call(Identifiers.declareLet, [literal$1(slot)], sourceSpan);
- }
- function storeLet(value, sourceSpan) {
- return importExpr(Identifiers.storeLet).callFn([value], sourceSpan);
- }
- function readContextLet(slot) {
- return importExpr(Identifiers.readContextLet).callFn([literal$1(slot)]);
- }
- function i18n(slot, constIndex, subTemplateIndex, sourceSpan) {
- const args = [literal$1(slot), literal$1(constIndex)];
- if (subTemplateIndex) {
- args.push(literal$1(subTemplateIndex));
- }
- return call(Identifiers.i18n, args, sourceSpan);
- }
- function i18nEnd(endSourceSpan) {
- return call(Identifiers.i18nEnd, [], endSourceSpan);
- }
- function i18nAttributes(slot, i18nAttributesConfig) {
- const args = [literal$1(slot), literal$1(i18nAttributesConfig)];
- return call(Identifiers.i18nAttributes, args, null);
- }
- function property(name, expression, sanitizer, sourceSpan) {
- const args = [literal$1(name), expression];
- if (sanitizer !== null) {
- args.push(sanitizer);
- }
- return call(Identifiers.property, args, sourceSpan);
- }
- function twoWayProperty(name, expression, sanitizer, sourceSpan) {
- const args = [literal$1(name), expression];
- if (sanitizer !== null) {
- args.push(sanitizer);
- }
- return call(Identifiers.twoWayProperty, args, sourceSpan);
- }
- function attribute(name, expression, sanitizer, namespace) {
- const args = [literal$1(name), expression];
- if (sanitizer !== null || namespace !== null) {
- args.push(sanitizer ?? literal$1(null));
- }
- if (namespace !== null) {
- args.push(literal$1(namespace));
- }
- return call(Identifiers.attribute, args, null);
- }
- function styleProp(name, expression, unit, sourceSpan) {
- const args = [literal$1(name), expression];
- if (unit !== null) {
- args.push(literal$1(unit));
- }
- return call(Identifiers.styleProp, args, sourceSpan);
- }
- function classProp(name, expression, sourceSpan) {
- return call(Identifiers.classProp, [literal$1(name), expression], sourceSpan);
- }
- function styleMap(expression, sourceSpan) {
- return call(Identifiers.styleMap, [expression], sourceSpan);
- }
- function classMap(expression, sourceSpan) {
- return call(Identifiers.classMap, [expression], sourceSpan);
- }
- const PIPE_BINDINGS = [
- Identifiers.pipeBind1,
- Identifiers.pipeBind2,
- Identifiers.pipeBind3,
- Identifiers.pipeBind4,
- ];
- function pipeBind(slot, varOffset, args) {
- if (args.length < 1 || args.length > PIPE_BINDINGS.length) {
- throw new Error(`pipeBind() argument count out of bounds`);
- }
- const instruction = PIPE_BINDINGS[args.length - 1];
- return importExpr(instruction).callFn([literal$1(slot), literal$1(varOffset), ...args]);
- }
- function pipeBindV(slot, varOffset, args) {
- return importExpr(Identifiers.pipeBindV).callFn([literal$1(slot), literal$1(varOffset), args]);
- }
- function textInterpolate(strings, expressions, sourceSpan) {
- const interpolationArgs = collateInterpolationArgs(strings, expressions);
- return callVariadicInstruction(TEXT_INTERPOLATE_CONFIG, [], interpolationArgs, [], sourceSpan);
- }
- function i18nExp(expr, sourceSpan) {
- return call(Identifiers.i18nExp, [expr], sourceSpan);
- }
- function i18nApply(slot, sourceSpan) {
- return call(Identifiers.i18nApply, [literal$1(slot)], sourceSpan);
- }
- function propertyInterpolate(name, strings, expressions, sanitizer, sourceSpan) {
- const interpolationArgs = collateInterpolationArgs(strings, expressions);
- const extraArgs = [];
- if (sanitizer !== null) {
- extraArgs.push(sanitizer);
- }
- return callVariadicInstruction(PROPERTY_INTERPOLATE_CONFIG, [literal$1(name)], interpolationArgs, extraArgs, sourceSpan);
- }
- function attributeInterpolate(name, strings, expressions, sanitizer, sourceSpan) {
- const interpolationArgs = collateInterpolationArgs(strings, expressions);
- const extraArgs = [];
- if (sanitizer !== null) {
- extraArgs.push(sanitizer);
- }
- return callVariadicInstruction(ATTRIBUTE_INTERPOLATE_CONFIG, [literal$1(name)], interpolationArgs, extraArgs, sourceSpan);
- }
- function stylePropInterpolate(name, strings, expressions, unit, sourceSpan) {
- const interpolationArgs = collateInterpolationArgs(strings, expressions);
- const extraArgs = [];
- if (unit !== null) {
- extraArgs.push(literal$1(unit));
- }
- return callVariadicInstruction(STYLE_PROP_INTERPOLATE_CONFIG, [literal$1(name)], interpolationArgs, extraArgs, sourceSpan);
- }
- function styleMapInterpolate(strings, expressions, sourceSpan) {
- const interpolationArgs = collateInterpolationArgs(strings, expressions);
- return callVariadicInstruction(STYLE_MAP_INTERPOLATE_CONFIG, [], interpolationArgs, [], sourceSpan);
- }
- function classMapInterpolate(strings, expressions, sourceSpan) {
- const interpolationArgs = collateInterpolationArgs(strings, expressions);
- return callVariadicInstruction(CLASS_MAP_INTERPOLATE_CONFIG, [], interpolationArgs, [], sourceSpan);
- }
- function hostProperty(name, expression, sanitizer, sourceSpan) {
- const args = [literal$1(name), expression];
- if (sanitizer !== null) {
- args.push(sanitizer);
- }
- return call(Identifiers.hostProperty, args, sourceSpan);
- }
- function syntheticHostProperty(name, expression, sourceSpan) {
- return call(Identifiers.syntheticHostProperty, [literal$1(name), expression], sourceSpan);
- }
- function pureFunction(varOffset, fn, args) {
- return callVariadicInstructionExpr(PURE_FUNCTION_CONFIG, [literal$1(varOffset), fn], args, [], null);
- }
- function attachSourceLocation(templatePath, locations) {
- return call(Identifiers.attachSourceLocations, [literal$1(templatePath), locations], null);
- }
- /**
- * Collates the string an expression arguments for an interpolation instruction.
- */
- function collateInterpolationArgs(strings, expressions) {
- if (strings.length < 1 || expressions.length !== strings.length - 1) {
- throw new Error(`AssertionError: expected specific shape of args for strings/expressions in interpolation`);
- }
- const interpolationArgs = [];
- if (expressions.length === 1 && strings[0] === '' && strings[1] === '') {
- interpolationArgs.push(expressions[0]);
- }
- else {
- let idx;
- for (idx = 0; idx < expressions.length; idx++) {
- interpolationArgs.push(literal$1(strings[idx]), expressions[idx]);
- }
- // idx points at the last string.
- interpolationArgs.push(literal$1(strings[idx]));
- }
- return interpolationArgs;
- }
- function call(instruction, args, sourceSpan) {
- const expr = importExpr(instruction).callFn(args, sourceSpan);
- return createStatementOp(new ExpressionStatement(expr, sourceSpan));
- }
- function conditional(condition, contextValue, sourceSpan) {
- const args = [condition];
- if (contextValue !== null) {
- args.push(contextValue);
- }
- return call(Identifiers.conditional, args, sourceSpan);
- }
- /**
- * `InterpolationConfig` for the `textInterpolate` instruction.
- */
- const TEXT_INTERPOLATE_CONFIG = {
- constant: [
- Identifiers.textInterpolate,
- Identifiers.textInterpolate1,
- Identifiers.textInterpolate2,
- Identifiers.textInterpolate3,
- Identifiers.textInterpolate4,
- Identifiers.textInterpolate5,
- Identifiers.textInterpolate6,
- Identifiers.textInterpolate7,
- Identifiers.textInterpolate8,
- ],
- variable: Identifiers.textInterpolateV,
- mapping: (n) => {
- if (n % 2 === 0) {
- throw new Error(`Expected odd number of arguments`);
- }
- return (n - 1) / 2;
- },
- };
- /**
- * `InterpolationConfig` for the `propertyInterpolate` instruction.
- */
- const PROPERTY_INTERPOLATE_CONFIG = {
- constant: [
- Identifiers.propertyInterpolate,
- Identifiers.propertyInterpolate1,
- Identifiers.propertyInterpolate2,
- Identifiers.propertyInterpolate3,
- Identifiers.propertyInterpolate4,
- Identifiers.propertyInterpolate5,
- Identifiers.propertyInterpolate6,
- Identifiers.propertyInterpolate7,
- Identifiers.propertyInterpolate8,
- ],
- variable: Identifiers.propertyInterpolateV,
- mapping: (n) => {
- if (n % 2 === 0) {
- throw new Error(`Expected odd number of arguments`);
- }
- return (n - 1) / 2;
- },
- };
- /**
- * `InterpolationConfig` for the `stylePropInterpolate` instruction.
- */
- const STYLE_PROP_INTERPOLATE_CONFIG = {
- constant: [
- Identifiers.styleProp,
- Identifiers.stylePropInterpolate1,
- Identifiers.stylePropInterpolate2,
- Identifiers.stylePropInterpolate3,
- Identifiers.stylePropInterpolate4,
- Identifiers.stylePropInterpolate5,
- Identifiers.stylePropInterpolate6,
- Identifiers.stylePropInterpolate7,
- Identifiers.stylePropInterpolate8,
- ],
- variable: Identifiers.stylePropInterpolateV,
- mapping: (n) => {
- if (n % 2 === 0) {
- throw new Error(`Expected odd number of arguments`);
- }
- return (n - 1) / 2;
- },
- };
- /**
- * `InterpolationConfig` for the `attributeInterpolate` instruction.
- */
- const ATTRIBUTE_INTERPOLATE_CONFIG = {
- constant: [
- Identifiers.attribute,
- Identifiers.attributeInterpolate1,
- Identifiers.attributeInterpolate2,
- Identifiers.attributeInterpolate3,
- Identifiers.attributeInterpolate4,
- Identifiers.attributeInterpolate5,
- Identifiers.attributeInterpolate6,
- Identifiers.attributeInterpolate7,
- Identifiers.attributeInterpolate8,
- ],
- variable: Identifiers.attributeInterpolateV,
- mapping: (n) => {
- if (n % 2 === 0) {
- throw new Error(`Expected odd number of arguments`);
- }
- return (n - 1) / 2;
- },
- };
- /**
- * `InterpolationConfig` for the `styleMapInterpolate` instruction.
- */
- const STYLE_MAP_INTERPOLATE_CONFIG = {
- constant: [
- Identifiers.styleMap,
- Identifiers.styleMapInterpolate1,
- Identifiers.styleMapInterpolate2,
- Identifiers.styleMapInterpolate3,
- Identifiers.styleMapInterpolate4,
- Identifiers.styleMapInterpolate5,
- Identifiers.styleMapInterpolate6,
- Identifiers.styleMapInterpolate7,
- Identifiers.styleMapInterpolate8,
- ],
- variable: Identifiers.styleMapInterpolateV,
- mapping: (n) => {
- if (n % 2 === 0) {
- throw new Error(`Expected odd number of arguments`);
- }
- return (n - 1) / 2;
- },
- };
- /**
- * `InterpolationConfig` for the `classMapInterpolate` instruction.
- */
- const CLASS_MAP_INTERPOLATE_CONFIG = {
- constant: [
- Identifiers.classMap,
- Identifiers.classMapInterpolate1,
- Identifiers.classMapInterpolate2,
- Identifiers.classMapInterpolate3,
- Identifiers.classMapInterpolate4,
- Identifiers.classMapInterpolate5,
- Identifiers.classMapInterpolate6,
- Identifiers.classMapInterpolate7,
- Identifiers.classMapInterpolate8,
- ],
- variable: Identifiers.classMapInterpolateV,
- mapping: (n) => {
- if (n % 2 === 0) {
- throw new Error(`Expected odd number of arguments`);
- }
- return (n - 1) / 2;
- },
- };
- const PURE_FUNCTION_CONFIG = {
- constant: [
- Identifiers.pureFunction0,
- Identifiers.pureFunction1,
- Identifiers.pureFunction2,
- Identifiers.pureFunction3,
- Identifiers.pureFunction4,
- Identifiers.pureFunction5,
- Identifiers.pureFunction6,
- Identifiers.pureFunction7,
- Identifiers.pureFunction8,
- ],
- variable: Identifiers.pureFunctionV,
- mapping: (n) => n,
- };
- function callVariadicInstructionExpr(config, baseArgs, interpolationArgs, extraArgs, sourceSpan) {
- const n = config.mapping(interpolationArgs.length);
- if (n < config.constant.length) {
- // Constant calling pattern.
- return importExpr(config.constant[n])
- .callFn([...baseArgs, ...interpolationArgs, ...extraArgs], sourceSpan);
- }
- else if (config.variable !== null) {
- // Variable calling pattern.
- return importExpr(config.variable)
- .callFn([...baseArgs, literalArr(interpolationArgs), ...extraArgs], sourceSpan);
- }
- else {
- throw new Error(`AssertionError: unable to call variadic function`);
- }
- }
- function callVariadicInstruction(config, baseArgs, interpolationArgs, extraArgs, sourceSpan) {
- return createStatementOp(callVariadicInstructionExpr(config, baseArgs, interpolationArgs, extraArgs, sourceSpan).toStmt());
- }
- /**
- * Map of target resolvers for event listeners.
- */
- const GLOBAL_TARGET_RESOLVERS = new Map([
- ['window', Identifiers.resolveWindow],
- ['document', Identifiers.resolveDocument],
- ['body', Identifiers.resolveBody],
- ]);
- /**
- * Compiles semantic operations across all views and generates output `o.Statement`s with actual
- * runtime calls in their place.
- *
- * Reification replaces semantic operations with selected Ivy instructions and other generated code
- * structures. After reification, the create/update operation lists of all views should only contain
- * `ir.StatementOp`s (which wrap generated `o.Statement`s).
- */
- function reify(job) {
- for (const unit of job.units) {
- reifyCreateOperations(unit, unit.create);
- reifyUpdateOperations(unit, unit.update);
- }
- }
- function reifyCreateOperations(unit, ops) {
- for (const op of ops) {
- transformExpressionsInOp(op, reifyIrExpression, VisitorContextFlag.None);
- switch (op.kind) {
- case OpKind.Text:
- OpList.replace(op, text(op.handle.slot, op.initialValue, op.sourceSpan));
- break;
- case OpKind.ElementStart:
- OpList.replace(op, elementStart(op.handle.slot, op.tag, op.attributes, op.localRefs, op.startSourceSpan));
- break;
- case OpKind.Element:
- OpList.replace(op, element(op.handle.slot, op.tag, op.attributes, op.localRefs, op.wholeSourceSpan));
- break;
- case OpKind.ElementEnd:
- OpList.replace(op, elementEnd(op.sourceSpan));
- break;
- case OpKind.ContainerStart:
- OpList.replace(op, elementContainerStart(op.handle.slot, op.attributes, op.localRefs, op.startSourceSpan));
- break;
- case OpKind.Container:
- OpList.replace(op, elementContainer(op.handle.slot, op.attributes, op.localRefs, op.wholeSourceSpan));
- break;
- case OpKind.ContainerEnd:
- OpList.replace(op, elementContainerEnd());
- break;
- case OpKind.I18nStart:
- OpList.replace(op, i18nStart(op.handle.slot, op.messageIndex, op.subTemplateIndex, op.sourceSpan));
- break;
- case OpKind.I18nEnd:
- OpList.replace(op, i18nEnd(op.sourceSpan));
- break;
- case OpKind.I18n:
- OpList.replace(op, i18n(op.handle.slot, op.messageIndex, op.subTemplateIndex, op.sourceSpan));
- break;
- case OpKind.I18nAttributes:
- if (op.i18nAttributesConfig === null) {
- throw new Error(`AssertionError: i18nAttributesConfig was not set`);
- }
- OpList.replace(op, i18nAttributes(op.handle.slot, op.i18nAttributesConfig));
- break;
- case OpKind.Template:
- if (!(unit instanceof ViewCompilationUnit)) {
- throw new Error(`AssertionError: must be compiling a component`);
- }
- if (Array.isArray(op.localRefs)) {
- throw new Error(`AssertionError: local refs array should have been extracted into a constant`);
- }
- const childView = unit.job.views.get(op.xref);
- OpList.replace(op, template(op.handle.slot, variable(childView.fnName), childView.decls, childView.vars, op.tag, op.attributes, op.localRefs, op.startSourceSpan));
- break;
- case OpKind.DisableBindings:
- OpList.replace(op, disableBindings());
- break;
- case OpKind.EnableBindings:
- OpList.replace(op, enableBindings());
- break;
- case OpKind.Pipe:
- OpList.replace(op, pipe(op.handle.slot, op.name));
- break;
- case OpKind.DeclareLet:
- OpList.replace(op, declareLet(op.handle.slot, op.sourceSpan));
- break;
- case OpKind.Listener:
- const listenerFn = reifyListenerHandler(unit, op.handlerFnName, op.handlerOps, op.consumesDollarEvent);
- const eventTargetResolver = op.eventTarget
- ? GLOBAL_TARGET_RESOLVERS.get(op.eventTarget)
- : null;
- if (eventTargetResolver === undefined) {
- throw new Error(`Unexpected global target '${op.eventTarget}' defined for '${op.name}' event. Supported list of global targets: window,document,body.`);
- }
- OpList.replace(op, listener(op.name, listenerFn, eventTargetResolver, op.hostListener && op.isAnimationListener, op.sourceSpan));
- break;
- case OpKind.TwoWayListener:
- OpList.replace(op, twoWayListener(op.name, reifyListenerHandler(unit, op.handlerFnName, op.handlerOps, true), op.sourceSpan));
- break;
- case OpKind.Variable:
- if (op.variable.name === null) {
- throw new Error(`AssertionError: unnamed variable ${op.xref}`);
- }
- OpList.replace(op, createStatementOp(new DeclareVarStmt(op.variable.name, op.initializer, undefined, exports.StmtModifier.Final)));
- break;
- case OpKind.Namespace:
- switch (op.active) {
- case Namespace.HTML:
- OpList.replace(op, namespaceHTML());
- break;
- case Namespace.SVG:
- OpList.replace(op, namespaceSVG());
- break;
- case Namespace.Math:
- OpList.replace(op, namespaceMath());
- break;
- }
- break;
- case OpKind.Defer:
- const timerScheduling = !!op.loadingMinimumTime || !!op.loadingAfterTime || !!op.placeholderMinimumTime;
- OpList.replace(op, defer(op.handle.slot, op.mainSlot.slot, op.resolverFn, op.loadingSlot?.slot ?? null, op.placeholderSlot?.slot ?? null, op.errorSlot?.slot ?? null, op.loadingConfig, op.placeholderConfig, timerScheduling, op.sourceSpan, op.flags));
- break;
- case OpKind.DeferOn:
- let args = [];
- switch (op.trigger.kind) {
- case DeferTriggerKind.Never:
- case DeferTriggerKind.Idle:
- case DeferTriggerKind.Immediate:
- break;
- case DeferTriggerKind.Timer:
- args = [op.trigger.delay];
- break;
- case DeferTriggerKind.Interaction:
- case DeferTriggerKind.Hover:
- case DeferTriggerKind.Viewport:
- // `hydrate` triggers don't support targets.
- if (op.modifier === "hydrate" /* ir.DeferOpModifierKind.HYDRATE */) {
- args = [];
- }
- else {
- if (op.trigger.targetSlot?.slot == null || op.trigger.targetSlotViewSteps === null) {
- throw new Error(`Slot or view steps not set in trigger reification for trigger kind ${op.trigger.kind}`);
- }
- args = [op.trigger.targetSlot.slot];
- if (op.trigger.targetSlotViewSteps !== 0) {
- args.push(op.trigger.targetSlotViewSteps);
- }
- }
- break;
- default:
- throw new Error(`AssertionError: Unsupported reification of defer trigger kind ${op.trigger.kind}`);
- }
- OpList.replace(op, deferOn(op.trigger.kind, args, op.modifier, op.sourceSpan));
- break;
- case OpKind.ProjectionDef:
- OpList.replace(op, projectionDef(op.def));
- break;
- case OpKind.Projection:
- if (op.handle.slot === null) {
- throw new Error('No slot was assigned for project instruction');
- }
- let fallbackViewFnName = null;
- let fallbackDecls = null;
- let fallbackVars = null;
- if (op.fallbackView !== null) {
- if (!(unit instanceof ViewCompilationUnit)) {
- throw new Error(`AssertionError: must be compiling a component`);
- }
- const fallbackView = unit.job.views.get(op.fallbackView);
- if (fallbackView === undefined) {
- throw new Error('AssertionError: projection had fallback view xref, but fallback view was not found');
- }
- if (fallbackView.fnName === null ||
- fallbackView.decls === null ||
- fallbackView.vars === null) {
- throw new Error(`AssertionError: expected projection fallback view to have been named and counted`);
- }
- fallbackViewFnName = fallbackView.fnName;
- fallbackDecls = fallbackView.decls;
- fallbackVars = fallbackView.vars;
- }
- OpList.replace(op, projection(op.handle.slot, op.projectionSlotIndex, op.attributes, fallbackViewFnName, fallbackDecls, fallbackVars, op.sourceSpan));
- break;
- case OpKind.RepeaterCreate:
- if (op.handle.slot === null) {
- throw new Error('No slot was assigned for repeater instruction');
- }
- if (!(unit instanceof ViewCompilationUnit)) {
- throw new Error(`AssertionError: must be compiling a component`);
- }
- const repeaterView = unit.job.views.get(op.xref);
- if (repeaterView.fnName === null) {
- throw new Error(`AssertionError: expected repeater primary view to have been named`);
- }
- let emptyViewFnName = null;
- let emptyDecls = null;
- let emptyVars = null;
- if (op.emptyView !== null) {
- const emptyView = unit.job.views.get(op.emptyView);
- if (emptyView === undefined) {
- throw new Error('AssertionError: repeater had empty view xref, but empty view was not found');
- }
- if (emptyView.fnName === null || emptyView.decls === null || emptyView.vars === null) {
- throw new Error(`AssertionError: expected repeater empty view to have been named and counted`);
- }
- emptyViewFnName = emptyView.fnName;
- emptyDecls = emptyView.decls;
- emptyVars = emptyView.vars;
- }
- OpList.replace(op, repeaterCreate(op.handle.slot, repeaterView.fnName, op.decls, op.vars, op.tag, op.attributes, reifyTrackBy(unit, op), op.usesComponentInstance, emptyViewFnName, emptyDecls, emptyVars, op.emptyTag, op.emptyAttributes, op.wholeSourceSpan));
- break;
- case OpKind.SourceLocation:
- const locationsLiteral = literalArr(op.locations.map(({ targetSlot, offset, line, column }) => {
- if (targetSlot.slot === null) {
- throw new Error('No slot was assigned for source location');
- }
- return literalArr([
- literal$1(targetSlot.slot),
- literal$1(offset),
- literal$1(line),
- literal$1(column),
- ]);
- }));
- OpList.replace(op, attachSourceLocation(op.templatePath, locationsLiteral));
- break;
- case OpKind.Statement:
- // Pass statement operations directly through.
- break;
- default:
- throw new Error(`AssertionError: Unsupported reification of create op ${OpKind[op.kind]}`);
- }
- }
- }
- function reifyUpdateOperations(_unit, ops) {
- for (const op of ops) {
- transformExpressionsInOp(op, reifyIrExpression, VisitorContextFlag.None);
- switch (op.kind) {
- case OpKind.Advance:
- OpList.replace(op, advance(op.delta, op.sourceSpan));
- break;
- case OpKind.Property:
- if (op.expression instanceof Interpolation) {
- OpList.replace(op, propertyInterpolate(op.name, op.expression.strings, op.expression.expressions, op.sanitizer, op.sourceSpan));
- }
- else {
- OpList.replace(op, property(op.name, op.expression, op.sanitizer, op.sourceSpan));
- }
- break;
- case OpKind.TwoWayProperty:
- OpList.replace(op, twoWayProperty(op.name, op.expression, op.sanitizer, op.sourceSpan));
- break;
- case OpKind.StyleProp:
- if (op.expression instanceof Interpolation) {
- OpList.replace(op, stylePropInterpolate(op.name, op.expression.strings, op.expression.expressions, op.unit, op.sourceSpan));
- }
- else {
- OpList.replace(op, styleProp(op.name, op.expression, op.unit, op.sourceSpan));
- }
- break;
- case OpKind.ClassProp:
- OpList.replace(op, classProp(op.name, op.expression, op.sourceSpan));
- break;
- case OpKind.StyleMap:
- if (op.expression instanceof Interpolation) {
- OpList.replace(op, styleMapInterpolate(op.expression.strings, op.expression.expressions, op.sourceSpan));
- }
- else {
- OpList.replace(op, styleMap(op.expression, op.sourceSpan));
- }
- break;
- case OpKind.ClassMap:
- if (op.expression instanceof Interpolation) {
- OpList.replace(op, classMapInterpolate(op.expression.strings, op.expression.expressions, op.sourceSpan));
- }
- else {
- OpList.replace(op, classMap(op.expression, op.sourceSpan));
- }
- break;
- case OpKind.I18nExpression:
- OpList.replace(op, i18nExp(op.expression, op.sourceSpan));
- break;
- case OpKind.I18nApply:
- OpList.replace(op, i18nApply(op.handle.slot, op.sourceSpan));
- break;
- case OpKind.InterpolateText:
- OpList.replace(op, textInterpolate(op.interpolation.strings, op.interpolation.expressions, op.sourceSpan));
- break;
- case OpKind.Attribute:
- if (op.expression instanceof Interpolation) {
- OpList.replace(op, attributeInterpolate(op.name, op.expression.strings, op.expression.expressions, op.sanitizer, op.sourceSpan));
- }
- else {
- OpList.replace(op, attribute(op.name, op.expression, op.sanitizer, op.namespace));
- }
- break;
- case OpKind.HostProperty:
- if (op.expression instanceof Interpolation) {
- throw new Error('not yet handled');
- }
- else {
- if (op.isAnimationTrigger) {
- OpList.replace(op, syntheticHostProperty(op.name, op.expression, op.sourceSpan));
- }
- else {
- OpList.replace(op, hostProperty(op.name, op.expression, op.sanitizer, op.sourceSpan));
- }
- }
- break;
- case OpKind.Variable:
- if (op.variable.name === null) {
- throw new Error(`AssertionError: unnamed variable ${op.xref}`);
- }
- OpList.replace(op, createStatementOp(new DeclareVarStmt(op.variable.name, op.initializer, undefined, exports.StmtModifier.Final)));
- break;
- case OpKind.Conditional:
- if (op.processed === null) {
- throw new Error(`Conditional test was not set.`);
- }
- OpList.replace(op, conditional(op.processed, op.contextValue, op.sourceSpan));
- break;
- case OpKind.Repeater:
- OpList.replace(op, repeater(op.collection, op.sourceSpan));
- break;
- case OpKind.DeferWhen:
- OpList.replace(op, deferWhen(op.modifier, op.expr, op.sourceSpan));
- break;
- case OpKind.StoreLet:
- throw new Error(`AssertionError: unexpected storeLet ${op.declaredName}`);
- case OpKind.Statement:
- // Pass statement operations directly through.
- break;
- default:
- throw new Error(`AssertionError: Unsupported reification of update op ${OpKind[op.kind]}`);
- }
- }
- }
- function reifyIrExpression(expr) {
- if (!isIrExpression(expr)) {
- return expr;
- }
- switch (expr.kind) {
- case ExpressionKind.NextContext:
- return nextContext(expr.steps);
- case ExpressionKind.Reference:
- return reference(expr.targetSlot.slot + 1 + expr.offset);
- case ExpressionKind.LexicalRead:
- throw new Error(`AssertionError: unresolved LexicalRead of ${expr.name}`);
- case ExpressionKind.TwoWayBindingSet:
- throw new Error(`AssertionError: unresolved TwoWayBindingSet`);
- case ExpressionKind.RestoreView:
- if (typeof expr.view === 'number') {
- throw new Error(`AssertionError: unresolved RestoreView`);
- }
- return restoreView(expr.view);
- case ExpressionKind.ResetView:
- return resetView(expr.expr);
- case ExpressionKind.GetCurrentView:
- return getCurrentView();
- case ExpressionKind.ReadVariable:
- if (expr.name === null) {
- throw new Error(`Read of unnamed variable ${expr.xref}`);
- }
- return variable(expr.name);
- case ExpressionKind.ReadTemporaryExpr:
- if (expr.name === null) {
- throw new Error(`Read of unnamed temporary ${expr.xref}`);
- }
- return variable(expr.name);
- case ExpressionKind.AssignTemporaryExpr:
- if (expr.name === null) {
- throw new Error(`Assign of unnamed temporary ${expr.xref}`);
- }
- return variable(expr.name).set(expr.expr);
- case ExpressionKind.PureFunctionExpr:
- if (expr.fn === null) {
- throw new Error(`AssertionError: expected PureFunctions to have been extracted`);
- }
- return pureFunction(expr.varOffset, expr.fn, expr.args);
- case ExpressionKind.PureFunctionParameterExpr:
- throw new Error(`AssertionError: expected PureFunctionParameterExpr to have been extracted`);
- case ExpressionKind.PipeBinding:
- return pipeBind(expr.targetSlot.slot, expr.varOffset, expr.args);
- case ExpressionKind.PipeBindingVariadic:
- return pipeBindV(expr.targetSlot.slot, expr.varOffset, expr.args);
- case ExpressionKind.SlotLiteralExpr:
- return literal$1(expr.slot.slot);
- case ExpressionKind.ContextLetReference:
- return readContextLet(expr.targetSlot.slot);
- case ExpressionKind.StoreLet:
- return storeLet(expr.value, expr.sourceSpan);
- case ExpressionKind.TrackContext:
- return variable('this');
- default:
- throw new Error(`AssertionError: Unsupported reification of ir.Expression kind: ${ExpressionKind[expr.kind]}`);
- }
- }
- /**
- * Listeners get turned into a function expression, which may or may not have the `$event`
- * parameter defined.
- */
- function reifyListenerHandler(unit, name, handlerOps, consumesDollarEvent) {
- // First, reify all instruction calls within `handlerOps`.
- reifyUpdateOperations(unit, handlerOps);
- // Next, extract all the `o.Statement`s from the reified operations. We can expect that at this
- // point, all operations have been converted to statements.
- const handlerStmts = [];
- for (const op of handlerOps) {
- if (op.kind !== OpKind.Statement) {
- throw new Error(`AssertionError: expected reified statements, but found op ${OpKind[op.kind]}`);
- }
- handlerStmts.push(op.statement);
- }
- // If `$event` is referenced, we need to generate it as a parameter.
- const params = [];
- if (consumesDollarEvent) {
- // We need the `$event` parameter.
- params.push(new FnParam('$event'));
- }
- return fn(params, handlerStmts, undefined, undefined, name);
- }
- /** Reifies the tracking expression of a `RepeaterCreateOp`. */
- function reifyTrackBy(unit, op) {
- // If the tracking function was created already, there's nothing left to do.
- if (op.trackByFn !== null) {
- return op.trackByFn;
- }
- const params = [new FnParam('$index'), new FnParam('$item')];
- let fn$1;
- if (op.trackByOps === null) {
- // If there are no additional ops related to the tracking function, we just need
- // to turn it into a function that returns the result of the expression.
- fn$1 = op.usesComponentInstance
- ? fn(params, [new ReturnStatement(op.track)])
- : arrowFn(params, op.track);
- }
- else {
- // Otherwise first we need to reify the track-related ops.
- reifyUpdateOperations(unit, op.trackByOps);
- const statements = [];
- for (const trackOp of op.trackByOps) {
- if (trackOp.kind !== OpKind.Statement) {
- throw new Error(`AssertionError: expected reified statements, but found op ${OpKind[trackOp.kind]}`);
- }
- statements.push(trackOp.statement);
- }
- // Afterwards we can create the function from those ops.
- fn$1 =
- op.usesComponentInstance ||
- statements.length !== 1 ||
- !(statements[0] instanceof ReturnStatement)
- ? fn(params, statements)
- : arrowFn(params, statements[0].value);
- }
- op.trackByFn = unit.job.pool.getSharedFunctionReference(fn$1, '_forTrack');
- return op.trackByFn;
- }
- /**
- * Binding with no content can be safely deleted.
- */
- function removeEmptyBindings(job) {
- for (const unit of job.units) {
- for (const op of unit.update) {
- switch (op.kind) {
- case OpKind.Attribute:
- case OpKind.Binding:
- case OpKind.ClassProp:
- case OpKind.ClassMap:
- case OpKind.Property:
- case OpKind.StyleProp:
- case OpKind.StyleMap:
- if (op.expression instanceof EmptyExpr) {
- OpList.remove(op);
- }
- break;
- }
- }
- }
- }
- /**
- * Remove the i18n context ops after they are no longer needed, and null out references to them to
- * be safe.
- */
- function removeI18nContexts(job) {
- for (const unit of job.units) {
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nContext:
- OpList.remove(op);
- break;
- case OpKind.I18nStart:
- op.context = null;
- break;
- }
- }
- }
- }
- /**
- * i18nAttributes ops will be generated for each i18n attribute. However, not all i18n attribues
- * will contain dynamic content, and so some of these i18nAttributes ops may be unnecessary.
- */
- function removeUnusedI18nAttributesOps(job) {
- for (const unit of job.units) {
- const ownersWithI18nExpressions = new Set();
- for (const op of unit.update) {
- switch (op.kind) {
- case OpKind.I18nExpression:
- ownersWithI18nExpressions.add(op.i18nOwner);
- }
- }
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nAttributes:
- if (ownersWithI18nExpressions.has(op.xref)) {
- continue;
- }
- OpList.remove(op);
- }
- }
- }
- }
- /**
- * Resolves `ir.ContextExpr` expressions (which represent embedded view or component contexts) to
- * either the `ctx` parameter to component functions (for the current view context) or to variables
- * that store those contexts (for contexts accessed via the `nextContext()` instruction).
- */
- function resolveContexts(job) {
- for (const unit of job.units) {
- processLexicalScope$1(unit, unit.create);
- processLexicalScope$1(unit, unit.update);
- }
- }
- function processLexicalScope$1(view, ops) {
- // Track the expressions used to access all available contexts within the current view, by the
- // view `ir.XrefId`.
- const scope = new Map();
- // The current view's context is accessible via the `ctx` parameter.
- scope.set(view.xref, variable('ctx'));
- for (const op of ops) {
- switch (op.kind) {
- case OpKind.Variable:
- switch (op.variable.kind) {
- case SemanticVariableKind.Context:
- scope.set(op.variable.view, new ReadVariableExpr(op.xref));
- break;
- }
- break;
- case OpKind.Listener:
- case OpKind.TwoWayListener:
- processLexicalScope$1(view, op.handlerOps);
- break;
- case OpKind.RepeaterCreate:
- if (op.trackByOps !== null) {
- processLexicalScope$1(view, op.trackByOps);
- }
- break;
- }
- }
- if (view === view.job.root) {
- // Prefer `ctx` of the root view to any variables which happen to contain the root context.
- scope.set(view.xref, variable('ctx'));
- }
- for (const op of ops) {
- transformExpressionsInOp(op, (expr) => {
- if (expr instanceof ContextExpr) {
- if (!scope.has(expr.view)) {
- throw new Error(`No context found for reference to view ${expr.view} from view ${view.xref}`);
- }
- return scope.get(expr.view);
- }
- else {
- return expr;
- }
- }, VisitorContextFlag.None);
- }
- }
- /**
- * Any variable inside a listener with the name `$event` will be transformed into a output lexical
- * read immediately, and does not participate in any of the normal logic for handling variables.
- */
- function resolveDollarEvent(job) {
- for (const unit of job.units) {
- transformDollarEvent(unit.create);
- transformDollarEvent(unit.update);
- }
- }
- function transformDollarEvent(ops) {
- for (const op of ops) {
- if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
- transformExpressionsInOp(op, (expr) => {
- if (expr instanceof LexicalReadExpr && expr.name === '$event') {
- // Two-way listeners always consume `$event` so they omit this field.
- if (op.kind === OpKind.Listener) {
- op.consumesDollarEvent = true;
- }
- return new ReadVarExpr(expr.name);
- }
- return expr;
- }, VisitorContextFlag.InChildOperation);
- }
- }
- }
- /**
- * Resolve the element placeholders in i18n messages.
- */
- function resolveI18nElementPlaceholders(job) {
- // Record all of the element and i18n context ops for use later.
- const i18nContexts = new Map();
- const elements = new Map();
- for (const unit of job.units) {
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nContext:
- i18nContexts.set(op.xref, op);
- break;
- case OpKind.ElementStart:
- elements.set(op.xref, op);
- break;
- }
- }
- }
- resolvePlaceholdersForView(job, job.root, i18nContexts, elements);
- }
- /**
- * Recursively resolves element and template tag placeholders in the given view.
- */
- function resolvePlaceholdersForView(job, unit, i18nContexts, elements, pendingStructuralDirective) {
- // Track the current i18n op and corresponding i18n context op as we step through the creation
- // IR.
- let currentOps = null;
- let pendingStructuralDirectiveCloses = new Map();
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nStart:
- if (!op.context) {
- throw Error('Could not find i18n context for i18n op');
- }
- currentOps = { i18nBlock: op, i18nContext: i18nContexts.get(op.context) };
- break;
- case OpKind.I18nEnd:
- currentOps = null;
- break;
- case OpKind.ElementStart:
- // For elements with i18n placeholders, record its slot value in the params map under the
- // corresponding tag start placeholder.
- if (op.i18nPlaceholder !== undefined) {
- if (currentOps === null) {
- throw Error('i18n tag placeholder should only occur inside an i18n block');
- }
- recordElementStart(op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
- // If there is a separate close tag placeholder for this element, save the pending
- // structural directive so we can pass it to the closing tag as well.
- if (pendingStructuralDirective && op.i18nPlaceholder.closeName) {
- pendingStructuralDirectiveCloses.set(op.xref, pendingStructuralDirective);
- }
- // Clear out the pending structural directive now that its been accounted for.
- pendingStructuralDirective = undefined;
- }
- break;
- case OpKind.ElementEnd:
- // For elements with i18n placeholders, record its slot value in the params map under the
- // corresponding tag close placeholder.
- const startOp = elements.get(op.xref);
- if (startOp && startOp.i18nPlaceholder !== undefined) {
- if (currentOps === null) {
- throw Error('AssertionError: i18n tag placeholder should only occur inside an i18n block');
- }
- recordElementClose(startOp, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirectiveCloses.get(op.xref));
- // Clear out the pending structural directive close that was accounted for.
- pendingStructuralDirectiveCloses.delete(op.xref);
- }
- break;
- case OpKind.Projection:
- // For content projections with i18n placeholders, record its slot value in the params map
- // under the corresponding tag start and close placeholders.
- if (op.i18nPlaceholder !== undefined) {
- if (currentOps === null) {
- throw Error('i18n tag placeholder should only occur inside an i18n block');
- }
- recordElementStart(op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
- recordElementClose(op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
- // Clear out the pending structural directive now that its been accounted for.
- pendingStructuralDirective = undefined;
- }
- break;
- case OpKind.Template:
- const view = job.views.get(op.xref);
- if (op.i18nPlaceholder === undefined) {
- // If there is no i18n placeholder, just recurse into the view in case it contains i18n
- // blocks.
- resolvePlaceholdersForView(job, view, i18nContexts, elements);
- }
- else {
- if (currentOps === null) {
- throw Error('i18n tag placeholder should only occur inside an i18n block');
- }
- if (op.templateKind === TemplateKind.Structural) {
- // If this is a structural directive template, don't record anything yet. Instead pass
- // the current template as a pending structural directive to be recorded when we find
- // the element, content, or template it belongs to. This allows us to create combined
- // values that represent, e.g. the start of a template and element at the same time.
- resolvePlaceholdersForView(job, view, i18nContexts, elements, op);
- }
- else {
- // If this is some other kind of template, we can record its start, recurse into its
- // view, and then record its end.
- recordTemplateStart(job, view, op.handle.slot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
- resolvePlaceholdersForView(job, view, i18nContexts, elements);
- recordTemplateClose(job, view, op.handle.slot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
- pendingStructuralDirective = undefined;
- }
- }
- break;
- case OpKind.RepeaterCreate:
- if (pendingStructuralDirective !== undefined) {
- throw Error('AssertionError: Unexpected structural directive associated with @for block');
- }
- // RepeaterCreate has 3 slots: the first is for the op itself, the second is for the @for
- // template and the (optional) third is for the @empty template.
- const forSlot = op.handle.slot + 1;
- const forView = job.views.get(op.xref);
- // First record all of the placeholders for the @for template.
- if (op.i18nPlaceholder === undefined) {
- // If there is no i18n placeholder, just recurse into the view in case it contains i18n
- // blocks.
- resolvePlaceholdersForView(job, forView, i18nContexts, elements);
- }
- else {
- if (currentOps === null) {
- throw Error('i18n tag placeholder should only occur inside an i18n block');
- }
- recordTemplateStart(job, forView, forSlot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
- resolvePlaceholdersForView(job, forView, i18nContexts, elements);
- recordTemplateClose(job, forView, forSlot, op.i18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
- pendingStructuralDirective = undefined;
- }
- // Then if there's an @empty template, add its placeholders as well.
- if (op.emptyView !== null) {
- // RepeaterCreate has 3 slots: the first is for the op itself, the second is for the @for
- // template and the (optional) third is for the @empty template.
- const emptySlot = op.handle.slot + 2;
- const emptyView = job.views.get(op.emptyView);
- if (op.emptyI18nPlaceholder === undefined) {
- // If there is no i18n placeholder, just recurse into the view in case it contains i18n
- // blocks.
- resolvePlaceholdersForView(job, emptyView, i18nContexts, elements);
- }
- else {
- if (currentOps === null) {
- throw Error('i18n tag placeholder should only occur inside an i18n block');
- }
- recordTemplateStart(job, emptyView, emptySlot, op.emptyI18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
- resolvePlaceholdersForView(job, emptyView, i18nContexts, elements);
- recordTemplateClose(job, emptyView, emptySlot, op.emptyI18nPlaceholder, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
- pendingStructuralDirective = undefined;
- }
- }
- break;
- }
- }
- }
- /**
- * Records an i18n param value for the start of an element.
- */
- function recordElementStart(op, i18nContext, i18nBlock, structuralDirective) {
- const { startName, closeName } = op.i18nPlaceholder;
- let flags = I18nParamValueFlags.ElementTag | I18nParamValueFlags.OpenTag;
- let value = op.handle.slot;
- // If the element is associated with a structural directive, start it as well.
- if (structuralDirective !== undefined) {
- flags |= I18nParamValueFlags.TemplateTag;
- value = { element: value, template: structuralDirective.handle.slot };
- }
- // For self-closing tags, there is no close tag placeholder. Instead, the start tag
- // placeholder accounts for the start and close of the element.
- if (!closeName) {
- flags |= I18nParamValueFlags.CloseTag;
- }
- addParam(i18nContext.params, startName, value, i18nBlock.subTemplateIndex, flags);
- }
- /**
- * Records an i18n param value for the closing of an element.
- */
- function recordElementClose(op, i18nContext, i18nBlock, structuralDirective) {
- const { closeName } = op.i18nPlaceholder;
- // Self-closing tags don't have a closing tag placeholder, instead the element closing is
- // recorded via an additional flag on the element start value.
- if (closeName) {
- let flags = I18nParamValueFlags.ElementTag | I18nParamValueFlags.CloseTag;
- let value = op.handle.slot;
- // If the element is associated with a structural directive, close it as well.
- if (structuralDirective !== undefined) {
- flags |= I18nParamValueFlags.TemplateTag;
- value = { element: value, template: structuralDirective.handle.slot };
- }
- addParam(i18nContext.params, closeName, value, i18nBlock.subTemplateIndex, flags);
- }
- }
- /**
- * Records an i18n param value for the start of a template.
- */
- function recordTemplateStart(job, view, slot, i18nPlaceholder, i18nContext, i18nBlock, structuralDirective) {
- let { startName, closeName } = i18nPlaceholder;
- let flags = I18nParamValueFlags.TemplateTag | I18nParamValueFlags.OpenTag;
- // For self-closing tags, there is no close tag placeholder. Instead, the start tag
- // placeholder accounts for the start and close of the element.
- if (!closeName) {
- flags |= I18nParamValueFlags.CloseTag;
- }
- // If the template is associated with a structural directive, record the structural directive's
- // start first. Since this template must be in the structural directive's view, we can just
- // directly use the current i18n block's sub-template index.
- if (structuralDirective !== undefined) {
- addParam(i18nContext.params, startName, structuralDirective.handle.slot, i18nBlock.subTemplateIndex, flags);
- }
- // Record the start of the template. For the sub-template index, pass the index for the template's
- // view, rather than the current i18n block's index.
- addParam(i18nContext.params, startName, slot, getSubTemplateIndexForTemplateTag(job, i18nBlock, view), flags);
- }
- /**
- * Records an i18n param value for the closing of a template.
- */
- function recordTemplateClose(job, view, slot, i18nPlaceholder, i18nContext, i18nBlock, structuralDirective) {
- const { closeName } = i18nPlaceholder;
- const flags = I18nParamValueFlags.TemplateTag | I18nParamValueFlags.CloseTag;
- // Self-closing tags don't have a closing tag placeholder, instead the template's closing is
- // recorded via an additional flag on the template start value.
- if (closeName) {
- // Record the closing of the template. For the sub-template index, pass the index for the
- // template's view, rather than the current i18n block's index.
- addParam(i18nContext.params, closeName, slot, getSubTemplateIndexForTemplateTag(job, i18nBlock, view), flags);
- // If the template is associated with a structural directive, record the structural directive's
- // closing after. Since this template must be in the structural directive's view, we can just
- // directly use the current i18n block's sub-template index.
- if (structuralDirective !== undefined) {
- addParam(i18nContext.params, closeName, structuralDirective.handle.slot, i18nBlock.subTemplateIndex, flags);
- }
- }
- }
- /**
- * Get the subTemplateIndex for the given template op. For template ops, use the subTemplateIndex of
- * the child i18n block inside the template.
- */
- function getSubTemplateIndexForTemplateTag(job, i18nOp, view) {
- for (const childOp of view.create) {
- if (childOp.kind === OpKind.I18nStart) {
- return childOp.subTemplateIndex;
- }
- }
- return i18nOp.subTemplateIndex;
- }
- /**
- * Add a param value to the given params map.
- */
- function addParam(params, placeholder, value, subTemplateIndex, flags) {
- const values = params.get(placeholder) ?? [];
- values.push({ value, subTemplateIndex, flags });
- params.set(placeholder, values);
- }
- /**
- * Resolve the i18n expression placeholders in i18n messages.
- */
- function resolveI18nExpressionPlaceholders(job) {
- // Record all of the i18n context ops, and the sub-template index for each i18n op.
- const subTemplateIndices = new Map();
- const i18nContexts = new Map();
- const icuPlaceholders = new Map();
- for (const unit of job.units) {
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nStart:
- subTemplateIndices.set(op.xref, op.subTemplateIndex);
- break;
- case OpKind.I18nContext:
- i18nContexts.set(op.xref, op);
- break;
- case OpKind.IcuPlaceholder:
- icuPlaceholders.set(op.xref, op);
- break;
- }
- }
- }
- // Keep track of the next available expression index for each i18n message.
- const expressionIndices = new Map();
- // Keep track of a reference index for each expression.
- // We use different references for normal i18n expressio and attribute i18n expressions. This is
- // because child i18n blocks in templates don't get their own context, since they're rolled into
- // the translated message of the parent, but they may target a different slot.
- const referenceIndex = (op) => op.usage === I18nExpressionFor.I18nText ? op.i18nOwner : op.context;
- for (const unit of job.units) {
- for (const op of unit.update) {
- if (op.kind === OpKind.I18nExpression) {
- const index = expressionIndices.get(referenceIndex(op)) || 0;
- const subTemplateIndex = subTemplateIndices.get(op.i18nOwner) ?? null;
- const value = {
- value: index,
- subTemplateIndex: subTemplateIndex,
- flags: I18nParamValueFlags.ExpressionIndex,
- };
- updatePlaceholder(op, value, i18nContexts, icuPlaceholders);
- expressionIndices.set(referenceIndex(op), index + 1);
- }
- }
- }
- }
- function updatePlaceholder(op, value, i18nContexts, icuPlaceholders) {
- if (op.i18nPlaceholder !== null) {
- const i18nContext = i18nContexts.get(op.context);
- const params = op.resolutionTime === I18nParamResolutionTime.Creation
- ? i18nContext.params
- : i18nContext.postprocessingParams;
- const values = params.get(op.i18nPlaceholder) || [];
- values.push(value);
- params.set(op.i18nPlaceholder, values);
- }
- if (op.icuPlaceholder !== null) {
- const icuPlaceholderOp = icuPlaceholders.get(op.icuPlaceholder);
- icuPlaceholderOp?.expressionPlaceholders.push(value);
- }
- }
- /**
- * Resolves lexical references in views (`ir.LexicalReadExpr`) to either a target variable or to
- * property reads on the top-level component context.
- *
- * Also matches `ir.RestoreViewExpr` expressions with the variables of their corresponding saved
- * views.
- */
- function resolveNames(job) {
- for (const unit of job.units) {
- processLexicalScope(unit, unit.create, null);
- processLexicalScope(unit, unit.update, null);
- }
- }
- function processLexicalScope(unit, ops, savedView) {
- // Maps names defined in the lexical scope of this template to the `ir.XrefId`s of the variable
- // declarations which represent those values.
- //
- // Since variables are generated in each view for the entire lexical scope (including any
- // identifiers from parent templates) only local variables need be considered here.
- const scope = new Map();
- // Symbols defined within the current scope. They take precedence over ones defined outside.
- const localDefinitions = new Map();
- // First, step through the operations list and:
- // 1) build up the `scope` mapping
- // 2) recurse into any listener functions
- for (const op of ops) {
- switch (op.kind) {
- case OpKind.Variable:
- switch (op.variable.kind) {
- case SemanticVariableKind.Identifier:
- if (op.variable.local) {
- if (localDefinitions.has(op.variable.identifier)) {
- continue;
- }
- localDefinitions.set(op.variable.identifier, op.xref);
- }
- else if (scope.has(op.variable.identifier)) {
- continue;
- }
- scope.set(op.variable.identifier, op.xref);
- break;
- case SemanticVariableKind.Alias:
- // This variable represents some kind of identifier which can be used in the template.
- if (scope.has(op.variable.identifier)) {
- continue;
- }
- scope.set(op.variable.identifier, op.xref);
- break;
- case SemanticVariableKind.SavedView:
- // This variable represents a snapshot of the current view context, and can be used to
- // restore that context within listener functions.
- savedView = {
- view: op.variable.view,
- variable: op.xref,
- };
- break;
- }
- break;
- case OpKind.Listener:
- case OpKind.TwoWayListener:
- // Listener functions have separate variable declarations, so process them as a separate
- // lexical scope.
- processLexicalScope(unit, op.handlerOps, savedView);
- break;
- case OpKind.RepeaterCreate:
- if (op.trackByOps !== null) {
- processLexicalScope(unit, op.trackByOps, savedView);
- }
- break;
- }
- }
- // Next, use the `scope` mapping to match `ir.LexicalReadExpr` with defined names in the lexical
- // scope. Also, look for `ir.RestoreViewExpr`s and match them with the snapshotted view context
- // variable.
- for (const op of ops) {
- if (op.kind == OpKind.Listener || op.kind === OpKind.TwoWayListener) {
- // Listeners were already processed above with their own scopes.
- continue;
- }
- transformExpressionsInOp(op, (expr) => {
- if (expr instanceof LexicalReadExpr) {
- // `expr` is a read of a name within the lexical scope of this view.
- // Either that name is defined within the current view, or it represents a property from the
- // main component context.
- if (localDefinitions.has(expr.name)) {
- return new ReadVariableExpr(localDefinitions.get(expr.name));
- }
- else if (scope.has(expr.name)) {
- // This was a defined variable in the current scope.
- return new ReadVariableExpr(scope.get(expr.name));
- }
- else {
- // Reading from the component context.
- return new ReadPropExpr(new ContextExpr(unit.job.root.xref), expr.name);
- }
- }
- else if (expr instanceof RestoreViewExpr && typeof expr.view === 'number') {
- // `ir.RestoreViewExpr` happens in listener functions and restores a saved view from the
- // parent creation list. We expect to find that we captured the `savedView` previously, and
- // that it matches the expected view to be restored.
- if (savedView === null || savedView.view !== expr.view) {
- throw new Error(`AssertionError: no saved view ${expr.view} from view ${unit.xref}`);
- }
- expr.view = new ReadVariableExpr(savedView.variable);
- return expr;
- }
- else {
- return expr;
- }
- }, VisitorContextFlag.None);
- }
- for (const op of ops) {
- visitExpressionsInOp(op, (expr) => {
- if (expr instanceof LexicalReadExpr) {
- throw new Error(`AssertionError: no lexical reads should remain, but found read of ${expr.name}`);
- }
- });
- }
- }
- /**
- * Map of security contexts to their sanitizer function.
- */
- const sanitizerFns = new Map([
- [SecurityContext.HTML, Identifiers.sanitizeHtml],
- [SecurityContext.RESOURCE_URL, Identifiers.sanitizeResourceUrl],
- [SecurityContext.SCRIPT, Identifiers.sanitizeScript],
- [SecurityContext.STYLE, Identifiers.sanitizeStyle],
- [SecurityContext.URL, Identifiers.sanitizeUrl],
- ]);
- /**
- * Map of security contexts to their trusted value function.
- */
- const trustedValueFns = new Map([
- [SecurityContext.HTML, Identifiers.trustConstantHtml],
- [SecurityContext.RESOURCE_URL, Identifiers.trustConstantResourceUrl],
- ]);
- /**
- * Resolves sanitization functions for ops that need them.
- */
- function resolveSanitizers(job) {
- for (const unit of job.units) {
- const elements = createOpXrefMap(unit);
- // For normal element bindings we create trusted values for security sensitive constant
- // attributes. However, for host bindings we skip this step (this matches what
- // TemplateDefinitionBuilder does).
- // TODO: Is the TDB behavior correct here?
- if (job.kind !== CompilationJobKind.Host) {
- for (const op of unit.create) {
- if (op.kind === OpKind.ExtractedAttribute) {
- const trustedValueFn = trustedValueFns.get(getOnlySecurityContext(op.securityContext)) ?? null;
- op.trustedValueFn = trustedValueFn !== null ? importExpr(trustedValueFn) : null;
- }
- }
- }
- for (const op of unit.update) {
- switch (op.kind) {
- case OpKind.Property:
- case OpKind.Attribute:
- case OpKind.HostProperty:
- let sanitizerFn = null;
- if (Array.isArray(op.securityContext) &&
- op.securityContext.length === 2 &&
- op.securityContext.indexOf(SecurityContext.URL) > -1 &&
- op.securityContext.indexOf(SecurityContext.RESOURCE_URL) > -1) {
- // When the host element isn't known, some URL attributes (such as "src" and "href") may
- // be part of multiple different security contexts. In this case we use special
- // sanitization function and select the actual sanitizer at runtime based on a tag name
- // that is provided while invoking sanitization function.
- sanitizerFn = Identifiers.sanitizeUrlOrResourceUrl;
- }
- else {
- sanitizerFn = sanitizerFns.get(getOnlySecurityContext(op.securityContext)) ?? null;
- }
- op.sanitizer = sanitizerFn !== null ? importExpr(sanitizerFn) : null;
- // If there was no sanitization function found based on the security context of an
- // attribute/property, check whether this attribute/property is one of the
- // security-sensitive <iframe> attributes (and that the current element is actually an
- // <iframe>).
- if (op.sanitizer === null) {
- let isIframe = false;
- if (job.kind === CompilationJobKind.Host || op.kind === OpKind.HostProperty) {
- // Note: for host bindings defined on a directive, we do not try to find all
- // possible places where it can be matched, so we can not determine whether
- // the host element is an <iframe>. In this case, we just assume it is and append a
- // validation function, which is invoked at runtime and would have access to the
- // underlying DOM element to check if it's an <iframe> and if so - run extra checks.
- isIframe = true;
- }
- else {
- // For a normal binding we can just check if the element its on is an iframe.
- const ownerOp = elements.get(op.target);
- if (ownerOp === undefined || !isElementOrContainerOp(ownerOp)) {
- throw Error('Property should have an element-like owner');
- }
- isIframe = isIframeElement(ownerOp);
- }
- if (isIframe && isIframeSecuritySensitiveAttr(op.name)) {
- op.sanitizer = importExpr(Identifiers.validateIframeAttribute);
- }
- }
- break;
- }
- }
- }
- }
- /**
- * Checks whether the given op represents an iframe element.
- */
- function isIframeElement(op) {
- return op.kind === OpKind.ElementStart && op.tag?.toLowerCase() === 'iframe';
- }
- /**
- * Asserts that there is only a single security context and returns it.
- */
- function getOnlySecurityContext(securityContext) {
- if (Array.isArray(securityContext)) {
- if (securityContext.length > 1) {
- // TODO: What should we do here? TDB just took the first one, but this feels like something we
- // would want to know about and create a special case for like we did for Url/ResourceUrl. My
- // guess is that, outside of the Url/ResourceUrl case, this never actually happens. If there
- // do turn out to be other cases, throwing an error until we can address it feels safer.
- throw Error(`AssertionError: Ambiguous security context`);
- }
- return securityContext[0] || SecurityContext.NONE;
- }
- return securityContext;
- }
- /**
- * Transforms a `TwoWayBindingSet` expression into an expression that either
- * sets a value through the `twoWayBindingSet` instruction or falls back to setting
- * the value directly. E.g. the expression `TwoWayBindingSet(target, value)` becomes:
- * `ng.twoWayBindingSet(target, value) || (target = value)`.
- */
- function transformTwoWayBindingSet(job) {
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind === OpKind.TwoWayListener) {
- transformExpressionsInOp(op, (expr) => {
- if (!(expr instanceof TwoWayBindingSetExpr)) {
- return expr;
- }
- const { target, value } = expr;
- if (target instanceof ReadPropExpr || target instanceof ReadKeyExpr) {
- return twoWayBindingSet(target, value).or(target.set(value));
- }
- // ASSUMPTION: here we're assuming that `ReadVariableExpr` will be a reference
- // to a local template variable. This appears to be the case at the time of writing.
- // If the expression is targeting a variable read, we only emit the `twoWayBindingSet`
- // since the fallback would be attempting to write into a constant. Invalid usages will be
- // flagged during template type checking.
- if (target instanceof ReadVariableExpr) {
- return twoWayBindingSet(target, value);
- }
- throw new Error(`Unsupported expression in two-way action binding.`);
- }, VisitorContextFlag.InChildOperation);
- }
- }
- }
- }
- /**
- * When inside of a listener, we may need access to one or more enclosing views. Therefore, each
- * view should save the current view, and each listener must have the ability to restore the
- * appropriate view. We eagerly generate all save view variables; they will be optimized away later.
- */
- function saveAndRestoreView(job) {
- for (const unit of job.units) {
- unit.create.prepend([
- createVariableOp(unit.job.allocateXrefId(), {
- kind: SemanticVariableKind.SavedView,
- name: null,
- view: unit.xref,
- }, new GetCurrentViewExpr(), VariableFlags.None),
- ]);
- for (const op of unit.create) {
- if (op.kind !== OpKind.Listener && op.kind !== OpKind.TwoWayListener) {
- continue;
- }
- // Embedded views always need the save/restore view operation.
- let needsRestoreView = unit !== job.root;
- if (!needsRestoreView) {
- for (const handlerOp of op.handlerOps) {
- visitExpressionsInOp(handlerOp, (expr) => {
- if (expr instanceof ReferenceExpr || expr instanceof ContextLetReferenceExpr) {
- // Listeners that reference() a local ref need the save/restore view operation.
- needsRestoreView = true;
- }
- });
- }
- }
- if (needsRestoreView) {
- addSaveRestoreViewOperationToListener(unit, op);
- }
- }
- }
- }
- function addSaveRestoreViewOperationToListener(unit, op) {
- op.handlerOps.prepend([
- createVariableOp(unit.job.allocateXrefId(), {
- kind: SemanticVariableKind.Context,
- name: null,
- view: unit.xref,
- }, new RestoreViewExpr(unit.xref), VariableFlags.None),
- ]);
- // The "restore view" operation in listeners requires a call to `resetView` to reset the
- // context prior to returning from the listener operation. Find any `return` statements in
- // the listener body and wrap them in a call to reset the view.
- for (const handlerOp of op.handlerOps) {
- if (handlerOp.kind === OpKind.Statement &&
- handlerOp.statement instanceof ReturnStatement) {
- handlerOp.statement.value = new ResetViewExpr(handlerOp.statement.value);
- }
- }
- }
- /**
- * Assign data slots for all operations which implement `ConsumesSlotOpTrait`, and propagate the
- * assigned data slots of those operations to any expressions which reference them via
- * `UsesSlotIndexTrait`.
- *
- * This phase is also responsible for counting the number of slots used for each view (its `decls`)
- * and propagating that number into the `Template` operations which declare embedded views.
- */
- function allocateSlots(job) {
- // Map of all declarations in all views within the component which require an assigned slot index.
- // This map needs to be global (across all views within the component) since it's possible to
- // reference a slot from one view from an expression within another (e.g. local references work
- // this way).
- const slotMap = new Map();
- // Process all views in the component and assign slot indexes.
- for (const unit of job.units) {
- // Slot indices start at 0 for each view (and are not unique between views).
- let slotCount = 0;
- for (const op of unit.create) {
- // Only consider declarations which consume data slots.
- if (!hasConsumesSlotTrait(op)) {
- continue;
- }
- // Assign slots to this declaration starting at the current `slotCount`.
- op.handle.slot = slotCount;
- // And track its assigned slot in the `slotMap`.
- slotMap.set(op.xref, op.handle.slot);
- // Each declaration may use more than 1 slot, so increment `slotCount` to reserve the number
- // of slots required.
- slotCount += op.numSlotsUsed;
- }
- // Record the total number of slots used on the view itself. This will later be propagated into
- // `ir.TemplateOp`s which declare those views (except for the root view).
- unit.decls = slotCount;
- }
- // After slot assignment, `slotMap` now contains slot assignments for every declaration in the
- // whole template, across all views. Next, look for expressions which implement
- // `UsesSlotIndexExprTrait` and propagate the assigned slot indexes into them.
- // Additionally, this second scan allows us to find `ir.TemplateOp`s which declare views and
- // propagate the number of slots used for each view into the operation which declares it.
- for (const unit of job.units) {
- for (const op of unit.ops()) {
- if (op.kind === OpKind.Template || op.kind === OpKind.RepeaterCreate) {
- // Record the number of slots used by the view this `ir.TemplateOp` declares in the
- // operation itself, so it can be emitted later.
- const childView = job.views.get(op.xref);
- op.decls = childView.decls;
- // TODO: currently we handle the decls for the RepeaterCreate empty template in the reify
- // phase. We should handle that here instead.
- }
- }
- }
- }
- /**
- * Transforms special-case bindings with 'style' or 'class' in their names. Must run before the
- * main binding specialization pass.
- */
- function specializeStyleBindings(job) {
- for (const unit of job.units) {
- for (const op of unit.update) {
- if (op.kind !== OpKind.Binding) {
- continue;
- }
- switch (op.bindingKind) {
- case BindingKind.ClassName:
- if (op.expression instanceof Interpolation) {
- throw new Error(`Unexpected interpolation in ClassName binding`);
- }
- OpList.replace(op, createClassPropOp(op.target, op.name, op.expression, op.sourceSpan));
- break;
- case BindingKind.StyleProperty:
- OpList.replace(op, createStylePropOp(op.target, op.name, op.expression, op.unit, op.sourceSpan));
- break;
- case BindingKind.Property:
- case BindingKind.Template:
- if (op.name === 'style') {
- OpList.replace(op, createStyleMapOp(op.target, op.expression, op.sourceSpan));
- }
- else if (op.name === 'class') {
- OpList.replace(op, createClassMapOp(op.target, op.expression, op.sourceSpan));
- }
- break;
- }
- }
- }
- }
- /**
- * Find all assignments and usages of temporary variables, which are linked to each other with cross
- * references. Generate names for each cross-reference, and add a `DeclareVarStmt` to initialize
- * them at the beginning of the update block.
- *
- * TODO: Sometimes, it will be possible to reuse names across different subexpressions. For example,
- * in the double keyed read `a?.[f()]?.[f()]`, the two function calls have non-overlapping scopes.
- * Implement an algorithm for reuse.
- */
- function generateTemporaryVariables(job) {
- for (const unit of job.units) {
- unit.create.prepend(generateTemporaries(unit.create));
- unit.update.prepend(generateTemporaries(unit.update));
- }
- }
- function generateTemporaries(ops) {
- let opCount = 0;
- let generatedStatements = [];
- // For each op, search for any variables that are assigned or read. For each variable, generate a
- // name and produce a `DeclareVarStmt` to the beginning of the block.
- for (const op of ops) {
- // Identify the final time each temp var is read.
- const finalReads = new Map();
- visitExpressionsInOp(op, (expr, flag) => {
- if (flag & VisitorContextFlag.InChildOperation) {
- return;
- }
- if (expr instanceof ReadTemporaryExpr) {
- finalReads.set(expr.xref, expr);
- }
- });
- // Name the temp vars, accounting for the fact that a name can be reused after it has been
- // read for the final time.
- let count = 0;
- const assigned = new Set();
- const released = new Set();
- const defs = new Map();
- visitExpressionsInOp(op, (expr, flag) => {
- if (flag & VisitorContextFlag.InChildOperation) {
- return;
- }
- if (expr instanceof AssignTemporaryExpr) {
- if (!assigned.has(expr.xref)) {
- assigned.add(expr.xref);
- // TODO: Exactly replicate the naming scheme used by `TemplateDefinitionBuilder`.
- // It seems to rely on an expression index instead of an op index.
- defs.set(expr.xref, `tmp_${opCount}_${count++}`);
- }
- assignName(defs, expr);
- }
- else if (expr instanceof ReadTemporaryExpr) {
- if (finalReads.get(expr.xref) === expr) {
- released.add(expr.xref);
- count--;
- }
- assignName(defs, expr);
- }
- });
- // Add declarations for the temp vars.
- generatedStatements.push(...Array.from(new Set(defs.values())).map((name) => createStatementOp(new DeclareVarStmt(name))));
- opCount++;
- if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
- op.handlerOps.prepend(generateTemporaries(op.handlerOps));
- }
- else if (op.kind === OpKind.RepeaterCreate && op.trackByOps !== null) {
- op.trackByOps.prepend(generateTemporaries(op.trackByOps));
- }
- }
- return generatedStatements;
- }
- /**
- * Assigns a name to the temporary variable in the given temporary variable expression.
- */
- function assignName(names, expr) {
- const name = names.get(expr.xref);
- if (name === undefined) {
- throw new Error(`Found xref with unassigned name: ${expr.xref}`);
- }
- expr.name = name;
- }
- /**
- * `track` functions in `for` repeaters can sometimes be "optimized," i.e. transformed into inline
- * expressions, in lieu of an external function call. For example, tracking by `$index` can be be
- * optimized into an inline `trackByIndex` reference. This phase checks track expressions for
- * optimizable cases.
- */
- function optimizeTrackFns(job) {
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind !== OpKind.RepeaterCreate) {
- continue;
- }
- if (op.track instanceof ReadVarExpr && op.track.name === '$index') {
- // Top-level access of `$index` uses the built in `repeaterTrackByIndex`.
- op.trackByFn = importExpr(Identifiers.repeaterTrackByIndex);
- }
- else if (op.track instanceof ReadVarExpr && op.track.name === '$item') {
- // Top-level access of the item uses the built in `repeaterTrackByIdentity`.
- op.trackByFn = importExpr(Identifiers.repeaterTrackByIdentity);
- }
- else if (isTrackByFunctionCall(job.root.xref, op.track)) {
- // Mark the function as using the component instance to play it safe
- // since the method might be using `this` internally (see #53628).
- op.usesComponentInstance = true;
- // Top-level method calls in the form of `fn($index, item)` can be passed in directly.
- if (op.track.receiver.receiver.view === unit.xref) {
- // TODO: this may be wrong
- op.trackByFn = op.track.receiver;
- }
- else {
- // This is a plain method call, but not in the component's root view.
- // We need to get the component instance, and then call the method on it.
- op.trackByFn = importExpr(Identifiers.componentInstance)
- .callFn([])
- .prop(op.track.receiver.name);
- // Because the context is not avaiable (without a special function), we don't want to
- // try to resolve it later. Let's get rid of it by overwriting the original track
- // expression (which won't be used anyway).
- op.track = op.trackByFn;
- }
- }
- else {
- // The track function could not be optimized.
- // Replace context reads with a special IR expression, since context reads in a track
- // function are emitted specially.
- op.track = transformExpressionsInExpression(op.track, (expr) => {
- if (expr instanceof PipeBindingExpr || expr instanceof PipeBindingVariadicExpr) {
- throw new Error(`Illegal State: Pipes are not allowed in this context`);
- }
- else if (expr instanceof ContextExpr) {
- op.usesComponentInstance = true;
- return new TrackContextExpr(expr.view);
- }
- return expr;
- }, VisitorContextFlag.None);
- // Also create an OpList for the tracking expression since it may need
- // additional ops when generating the final code (e.g. temporary variables).
- const trackOpList = new OpList();
- trackOpList.push(createStatementOp(new ReturnStatement(op.track, op.track.sourceSpan)));
- op.trackByOps = trackOpList;
- }
- }
- }
- }
- function isTrackByFunctionCall(rootView, expr) {
- if (!(expr instanceof InvokeFunctionExpr) || expr.args.length === 0 || expr.args.length > 2) {
- return false;
- }
- if (!(expr.receiver instanceof ReadPropExpr && expr.receiver.receiver instanceof ContextExpr) ||
- expr.receiver.receiver.view !== rootView) {
- return false;
- }
- const [arg0, arg1] = expr.args;
- if (!(arg0 instanceof ReadVarExpr) || arg0.name !== '$index') {
- return false;
- }
- else if (expr.args.length === 1) {
- return true;
- }
- if (!(arg1 instanceof ReadVarExpr) || arg1.name !== '$item') {
- return false;
- }
- return true;
- }
- /**
- * Inside the `track` expression on a `for` repeater, the `$index` and `$item` variables are
- * ambiently available. In this phase, we find those variable usages, and replace them with the
- * appropriate output read.
- */
- function generateTrackVariables(job) {
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind !== OpKind.RepeaterCreate) {
- continue;
- }
- op.track = transformExpressionsInExpression(op.track, (expr) => {
- if (expr instanceof LexicalReadExpr) {
- if (op.varNames.$index.has(expr.name)) {
- return variable('$index');
- }
- else if (expr.name === op.varNames.$implicit) {
- return variable('$item');
- }
- // TODO: handle prohibited context variables (emit as globals?)
- }
- return expr;
- }, VisitorContextFlag.None);
- }
- }
- }
- /**
- * Counts the number of variable slots used within each view, and stores that on the view itself, as
- * well as propagates it to the `ir.TemplateOp` for embedded views.
- */
- function countVariables(job) {
- // First, count the vars used in each view, and update the view-level counter.
- for (const unit of job.units) {
- let varCount = 0;
- // Count variables on top-level ops first. Don't explore nested expressions just yet.
- for (const op of unit.ops()) {
- if (hasConsumesVarsTrait(op)) {
- varCount += varsUsedByOp(op);
- }
- }
- // Count variables on expressions inside ops. We do this later because some of these expressions
- // might be conditional (e.g. `pipeBinding` inside of a ternary), and we don't want to interfere
- // with indices for top-level binding slots (e.g. `property`).
- for (const op of unit.ops()) {
- visitExpressionsInOp(op, (expr) => {
- if (!isIrExpression(expr)) {
- return;
- }
- // TemplateDefinitionBuilder assigns variable offsets for everything but pure functions
- // first, and then assigns offsets to pure functions lazily. We emulate that behavior by
- // assigning offsets in two passes instead of one, only in compatibility mode.
- if (job.compatibility === CompatibilityMode.TemplateDefinitionBuilder &&
- expr instanceof PureFunctionExpr) {
- return;
- }
- // Some expressions require knowledge of the number of variable slots consumed.
- if (hasUsesVarOffsetTrait(expr)) {
- expr.varOffset = varCount;
- }
- if (hasConsumesVarsTrait(expr)) {
- varCount += varsUsedByIrExpression(expr);
- }
- });
- }
- // Compatibility mode pass for pure function offsets (as explained above).
- if (job.compatibility === CompatibilityMode.TemplateDefinitionBuilder) {
- for (const op of unit.ops()) {
- visitExpressionsInOp(op, (expr) => {
- if (!isIrExpression(expr) || !(expr instanceof PureFunctionExpr)) {
- return;
- }
- // Some expressions require knowledge of the number of variable slots consumed.
- if (hasUsesVarOffsetTrait(expr)) {
- expr.varOffset = varCount;
- }
- if (hasConsumesVarsTrait(expr)) {
- varCount += varsUsedByIrExpression(expr);
- }
- });
- }
- }
- unit.vars = varCount;
- }
- if (job instanceof ComponentCompilationJob) {
- // Add var counts for each view to the `ir.TemplateOp` which declares that view (if the view is
- // an embedded view).
- for (const unit of job.units) {
- for (const op of unit.create) {
- if (op.kind !== OpKind.Template && op.kind !== OpKind.RepeaterCreate) {
- continue;
- }
- const childView = job.views.get(op.xref);
- op.vars = childView.vars;
- // TODO: currently we handle the vars for the RepeaterCreate empty template in the reify
- // phase. We should handle that here instead.
- }
- }
- }
- }
- /**
- * Different operations that implement `ir.UsesVarsTrait` use different numbers of variables, so
- * count the variables used by any particular `op`.
- */
- function varsUsedByOp(op) {
- let slots;
- switch (op.kind) {
- case OpKind.Property:
- case OpKind.HostProperty:
- case OpKind.Attribute:
- // All of these bindings use 1 variable slot, plus 1 slot for every interpolated expression,
- // if any.
- slots = 1;
- if (op.expression instanceof Interpolation && !isSingletonInterpolation(op.expression)) {
- slots += op.expression.expressions.length;
- }
- return slots;
- case OpKind.TwoWayProperty:
- // Two-way properties can only have expressions so they only need one variable slot.
- return 1;
- case OpKind.StyleProp:
- case OpKind.ClassProp:
- case OpKind.StyleMap:
- case OpKind.ClassMap:
- // Style & class bindings use 2 variable slots, plus 1 slot for every interpolated expression,
- // if any.
- slots = 2;
- if (op.expression instanceof Interpolation) {
- slots += op.expression.expressions.length;
- }
- return slots;
- case OpKind.InterpolateText:
- // `ir.InterpolateTextOp`s use a variable slot for each dynamic expression.
- return op.interpolation.expressions.length;
- case OpKind.I18nExpression:
- case OpKind.Conditional:
- case OpKind.DeferWhen:
- case OpKind.StoreLet:
- return 1;
- case OpKind.RepeaterCreate:
- // Repeaters may require an extra variable binding slot, if they have an empty view, for the
- // empty block tracking.
- // TODO: It's a bit odd to have a create mode instruction consume variable slots. Maybe we can
- // find a way to use the Repeater update op instead.
- return op.emptyView ? 1 : 0;
- default:
- throw new Error(`Unhandled op: ${OpKind[op.kind]}`);
- }
- }
- function varsUsedByIrExpression(expr) {
- switch (expr.kind) {
- case ExpressionKind.PureFunctionExpr:
- return 1 + expr.args.length;
- case ExpressionKind.PipeBinding:
- return 1 + expr.args.length;
- case ExpressionKind.PipeBindingVariadic:
- return 1 + expr.numArgs;
- case ExpressionKind.StoreLet:
- return 1;
- default:
- throw new Error(`AssertionError: unhandled ConsumesVarsTrait expression ${expr.constructor.name}`);
- }
- }
- function isSingletonInterpolation(expr) {
- if (expr.expressions.length !== 1 || expr.strings.length !== 2) {
- return false;
- }
- if (expr.strings[0] !== '' || expr.strings[1] !== '') {
- return false;
- }
- return true;
- }
- /**
- * Optimize variables declared and used in the IR.
- *
- * Variables are eagerly generated by pipeline stages for all possible values that could be
- * referenced. This stage processes the list of declared variables and all variable usages,
- * and optimizes where possible. It performs 3 main optimizations:
- *
- * * It transforms variable declarations to side effectful expressions when the
- * variable is not used, but its initializer has global effects which other
- * operations rely upon.
- * * It removes variable declarations if those variables are not referenced and
- * either they do not have global effects, or nothing relies on them.
- * * It inlines variable declarations when those variables are only used once
- * and the inlining is semantically safe.
- *
- * To guarantee correctness, analysis of "fences" in the instruction lists is used to determine
- * which optimizations are safe to perform.
- */
- function optimizeVariables(job) {
- for (const unit of job.units) {
- inlineAlwaysInlineVariables(unit.create);
- inlineAlwaysInlineVariables(unit.update);
- for (const op of unit.create) {
- if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
- inlineAlwaysInlineVariables(op.handlerOps);
- }
- else if (op.kind === OpKind.RepeaterCreate && op.trackByOps !== null) {
- inlineAlwaysInlineVariables(op.trackByOps);
- }
- }
- optimizeVariablesInOpList(unit.create, job.compatibility);
- optimizeVariablesInOpList(unit.update, job.compatibility);
- for (const op of unit.create) {
- if (op.kind === OpKind.Listener || op.kind === OpKind.TwoWayListener) {
- optimizeVariablesInOpList(op.handlerOps, job.compatibility);
- }
- else if (op.kind === OpKind.RepeaterCreate && op.trackByOps !== null) {
- optimizeVariablesInOpList(op.trackByOps, job.compatibility);
- }
- }
- }
- }
- /**
- * A [fence](https://en.wikipedia.org/wiki/Memory_barrier) flag for an expression which indicates
- * how that expression can be optimized in relation to other expressions or instructions.
- *
- * `Fence`s are a bitfield, so multiple flags may be set on a single expression.
- */
- var Fence;
- (function (Fence) {
- /**
- * Empty flag (no fence exists).
- */
- Fence[Fence["None"] = 0] = "None";
- /**
- * A context read fence, meaning that the expression in question reads from the "current view"
- * context of the runtime.
- */
- Fence[Fence["ViewContextRead"] = 1] = "ViewContextRead";
- /**
- * A context write fence, meaning that the expression in question writes to the "current view"
- * context of the runtime.
- *
- * Note that all `ContextWrite` fences are implicitly `ContextRead` fences as operations which
- * change the view context do so based on the current one.
- */
- Fence[Fence["ViewContextWrite"] = 2] = "ViewContextWrite";
- /**
- * Indicates that a call is required for its side-effects, even if nothing reads its result.
- *
- * This is also true of `ViewContextWrite` operations **if** they are followed by a
- * `ViewContextRead`.
- */
- Fence[Fence["SideEffectful"] = 4] = "SideEffectful";
- })(Fence || (Fence = {}));
- function inlineAlwaysInlineVariables(ops) {
- const vars = new Map();
- for (const op of ops) {
- if (op.kind === OpKind.Variable && op.flags & VariableFlags.AlwaysInline) {
- visitExpressionsInOp(op, (expr) => {
- if (isIrExpression(expr) && fencesForIrExpression(expr) !== Fence.None) {
- throw new Error(`AssertionError: A context-sensitive variable was marked AlwaysInline`);
- }
- });
- vars.set(op.xref, op);
- }
- transformExpressionsInOp(op, (expr) => {
- if (expr instanceof ReadVariableExpr && vars.has(expr.xref)) {
- const varOp = vars.get(expr.xref);
- // Inline by cloning, because we might inline into multiple places.
- return varOp.initializer.clone();
- }
- return expr;
- }, VisitorContextFlag.None);
- }
- for (const op of vars.values()) {
- OpList.remove(op);
- }
- }
- /**
- * Process a list of operations and optimize variables within that list.
- */
- function optimizeVariablesInOpList(ops, compatibility) {
- const varDecls = new Map();
- const varUsages = new Map();
- // Track variables that are used outside of the immediate operation list. For example, within
- // `ListenerOp` handler operations of listeners in the current operation list.
- const varRemoteUsages = new Set();
- const opMap = new Map();
- // First, extract information about variables declared or used within the whole list.
- for (const op of ops) {
- if (op.kind === OpKind.Variable) {
- if (varDecls.has(op.xref) || varUsages.has(op.xref)) {
- throw new Error(`Should not see two declarations of the same variable: ${op.xref}`);
- }
- varDecls.set(op.xref, op);
- varUsages.set(op.xref, 0);
- }
- opMap.set(op, collectOpInfo(op));
- countVariableUsages(op, varUsages, varRemoteUsages);
- }
- // The next step is to remove any variable declarations for variables that aren't used. The
- // variable initializer expressions may be side-effectful, so they may need to be retained as
- // expression statements.
- // Track whether we've seen an operation which reads from the view context yet. This is used to
- // determine whether a write to the view context in a variable initializer can be observed.
- let contextIsUsed = false;
- // Note that iteration through the list happens in reverse, which guarantees that we'll process
- // all reads of a variable prior to processing its declaration.
- for (const op of ops.reversed()) {
- const opInfo = opMap.get(op);
- if (op.kind === OpKind.Variable && varUsages.get(op.xref) === 0) {
- // This variable is unused and can be removed. We might need to keep the initializer around,
- // though, if something depends on it running.
- if ((contextIsUsed && opInfo.fences & Fence.ViewContextWrite) ||
- opInfo.fences & Fence.SideEffectful) {
- // This variable initializer has a side effect which must be retained. Either:
- // * it writes to the view context, and we know there is a future operation which depends
- // on that write, or
- // * it's an operation which is inherently side-effectful.
- // We can't remove the initializer, but we can remove the variable declaration itself and
- // replace it with a side-effectful statement.
- const stmtOp = createStatementOp(op.initializer.toStmt());
- opMap.set(stmtOp, opInfo);
- OpList.replace(op, stmtOp);
- }
- else {
- // It's safe to delete this entire variable declaration as nothing depends on it, even
- // side-effectfully. Note that doing this might make other variables unused. Since we're
- // iterating in reverse order, we should always be processing usages before declarations
- // and therefore by the time we get to a declaration, all removable usages will have been
- // removed.
- uncountVariableUsages(op, varUsages);
- OpList.remove(op);
- }
- opMap.delete(op);
- varDecls.delete(op.xref);
- varUsages.delete(op.xref);
- continue;
- }
- // Does this operation depend on the view context?
- if (opInfo.fences & Fence.ViewContextRead) {
- contextIsUsed = true;
- }
- }
- // Next, inline any remaining variables with exactly one usage.
- const toInline = [];
- for (const [id, count] of varUsages) {
- const decl = varDecls.get(id);
- // We can inline variables that:
- // - are used exactly once, and
- // - are not used remotely
- // OR
- // - are marked for always inlining
- const isAlwaysInline = !!(decl.flags & VariableFlags.AlwaysInline);
- if (count !== 1 || isAlwaysInline) {
- // We can't inline this variable as it's used more than once.
- continue;
- }
- if (varRemoteUsages.has(id)) {
- // This variable is used once, but across an operation boundary, so it can't be inlined.
- continue;
- }
- toInline.push(id);
- }
- let candidate;
- while ((candidate = toInline.pop())) {
- // We will attempt to inline this variable. If inlining fails (due to fences for example),
- // no future operation will make inlining legal.
- const decl = varDecls.get(candidate);
- const varInfo = opMap.get(decl);
- const isAlwaysInline = !!(decl.flags & VariableFlags.AlwaysInline);
- if (isAlwaysInline) {
- throw new Error(`AssertionError: Found an 'AlwaysInline' variable after the always inlining pass.`);
- }
- // Scan operations following the variable declaration and look for the point where that variable
- // is used. There should only be one usage given the precondition above.
- for (let targetOp = decl.next; targetOp.kind !== OpKind.ListEnd; targetOp = targetOp.next) {
- const opInfo = opMap.get(targetOp);
- // Is the variable used in this operation?
- if (opInfo.variablesUsed.has(candidate)) {
- if (compatibility === CompatibilityMode.TemplateDefinitionBuilder &&
- !allowConservativeInlining(decl, targetOp)) {
- // We're in conservative mode, and this variable is not eligible for inlining into the
- // target operation in this mode.
- break;
- }
- // Yes, try to inline it. Inlining may not be successful if fences in this operation before
- // the variable's usage cannot be safely crossed.
- if (tryInlineVariableInitializer(candidate, decl.initializer, targetOp, varInfo.fences)) {
- // Inlining was successful! Update the tracking structures to reflect the inlined
- // variable.
- opInfo.variablesUsed.delete(candidate);
- // Add all variables used in the variable's initializer to its new usage site.
- for (const id of varInfo.variablesUsed) {
- opInfo.variablesUsed.add(id);
- }
- // Merge fences in the variable's initializer into its new usage site.
- opInfo.fences |= varInfo.fences;
- // Delete tracking info related to the declaration.
- varDecls.delete(candidate);
- varUsages.delete(candidate);
- opMap.delete(decl);
- // And finally, delete the original declaration from the operation list.
- OpList.remove(decl);
- }
- // Whether inlining succeeded or failed, we're done processing this variable.
- break;
- }
- // If the variable is not used in this operation, then we'd need to inline across it. Check if
- // that's safe to do.
- if (!safeToInlinePastFences(opInfo.fences, varInfo.fences)) {
- // We can't safely inline this variable beyond this operation, so don't proceed with
- // inlining this variable.
- break;
- }
- }
- }
- }
- /**
- * Given an `ir.Expression`, returns the `Fence` flags for that expression type.
- */
- function fencesForIrExpression(expr) {
- switch (expr.kind) {
- case ExpressionKind.NextContext:
- return Fence.ViewContextRead | Fence.ViewContextWrite;
- case ExpressionKind.RestoreView:
- return Fence.ViewContextRead | Fence.ViewContextWrite | Fence.SideEffectful;
- case ExpressionKind.StoreLet:
- return Fence.SideEffectful;
- case ExpressionKind.Reference:
- case ExpressionKind.ContextLetReference:
- return Fence.ViewContextRead;
- default:
- return Fence.None;
- }
- }
- /**
- * Build the `OpInfo` structure for the given `op`. This performs two operations:
- *
- * * It tracks which variables are used in the operation's expressions.
- * * It rolls up fence flags for expressions within the operation.
- */
- function collectOpInfo(op) {
- let fences = Fence.None;
- const variablesUsed = new Set();
- visitExpressionsInOp(op, (expr) => {
- if (!isIrExpression(expr)) {
- return;
- }
- switch (expr.kind) {
- case ExpressionKind.ReadVariable:
- variablesUsed.add(expr.xref);
- break;
- default:
- fences |= fencesForIrExpression(expr);
- }
- });
- return { fences, variablesUsed };
- }
- /**
- * Count the number of usages of each variable, being careful to track whether those usages are
- * local or remote.
- */
- function countVariableUsages(op, varUsages, varRemoteUsage) {
- visitExpressionsInOp(op, (expr, flags) => {
- if (!isIrExpression(expr)) {
- return;
- }
- if (expr.kind !== ExpressionKind.ReadVariable) {
- return;
- }
- const count = varUsages.get(expr.xref);
- if (count === undefined) {
- // This variable is declared outside the current scope of optimization.
- return;
- }
- varUsages.set(expr.xref, count + 1);
- if (flags & VisitorContextFlag.InChildOperation) {
- varRemoteUsage.add(expr.xref);
- }
- });
- }
- /**
- * Remove usages of a variable in `op` from the `varUsages` tracking.
- */
- function uncountVariableUsages(op, varUsages) {
- visitExpressionsInOp(op, (expr) => {
- if (!isIrExpression(expr)) {
- return;
- }
- if (expr.kind !== ExpressionKind.ReadVariable) {
- return;
- }
- const count = varUsages.get(expr.xref);
- if (count === undefined) {
- // This variable is declared outside the current scope of optimization.
- return;
- }
- else if (count === 0) {
- throw new Error(`Inaccurate variable count: ${expr.xref} - found another read but count is already 0`);
- }
- varUsages.set(expr.xref, count - 1);
- });
- }
- /**
- * Checks whether it's safe to inline a variable across a particular operation.
- *
- * @param fences the fences of the operation which the inlining will cross
- * @param declFences the fences of the variable being inlined.
- */
- function safeToInlinePastFences(fences, declFences) {
- if (fences & Fence.ViewContextWrite) {
- // It's not safe to inline context reads across context writes.
- if (declFences & Fence.ViewContextRead) {
- return false;
- }
- }
- else if (fences & Fence.ViewContextRead) {
- // It's not safe to inline context writes across context reads.
- if (declFences & Fence.ViewContextWrite) {
- return false;
- }
- }
- return true;
- }
- /**
- * Attempt to inline the initializer of a variable into a target operation's expressions.
- *
- * This may or may not be safe to do. For example, the variable could be read following the
- * execution of an expression with fences that don't permit the variable to be inlined across them.
- */
- function tryInlineVariableInitializer(id, initializer, target, declFences) {
- // We use `ir.transformExpressionsInOp` to walk the expressions and inline the variable if
- // possible. Since this operation is callback-based, once inlining succeeds or fails we can't
- // "stop" the expression processing, and have to keep track of whether inlining has succeeded or
- // is no longer allowed.
- let inlined = false;
- let inliningAllowed = true;
- transformExpressionsInOp(target, (expr, flags) => {
- if (!isIrExpression(expr)) {
- return expr;
- }
- if (inlined || !inliningAllowed) {
- // Either the inlining has already succeeded, or we've passed a fence that disallows inlining
- // at this point, so don't try.
- return expr;
- }
- else if (flags & VisitorContextFlag.InChildOperation &&
- declFences & Fence.ViewContextRead) {
- // We cannot inline variables that are sensitive to the current context across operation
- // boundaries.
- return expr;
- }
- switch (expr.kind) {
- case ExpressionKind.ReadVariable:
- if (expr.xref === id) {
- // This is the usage site of the variable. Since nothing has disallowed inlining, it's
- // safe to inline the initializer here.
- inlined = true;
- return initializer;
- }
- break;
- default:
- // For other types of `ir.Expression`s, whether inlining is allowed depends on their fences.
- const exprFences = fencesForIrExpression(expr);
- inliningAllowed = inliningAllowed && safeToInlinePastFences(exprFences, declFences);
- break;
- }
- return expr;
- }, VisitorContextFlag.None);
- return inlined;
- }
- /**
- * Determines whether inlining of `decl` should be allowed in "conservative" mode.
- *
- * In conservative mode, inlining behavior is limited to those operations which the
- * `TemplateDefinitionBuilder` supported, with the goal of producing equivalent output.
- */
- function allowConservativeInlining(decl, target) {
- // TODO(alxhub): understand exactly how TemplateDefinitionBuilder approaches inlining, and record
- // that behavior here.
- switch (decl.variable.kind) {
- case SemanticVariableKind.Identifier:
- if (decl.initializer instanceof ReadVarExpr && decl.initializer.name === 'ctx') {
- // Although TemplateDefinitionBuilder is cautious about inlining, we still want to do so
- // when the variable is the context, to imitate its behavior with aliases in control flow
- // blocks. This quirky behavior will become dead code once compatibility mode is no longer
- // supported.
- return true;
- }
- return false;
- case SemanticVariableKind.Context:
- // Context can only be inlined into other variables.
- return target.kind === OpKind.Variable;
- default:
- return true;
- }
- }
- /**
- * Wraps ICUs that do not already belong to an i18n block in a new i18n block.
- */
- function wrapI18nIcus(job) {
- for (const unit of job.units) {
- let currentI18nOp = null;
- let addedI18nId = null;
- for (const op of unit.create) {
- switch (op.kind) {
- case OpKind.I18nStart:
- currentI18nOp = op;
- break;
- case OpKind.I18nEnd:
- currentI18nOp = null;
- break;
- case OpKind.IcuStart:
- if (currentI18nOp === null) {
- addedI18nId = job.allocateXrefId();
- // ICU i18n start/end ops should not receive source spans.
- OpList.insertBefore(createI18nStartOp(addedI18nId, op.message, undefined, null), op);
- }
- break;
- case OpKind.IcuEnd:
- if (addedI18nId !== null) {
- OpList.insertAfter(createI18nEndOp(addedI18nId, null), op);
- addedI18nId = null;
- }
- break;
- }
- }
- }
- }
- /*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
- /**
- * Removes any `storeLet` calls that aren't referenced outside of the current view.
- */
- function optimizeStoreLet(job) {
- const letUsedExternally = new Set();
- // Since `@let` declarations can be referenced in child views, both in
- // the creation block (via listeners) and in the update block, we have
- // to look through all the ops to find the references.
- for (const unit of job.units) {
- for (const op of unit.ops()) {
- visitExpressionsInOp(op, (expr) => {
- if (expr instanceof ContextLetReferenceExpr) {
- letUsedExternally.add(expr.target);
- }
- });
- }
- }
- // TODO(crisbeto): potentially remove the unused calls completely, pending discussion.
- for (const unit of job.units) {
- for (const op of unit.update) {
- transformExpressionsInOp(op, (expression) => expression instanceof StoreLetExpr && !letUsedExternally.has(expression.target)
- ? expression.value
- : expression, VisitorContextFlag.None);
- }
- }
- }
- /**
- * It's not allowed to access a `@let` declaration before it has been defined. This is enforced
- * already via template type checking, however it can trip some of the assertions in the pipeline.
- * E.g. the naming phase can fail because we resolved the variable here, but the variable doesn't
- * exist anymore because the optimization phase removed it since it's invalid. To avoid surfacing
- * confusing errors to users in the case where template type checking isn't running (e.g. in JIT
- * mode) this phase detects illegal forward references and replaces them with `undefined`.
- * Eventually users will see the proper error from the template type checker.
- */
- function removeIllegalLetReferences(job) {
- for (const unit of job.units) {
- for (const op of unit.update) {
- if (op.kind !== OpKind.Variable ||
- op.variable.kind !== SemanticVariableKind.Identifier ||
- !(op.initializer instanceof StoreLetExpr)) {
- continue;
- }
- const name = op.variable.identifier;
- let current = op;
- while (current && current.kind !== OpKind.ListEnd) {
- transformExpressionsInOp(current, (expr) => expr instanceof LexicalReadExpr && expr.name === name ? literal$1(undefined) : expr, VisitorContextFlag.None);
- current = current.prev;
- }
- }
- }
- }
- /**
- * Replaces the `storeLet` ops with variables that can be
- * used to reference the value within the same view.
- */
- function generateLocalLetReferences(job) {
- for (const unit of job.units) {
- for (const op of unit.update) {
- if (op.kind !== OpKind.StoreLet) {
- continue;
- }
- const variable = {
- kind: SemanticVariableKind.Identifier,
- name: null,
- identifier: op.declaredName,
- local: true,
- };
- OpList.replace(op, createVariableOp(job.allocateXrefId(), variable, new StoreLetExpr(op.target, op.value, op.sourceSpan), VariableFlags.None));
- }
- }
- }
- /**
- * Locates all of the elements defined in a creation block and outputs an op
- * that will expose their definition location in the DOM.
- */
- function attachSourceLocations(job) {
- if (!job.enableDebugLocations || job.relativeTemplatePath === null) {
- return;
- }
- for (const unit of job.units) {
- const locations = [];
- for (const op of unit.create) {
- if (op.kind === OpKind.ElementStart || op.kind === OpKind.Element) {
- const start = op.startSourceSpan.start;
- locations.push({
- targetSlot: op.handle,
- offset: start.offset,
- line: start.line,
- column: start.col,
- });
- }
- }
- if (locations.length > 0) {
- unit.create.push(createSourceLocationOp(job.relativeTemplatePath, locations));
- }
- }
- }
- /**
- *
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
- const phases = [
- { kind: CompilationJobKind.Tmpl, fn: removeContentSelectors },
- { kind: CompilationJobKind.Host, fn: parseHostStyleProperties },
- { kind: CompilationJobKind.Tmpl, fn: emitNamespaceChanges },
- { kind: CompilationJobKind.Tmpl, fn: propagateI18nBlocks },
- { kind: CompilationJobKind.Tmpl, fn: wrapI18nIcus },
- { kind: CompilationJobKind.Both, fn: deduplicateTextBindings },
- { kind: CompilationJobKind.Both, fn: specializeStyleBindings },
- { kind: CompilationJobKind.Both, fn: specializeBindings },
- { kind: CompilationJobKind.Both, fn: extractAttributes },
- { kind: CompilationJobKind.Tmpl, fn: createI18nContexts },
- { kind: CompilationJobKind.Both, fn: parseExtractedStyles },
- { kind: CompilationJobKind.Tmpl, fn: removeEmptyBindings },
- { kind: CompilationJobKind.Both, fn: collapseSingletonInterpolations },
- { kind: CompilationJobKind.Both, fn: orderOps },
- { kind: CompilationJobKind.Tmpl, fn: generateConditionalExpressions },
- { kind: CompilationJobKind.Tmpl, fn: createPipes },
- { kind: CompilationJobKind.Tmpl, fn: configureDeferInstructions },
- { kind: CompilationJobKind.Tmpl, fn: convertI18nText },
- { kind: CompilationJobKind.Tmpl, fn: convertI18nBindings },
- { kind: CompilationJobKind.Tmpl, fn: removeUnusedI18nAttributesOps },
- { kind: CompilationJobKind.Tmpl, fn: assignI18nSlotDependencies },
- { kind: CompilationJobKind.Tmpl, fn: applyI18nExpressions },
- { kind: CompilationJobKind.Tmpl, fn: createVariadicPipes },
- { kind: CompilationJobKind.Both, fn: generatePureLiteralStructures },
- { kind: CompilationJobKind.Tmpl, fn: generateProjectionDefs },
- { kind: CompilationJobKind.Tmpl, fn: generateLocalLetReferences },
- { kind: CompilationJobKind.Tmpl, fn: generateVariables },
- { kind: CompilationJobKind.Tmpl, fn: saveAndRestoreView },
- { kind: CompilationJobKind.Both, fn: deleteAnyCasts },
- { kind: CompilationJobKind.Both, fn: resolveDollarEvent },
- { kind: CompilationJobKind.Tmpl, fn: generateTrackVariables },
- { kind: CompilationJobKind.Tmpl, fn: removeIllegalLetReferences },
- { kind: CompilationJobKind.Both, fn: resolveNames },
- { kind: CompilationJobKind.Tmpl, fn: resolveDeferTargetNames },
- { kind: CompilationJobKind.Tmpl, fn: transformTwoWayBindingSet },
- { kind: CompilationJobKind.Tmpl, fn: optimizeTrackFns },
- { kind: CompilationJobKind.Both, fn: resolveContexts },
- { kind: CompilationJobKind.Both, fn: resolveSanitizers },
- { kind: CompilationJobKind.Tmpl, fn: liftLocalRefs },
- { kind: CompilationJobKind.Both, fn: generateNullishCoalesceExpressions },
- { kind: CompilationJobKind.Both, fn: expandSafeReads },
- { kind: CompilationJobKind.Both, fn: generateTemporaryVariables },
- { kind: CompilationJobKind.Both, fn: optimizeVariables },
- { kind: CompilationJobKind.Both, fn: optimizeStoreLet },
- { kind: CompilationJobKind.Tmpl, fn: allocateSlots },
- { kind: CompilationJobKind.Tmpl, fn: resolveI18nElementPlaceholders },
- { kind: CompilationJobKind.Tmpl, fn: resolveI18nExpressionPlaceholders },
- { kind: CompilationJobKind.Tmpl, fn: extractI18nMessages },
- { kind: CompilationJobKind.Tmpl, fn: collectI18nConsts },
- { kind: CompilationJobKind.Tmpl, fn: collectConstExpressions },
- { kind: CompilationJobKind.Both, fn: collectElementConsts },
- { kind: CompilationJobKind.Tmpl, fn: removeI18nContexts },
- { kind: CompilationJobKind.Both, fn: countVariables },
- { kind: CompilationJobKind.Tmpl, fn: generateAdvance },
- { kind: CompilationJobKind.Both, fn: nameFunctionsAndVariables },
- { kind: CompilationJobKind.Tmpl, fn: resolveDeferDepsFns },
- { kind: CompilationJobKind.Tmpl, fn: mergeNextContextExpressions },
- { kind: CompilationJobKind.Tmpl, fn: generateNgContainerOps },
- { kind: CompilationJobKind.Tmpl, fn: collapseEmptyInstructions },
- { kind: CompilationJobKind.Tmpl, fn: attachSourceLocations },
- { kind: CompilationJobKind.Tmpl, fn: disableBindings$1 },
- { kind: CompilationJobKind.Both, fn: extractPureFunctions },
- { kind: CompilationJobKind.Both, fn: reify },
- { kind: CompilationJobKind.Both, fn: chain },
- ];
- /**
- * Run all transformation phases in the correct order against a compilation job. After this
- * processing, the compilation should be in a state where it can be emitted.
- */
- function transform(job, kind) {
- for (const phase of phases) {
- if (phase.kind === kind || phase.kind === CompilationJobKind.Both) {
- // The type of `Phase` above ensures it is impossible to call a phase that doesn't support the
- // job kind.
- phase.fn(job);
- }
- }
- }
- /**
- * Compile all views in the given `ComponentCompilation` into the final template function, which may
- * reference constants defined in a `ConstantPool`.
- */
- function emitTemplateFn(tpl, pool) {
- const rootFn = emitView(tpl.root);
- emitChildViews(tpl.root, pool);
- return rootFn;
- }
- function emitChildViews(parent, pool) {
- for (const unit of parent.job.units) {
- if (unit.parent !== parent.xref) {
- continue;
- }
- // Child views are emitted depth-first.
- emitChildViews(unit, pool);
- const viewFn = emitView(unit);
- pool.statements.push(viewFn.toDeclStmt(viewFn.name));
- }
- }
- /**
- * Emit a template function for an individual `ViewCompilation` (which may be either the root view
- * or an embedded view).
- */
- function emitView(view) {
- if (view.fnName === null) {
- throw new Error(`AssertionError: view ${view.xref} is unnamed`);
- }
- const createStatements = [];
- for (const op of view.create) {
- if (op.kind !== OpKind.Statement) {
- throw new Error(`AssertionError: expected all create ops to have been compiled, but got ${OpKind[op.kind]}`);
- }
- createStatements.push(op.statement);
- }
- const updateStatements = [];
- for (const op of view.update) {
- if (op.kind !== OpKind.Statement) {
- throw new Error(`AssertionError: expected all update ops to have been compiled, but got ${OpKind[op.kind]}`);
- }
- updateStatements.push(op.statement);
- }
- const createCond = maybeGenerateRfBlock(1, createStatements);
- const updateCond = maybeGenerateRfBlock(2, updateStatements);
- return fn([new FnParam('rf'), new FnParam('ctx')], [...createCond, ...updateCond],
- /* type */ undefined,
- /* sourceSpan */ undefined, view.fnName);
- }
- function maybeGenerateRfBlock(flag, statements) {
- if (statements.length === 0) {
- return [];
- }
- return [
- ifStmt(new BinaryOperatorExpr(BinaryOperator.BitwiseAnd, variable('rf'), literal$1(flag)), statements),
- ];
- }
- function emitHostBindingFunction(job) {
- if (job.root.fnName === null) {
- throw new Error(`AssertionError: host binding function is unnamed`);
- }
- const createStatements = [];
- for (const op of job.root.create) {
- if (op.kind !== OpKind.Statement) {
- throw new Error(`AssertionError: expected all create ops to have been compiled, but got ${OpKind[op.kind]}`);
- }
- createStatements.push(op.statement);
- }
- const updateStatements = [];
- for (const op of job.root.update) {
- if (op.kind !== OpKind.Statement) {
- throw new Error(`AssertionError: expected all update ops to have been compiled, but got ${OpKind[op.kind]}`);
- }
- updateStatements.push(op.statement);
- }
- if (createStatements.length === 0 && updateStatements.length === 0) {
- return null;
- }
- const createCond = maybeGenerateRfBlock(1, createStatements);
- const updateCond = maybeGenerateRfBlock(2, updateStatements);
- return fn([new FnParam('rf'), new FnParam('ctx')], [...createCond, ...updateCond],
- /* type */ undefined,
- /* sourceSpan */ undefined, job.root.fnName);
- }
- const compatibilityMode = CompatibilityMode.TemplateDefinitionBuilder;
- // Schema containing DOM elements and their properties.
- const domSchema = new DomElementSchemaRegistry();
- // Tag name of the `ng-template` element.
- const NG_TEMPLATE_TAG_NAME = 'ng-template';
- function isI18nRootNode(meta) {
- return meta instanceof Message;
- }
- function isSingleI18nIcu(meta) {
- return isI18nRootNode(meta) && meta.nodes.length === 1 && meta.nodes[0] instanceof Icu;
- }
- /**
- * Process a template AST and convert it into a `ComponentCompilation` in the intermediate
- * representation.
- * TODO: Refactor more of the ingestion code into phases.
- */
- function ingestComponent(componentName, template, constantPool, relativeContextFilePath, i18nUseExternalIds, deferMeta, allDeferrableDepsFn, relativeTemplatePath, enableDebugLocations) {
- const job = new ComponentCompilationJob(componentName, constantPool, compatibilityMode, relativeContextFilePath, i18nUseExternalIds, deferMeta, allDeferrableDepsFn, relativeTemplatePath, enableDebugLocations);
- ingestNodes(job.root, template);
- return job;
- }
- /**
- * Process a host binding AST and convert it into a `HostBindingCompilationJob` in the intermediate
- * representation.
- */
- function ingestHostBinding(input, bindingParser, constantPool) {
- const job = new HostBindingCompilationJob(input.componentName, constantPool, compatibilityMode);
- for (const property of input.properties ?? []) {
- let bindingKind = BindingKind.Property;
- // TODO: this should really be handled in the parser.
- if (property.name.startsWith('attr.')) {
- property.name = property.name.substring('attr.'.length);
- bindingKind = BindingKind.Attribute;
- }
- if (property.isAnimation) {
- bindingKind = BindingKind.Animation;
- }
- const securityContexts = bindingParser
- .calcPossibleSecurityContexts(input.componentSelector, property.name, bindingKind === BindingKind.Attribute)
- .filter((context) => context !== SecurityContext.NONE);
- ingestHostProperty(job, property, bindingKind, securityContexts);
- }
- for (const [name, expr] of Object.entries(input.attributes) ?? []) {
- const securityContexts = bindingParser
- .calcPossibleSecurityContexts(input.componentSelector, name, true)
- .filter((context) => context !== SecurityContext.NONE);
- ingestHostAttribute(job, name, expr, securityContexts);
- }
- for (const event of input.events ?? []) {
- ingestHostEvent(job, event);
- }
- return job;
- }
- // TODO: We should refactor the parser to use the same types and structures for host bindings as
- // with ordinary components. This would allow us to share a lot more ingestion code.
- function ingestHostProperty(job, property, bindingKind, securityContexts) {
- let expression;
- const ast = property.expression.ast;
- if (ast instanceof Interpolation$1) {
- expression = new Interpolation(ast.strings, ast.expressions.map((expr) => convertAst(expr, job, property.sourceSpan)), []);
- }
- else {
- expression = convertAst(ast, job, property.sourceSpan);
- }
- job.root.update.push(createBindingOp(job.root.xref, bindingKind, property.name, expression, null, securityContexts, false, false, null,
- /* TODO: How do Host bindings handle i18n attrs? */ null, property.sourceSpan));
- }
- function ingestHostAttribute(job, name, value, securityContexts) {
- const attrBinding = createBindingOp(job.root.xref, BindingKind.Attribute, name, value, null, securityContexts,
- /* Host attributes should always be extracted to const hostAttrs, even if they are not
- *strictly* text literals */
- true, false, null,
- /* TODO */ null,
- /** TODO: May be null? */ value.sourceSpan);
- job.root.update.push(attrBinding);
- }
- function ingestHostEvent(job, event) {
- const [phase, target] = event.type !== exports.ParsedEventType.Animation
- ? [null, event.targetOrPhase]
- : [event.targetOrPhase, null];
- const eventBinding = createListenerOp(job.root.xref, new SlotHandle(), event.name, null, makeListenerHandlerOps(job.root, event.handler, event.handlerSpan), phase, target, true, event.sourceSpan);
- job.root.create.push(eventBinding);
- }
- /**
- * Ingest the nodes of a template AST into the given `ViewCompilation`.
- */
- function ingestNodes(unit, template) {
- for (const node of template) {
- if (node instanceof Element$1) {
- ingestElement(unit, node);
- }
- else if (node instanceof Template) {
- ingestTemplate(unit, node);
- }
- else if (node instanceof Content) {
- ingestContent(unit, node);
- }
- else if (node instanceof Text$3) {
- ingestText(unit, node, null);
- }
- else if (node instanceof BoundText) {
- ingestBoundText(unit, node, null);
- }
- else if (node instanceof IfBlock) {
- ingestIfBlock(unit, node);
- }
- else if (node instanceof SwitchBlock) {
- ingestSwitchBlock(unit, node);
- }
- else if (node instanceof DeferredBlock) {
- ingestDeferBlock(unit, node);
- }
- else if (node instanceof Icu$1) {
- ingestIcu(unit, node);
- }
- else if (node instanceof ForLoopBlock) {
- ingestForBlock(unit, node);
- }
- else if (node instanceof LetDeclaration$1) {
- ingestLetDeclaration(unit, node);
- }
- else {
- throw new Error(`Unsupported template node: ${node.constructor.name}`);
- }
- }
- }
- /**
- * Ingest an element AST from the template into the given `ViewCompilation`.
- */
- function ingestElement(unit, element) {
- if (element.i18n !== undefined &&
- !(element.i18n instanceof Message || element.i18n instanceof TagPlaceholder)) {
- throw Error(`Unhandled i18n metadata type for element: ${element.i18n.constructor.name}`);
- }
- const id = unit.job.allocateXrefId();
- const [namespaceKey, elementName] = splitNsName(element.name);
- const startOp = createElementStartOp(elementName, id, namespaceForKey(namespaceKey), element.i18n instanceof TagPlaceholder ? element.i18n : undefined, element.startSourceSpan, element.sourceSpan);
- unit.create.push(startOp);
- ingestElementBindings(unit, startOp, element);
- ingestReferences(startOp, element);
- // Start i18n, if needed, goes after the element create and bindings, but before the nodes
- let i18nBlockId = null;
- if (element.i18n instanceof Message) {
- i18nBlockId = unit.job.allocateXrefId();
- unit.create.push(createI18nStartOp(i18nBlockId, element.i18n, undefined, element.startSourceSpan));
- }
- ingestNodes(unit, element.children);
- // The source span for the end op is typically the element closing tag. However, if no closing tag
- // exists, such as in `<input>`, we use the start source span instead. Usually the start and end
- // instructions will be collapsed into one `element` instruction, negating the purpose of this
- // fallback, but in cases when it is not collapsed (such as an input with a binding), we still
- // want to map the end instruction to the main element.
- const endOp = createElementEndOp(id, element.endSourceSpan ?? element.startSourceSpan);
- unit.create.push(endOp);
- // If there is an i18n message associated with this element, insert i18n start and end ops.
- if (i18nBlockId !== null) {
- OpList.insertBefore(createI18nEndOp(i18nBlockId, element.endSourceSpan ?? element.startSourceSpan), endOp);
- }
- }
- /**
- * Ingest an `ng-template` node from the AST into the given `ViewCompilation`.
- */
- function ingestTemplate(unit, tmpl) {
- if (tmpl.i18n !== undefined &&
- !(tmpl.i18n instanceof Message || tmpl.i18n instanceof TagPlaceholder)) {
- throw Error(`Unhandled i18n metadata type for template: ${tmpl.i18n.constructor.name}`);
- }
- const childView = unit.job.allocateView(unit.xref);
- let tagNameWithoutNamespace = tmpl.tagName;
- let namespacePrefix = '';
- if (tmpl.tagName) {
- [namespacePrefix, tagNameWithoutNamespace] = splitNsName(tmpl.tagName);
- }
- const i18nPlaceholder = tmpl.i18n instanceof TagPlaceholder ? tmpl.i18n : undefined;
- const namespace = namespaceForKey(namespacePrefix);
- const functionNameSuffix = tagNameWithoutNamespace === null ? '' : prefixWithNamespace(tagNameWithoutNamespace, namespace);
- const templateKind = isPlainTemplate(tmpl)
- ? TemplateKind.NgTemplate
- : TemplateKind.Structural;
- const templateOp = createTemplateOp(childView.xref, templateKind, tagNameWithoutNamespace, functionNameSuffix, namespace, i18nPlaceholder, tmpl.startSourceSpan, tmpl.sourceSpan);
- unit.create.push(templateOp);
- ingestTemplateBindings(unit, templateOp, tmpl, templateKind);
- ingestReferences(templateOp, tmpl);
- ingestNodes(childView, tmpl.children);
- for (const { name, value } of tmpl.variables) {
- childView.contextVariables.set(name, value !== '' ? value : '$implicit');
- }
- // If this is a plain template and there is an i18n message associated with it, insert i18n start
- // and end ops. For structural directive templates, the i18n ops will be added when ingesting the
- // element/template the directive is placed on.
- if (templateKind === TemplateKind.NgTemplate && tmpl.i18n instanceof Message) {
- const id = unit.job.allocateXrefId();
- OpList.insertAfter(createI18nStartOp(id, tmpl.i18n, undefined, tmpl.startSourceSpan), childView.create.head);
- OpList.insertBefore(createI18nEndOp(id, tmpl.endSourceSpan ?? tmpl.startSourceSpan), childView.create.tail);
- }
- }
- /**
- * Ingest a content node from the AST into the given `ViewCompilation`.
- */
- function ingestContent(unit, content) {
- if (content.i18n !== undefined && !(content.i18n instanceof TagPlaceholder)) {
- throw Error(`Unhandled i18n metadata type for element: ${content.i18n.constructor.name}`);
- }
- let fallbackView = null;
- // Don't capture default content that's only made up of empty text nodes and comments.
- // Note that we process the default content before the projection in order to match the
- // insertion order at runtime.
- if (content.children.some((child) => !(child instanceof Comment$1) &&
- (!(child instanceof Text$3) || child.value.trim().length > 0))) {
- fallbackView = unit.job.allocateView(unit.xref);
- ingestNodes(fallbackView, content.children);
- }
- const id = unit.job.allocateXrefId();
- const op = createProjectionOp(id, content.selector, content.i18n, fallbackView?.xref ?? null, content.sourceSpan);
- for (const attr of content.attributes) {
- const securityContext = domSchema.securityContext(content.name, attr.name, true);
- unit.update.push(createBindingOp(op.xref, BindingKind.Attribute, attr.name, literal$1(attr.value), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
- }
- unit.create.push(op);
- }
- /**
- * Ingest a literal text node from the AST into the given `ViewCompilation`.
- */
- function ingestText(unit, text, icuPlaceholder) {
- unit.create.push(createTextOp(unit.job.allocateXrefId(), text.value, icuPlaceholder, text.sourceSpan));
- }
- /**
- * Ingest an interpolated text node from the AST into the given `ViewCompilation`.
- */
- function ingestBoundText(unit, text, icuPlaceholder) {
- let value = text.value;
- if (value instanceof ASTWithSource) {
- value = value.ast;
- }
- if (!(value instanceof Interpolation$1)) {
- throw new Error(`AssertionError: expected Interpolation for BoundText node, got ${value.constructor.name}`);
- }
- if (text.i18n !== undefined && !(text.i18n instanceof Container)) {
- throw Error(`Unhandled i18n metadata type for text interpolation: ${text.i18n?.constructor.name}`);
- }
- const i18nPlaceholders = text.i18n instanceof Container
- ? text.i18n.children
- .filter((node) => node instanceof Placeholder)
- .map((placeholder) => placeholder.name)
- : [];
- if (i18nPlaceholders.length > 0 && i18nPlaceholders.length !== value.expressions.length) {
- throw Error(`Unexpected number of i18n placeholders (${value.expressions.length}) for BoundText with ${value.expressions.length} expressions`);
- }
- const textXref = unit.job.allocateXrefId();
- unit.create.push(createTextOp(textXref, '', icuPlaceholder, text.sourceSpan));
- // TemplateDefinitionBuilder does not generate source maps for sub-expressions inside an
- // interpolation. We copy that behavior in compatibility mode.
- // TODO: is it actually correct to generate these extra maps in modern mode?
- const baseSourceSpan = unit.job.compatibility ? null : text.sourceSpan;
- unit.update.push(createInterpolateTextOp(textXref, new Interpolation(value.strings, value.expressions.map((expr) => convertAst(expr, unit.job, baseSourceSpan)), i18nPlaceholders), text.sourceSpan));
- }
- /**
- * Ingest an `@if` block into the given `ViewCompilation`.
- */
- function ingestIfBlock(unit, ifBlock) {
- let firstXref = null;
- let conditions = [];
- for (let i = 0; i < ifBlock.branches.length; i++) {
- const ifCase = ifBlock.branches[i];
- const cView = unit.job.allocateView(unit.xref);
- const tagName = ingestControlFlowInsertionPoint(unit, cView.xref, ifCase);
- if (ifCase.expressionAlias !== null) {
- cView.contextVariables.set(ifCase.expressionAlias.name, CTX_REF);
- }
- let ifCaseI18nMeta = undefined;
- if (ifCase.i18n !== undefined) {
- if (!(ifCase.i18n instanceof BlockPlaceholder)) {
- throw Error(`Unhandled i18n metadata type for if block: ${ifCase.i18n?.constructor.name}`);
- }
- ifCaseI18nMeta = ifCase.i18n;
- }
- const templateOp = createTemplateOp(cView.xref, TemplateKind.Block, tagName, 'Conditional', Namespace.HTML, ifCaseI18nMeta, ifCase.startSourceSpan, ifCase.sourceSpan);
- unit.create.push(templateOp);
- if (firstXref === null) {
- firstXref = cView.xref;
- }
- const caseExpr = ifCase.expression ? convertAst(ifCase.expression, unit.job, null) : null;
- const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, templateOp.xref, templateOp.handle, ifCase.expressionAlias);
- conditions.push(conditionalCaseExpr);
- ingestNodes(cView, ifCase.children);
- }
- unit.update.push(createConditionalOp(firstXref, null, conditions, ifBlock.sourceSpan));
- }
- /**
- * Ingest an `@switch` block into the given `ViewCompilation`.
- */
- function ingestSwitchBlock(unit, switchBlock) {
- // Don't ingest empty switches since they won't render anything.
- if (switchBlock.cases.length === 0) {
- return;
- }
- let firstXref = null;
- let conditions = [];
- for (const switchCase of switchBlock.cases) {
- const cView = unit.job.allocateView(unit.xref);
- const tagName = ingestControlFlowInsertionPoint(unit, cView.xref, switchCase);
- let switchCaseI18nMeta = undefined;
- if (switchCase.i18n !== undefined) {
- if (!(switchCase.i18n instanceof BlockPlaceholder)) {
- throw Error(`Unhandled i18n metadata type for switch block: ${switchCase.i18n?.constructor.name}`);
- }
- switchCaseI18nMeta = switchCase.i18n;
- }
- const templateOp = createTemplateOp(cView.xref, TemplateKind.Block, tagName, 'Case', Namespace.HTML, switchCaseI18nMeta, switchCase.startSourceSpan, switchCase.sourceSpan);
- unit.create.push(templateOp);
- if (firstXref === null) {
- firstXref = cView.xref;
- }
- const caseExpr = switchCase.expression
- ? convertAst(switchCase.expression, unit.job, switchBlock.startSourceSpan)
- : null;
- const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, templateOp.xref, templateOp.handle);
- conditions.push(conditionalCaseExpr);
- ingestNodes(cView, switchCase.children);
- }
- unit.update.push(createConditionalOp(firstXref, convertAst(switchBlock.expression, unit.job, null), conditions, switchBlock.sourceSpan));
- }
- function ingestDeferView(unit, suffix, i18nMeta, children, sourceSpan) {
- if (i18nMeta !== undefined && !(i18nMeta instanceof BlockPlaceholder)) {
- throw Error('Unhandled i18n metadata type for defer block');
- }
- if (children === undefined) {
- return null;
- }
- const secondaryView = unit.job.allocateView(unit.xref);
- ingestNodes(secondaryView, children);
- const templateOp = createTemplateOp(secondaryView.xref, TemplateKind.Block, null, `Defer${suffix}`, Namespace.HTML, i18nMeta, sourceSpan, sourceSpan);
- unit.create.push(templateOp);
- return templateOp;
- }
- function ingestDeferBlock(unit, deferBlock) {
- let ownResolverFn = null;
- if (unit.job.deferMeta.mode === 0 /* DeferBlockDepsEmitMode.PerBlock */) {
- if (!unit.job.deferMeta.blocks.has(deferBlock)) {
- throw new Error(`AssertionError: unable to find a dependency function for this deferred block`);
- }
- ownResolverFn = unit.job.deferMeta.blocks.get(deferBlock) ?? null;
- }
- // Generate the defer main view and all secondary views.
- const main = ingestDeferView(unit, '', deferBlock.i18n, deferBlock.children, deferBlock.sourceSpan);
- const loading = ingestDeferView(unit, 'Loading', deferBlock.loading?.i18n, deferBlock.loading?.children, deferBlock.loading?.sourceSpan);
- const placeholder = ingestDeferView(unit, 'Placeholder', deferBlock.placeholder?.i18n, deferBlock.placeholder?.children, deferBlock.placeholder?.sourceSpan);
- const error = ingestDeferView(unit, 'Error', deferBlock.error?.i18n, deferBlock.error?.children, deferBlock.error?.sourceSpan);
- // Create the main defer op, and ops for all secondary views.
- const deferXref = unit.job.allocateXrefId();
- const deferOp = createDeferOp(deferXref, main.xref, main.handle, ownResolverFn, unit.job.allDeferrableDepsFn, deferBlock.sourceSpan);
- deferOp.placeholderView = placeholder?.xref ?? null;
- deferOp.placeholderSlot = placeholder?.handle ?? null;
- deferOp.loadingSlot = loading?.handle ?? null;
- deferOp.errorSlot = error?.handle ?? null;
- deferOp.placeholderMinimumTime = deferBlock.placeholder?.minimumTime ?? null;
- deferOp.loadingMinimumTime = deferBlock.loading?.minimumTime ?? null;
- deferOp.loadingAfterTime = deferBlock.loading?.afterTime ?? null;
- deferOp.flags = calcDeferBlockFlags(deferBlock);
- unit.create.push(deferOp);
- // Configure all defer `on` conditions.
- // TODO: refactor prefetch triggers to use a separate op type, with a shared superclass. This will
- // make it easier to refactor prefetch behavior in the future.
- const deferOnOps = [];
- const deferWhenOps = [];
- // Ingest the hydrate triggers first since they set up all the other triggers during SSR.
- ingestDeferTriggers("hydrate" /* ir.DeferOpModifierKind.HYDRATE */, deferBlock.hydrateTriggers, deferOnOps, deferWhenOps, unit, deferXref);
- ingestDeferTriggers("none" /* ir.DeferOpModifierKind.NONE */, deferBlock.triggers, deferOnOps, deferWhenOps, unit, deferXref);
- ingestDeferTriggers("prefetch" /* ir.DeferOpModifierKind.PREFETCH */, deferBlock.prefetchTriggers, deferOnOps, deferWhenOps, unit, deferXref);
- // If no (non-prefetching or hydrating) defer triggers were provided, default to `idle`.
- const hasConcreteTrigger = deferOnOps.some((op) => op.modifier === "none" /* ir.DeferOpModifierKind.NONE */) ||
- deferWhenOps.some((op) => op.modifier === "none" /* ir.DeferOpModifierKind.NONE */);
- if (!hasConcreteTrigger) {
- deferOnOps.push(createDeferOnOp(deferXref, { kind: DeferTriggerKind.Idle }, "none" /* ir.DeferOpModifierKind.NONE */, null));
- }
- unit.create.push(deferOnOps);
- unit.update.push(deferWhenOps);
- }
- function calcDeferBlockFlags(deferBlockDetails) {
- if (Object.keys(deferBlockDetails.hydrateTriggers).length > 0) {
- return 1 /* ir.TDeferDetailsFlags.HasHydrateTriggers */;
- }
- return null;
- }
- function ingestDeferTriggers(modifier, triggers, onOps, whenOps, unit, deferXref) {
- if (triggers.idle !== undefined) {
- const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Idle }, modifier, triggers.idle.sourceSpan);
- onOps.push(deferOnOp);
- }
- if (triggers.immediate !== undefined) {
- const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Immediate }, modifier, triggers.immediate.sourceSpan);
- onOps.push(deferOnOp);
- }
- if (triggers.timer !== undefined) {
- const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Timer, delay: triggers.timer.delay }, modifier, triggers.timer.sourceSpan);
- onOps.push(deferOnOp);
- }
- if (triggers.hover !== undefined) {
- const deferOnOp = createDeferOnOp(deferXref, {
- kind: DeferTriggerKind.Hover,
- targetName: triggers.hover.reference,
- targetXref: null,
- targetSlot: null,
- targetView: null,
- targetSlotViewSteps: null,
- }, modifier, triggers.hover.sourceSpan);
- onOps.push(deferOnOp);
- }
- if (triggers.interaction !== undefined) {
- const deferOnOp = createDeferOnOp(deferXref, {
- kind: DeferTriggerKind.Interaction,
- targetName: triggers.interaction.reference,
- targetXref: null,
- targetSlot: null,
- targetView: null,
- targetSlotViewSteps: null,
- }, modifier, triggers.interaction.sourceSpan);
- onOps.push(deferOnOp);
- }
- if (triggers.viewport !== undefined) {
- const deferOnOp = createDeferOnOp(deferXref, {
- kind: DeferTriggerKind.Viewport,
- targetName: triggers.viewport.reference,
- targetXref: null,
- targetSlot: null,
- targetView: null,
- targetSlotViewSteps: null,
- }, modifier, triggers.viewport.sourceSpan);
- onOps.push(deferOnOp);
- }
- if (triggers.never !== undefined) {
- const deferOnOp = createDeferOnOp(deferXref, { kind: DeferTriggerKind.Never }, modifier, triggers.never.sourceSpan);
- onOps.push(deferOnOp);
- }
- if (triggers.when !== undefined) {
- if (triggers.when.value instanceof Interpolation$1) {
- // TemplateDefinitionBuilder supports this case, but it's very strange to me. What would it
- // even mean?
- throw new Error(`Unexpected interpolation in defer block when trigger`);
- }
- const deferOnOp = createDeferWhenOp(deferXref, convertAst(triggers.when.value, unit.job, triggers.when.sourceSpan), modifier, triggers.when.sourceSpan);
- whenOps.push(deferOnOp);
- }
- }
- function ingestIcu(unit, icu) {
- if (icu.i18n instanceof Message && isSingleI18nIcu(icu.i18n)) {
- const xref = unit.job.allocateXrefId();
- unit.create.push(createIcuStartOp(xref, icu.i18n, icuFromI18nMessage(icu.i18n).name, null));
- for (const [placeholder, text] of Object.entries({ ...icu.vars, ...icu.placeholders })) {
- if (text instanceof BoundText) {
- ingestBoundText(unit, text, placeholder);
- }
- else {
- ingestText(unit, text, placeholder);
- }
- }
- unit.create.push(createIcuEndOp(xref));
- }
- else {
- throw Error(`Unhandled i18n metadata type for ICU: ${icu.i18n?.constructor.name}`);
- }
- }
- /**
- * Ingest an `@for` block into the given `ViewCompilation`.
- */
- function ingestForBlock(unit, forBlock) {
- const repeaterView = unit.job.allocateView(unit.xref);
- // We copy TemplateDefinitionBuilder's scheme of creating names for `$count` and `$index`
- // that are suffixed with special information, to disambiguate which level of nested loop
- // the below aliases refer to.
- // TODO: We should refactor Template Pipeline's variable phases to gracefully handle
- // shadowing, and arbitrarily many levels of variables depending on each other.
- const indexName = `ɵ$index_${repeaterView.xref}`;
- const countName = `ɵ$count_${repeaterView.xref}`;
- const indexVarNames = new Set();
- // Set all the context variables and aliases available in the repeater.
- repeaterView.contextVariables.set(forBlock.item.name, forBlock.item.value);
- for (const variable of forBlock.contextVariables) {
- if (variable.value === '$index') {
- indexVarNames.add(variable.name);
- }
- if (variable.name === '$index') {
- repeaterView.contextVariables.set('$index', variable.value).set(indexName, variable.value);
- }
- else if (variable.name === '$count') {
- repeaterView.contextVariables.set('$count', variable.value).set(countName, variable.value);
- }
- else {
- repeaterView.aliases.add({
- kind: SemanticVariableKind.Alias,
- name: null,
- identifier: variable.name,
- expression: getComputedForLoopVariableExpression(variable, indexName, countName),
- });
- }
- }
- const sourceSpan = convertSourceSpan(forBlock.trackBy.span, forBlock.sourceSpan);
- const track = convertAst(forBlock.trackBy, unit.job, sourceSpan);
- ingestNodes(repeaterView, forBlock.children);
- let emptyView = null;
- let emptyTagName = null;
- if (forBlock.empty !== null) {
- emptyView = unit.job.allocateView(unit.xref);
- ingestNodes(emptyView, forBlock.empty.children);
- emptyTagName = ingestControlFlowInsertionPoint(unit, emptyView.xref, forBlock.empty);
- }
- const varNames = {
- $index: indexVarNames,
- $implicit: forBlock.item.name,
- };
- if (forBlock.i18n !== undefined && !(forBlock.i18n instanceof BlockPlaceholder)) {
- throw Error('AssertionError: Unhandled i18n metadata type or @for');
- }
- if (forBlock.empty?.i18n !== undefined &&
- !(forBlock.empty.i18n instanceof BlockPlaceholder)) {
- throw Error('AssertionError: Unhandled i18n metadata type or @empty');
- }
- const i18nPlaceholder = forBlock.i18n;
- const emptyI18nPlaceholder = forBlock.empty?.i18n;
- const tagName = ingestControlFlowInsertionPoint(unit, repeaterView.xref, forBlock);
- const repeaterCreate = createRepeaterCreateOp(repeaterView.xref, emptyView?.xref ?? null, tagName, track, varNames, emptyTagName, i18nPlaceholder, emptyI18nPlaceholder, forBlock.startSourceSpan, forBlock.sourceSpan);
- unit.create.push(repeaterCreate);
- const expression = convertAst(forBlock.expression, unit.job, convertSourceSpan(forBlock.expression.span, forBlock.sourceSpan));
- const repeater = createRepeaterOp(repeaterCreate.xref, repeaterCreate.handle, expression, forBlock.sourceSpan);
- unit.update.push(repeater);
- }
- /**
- * Gets an expression that represents a variable in an `@for` loop.
- * @param variable AST representing the variable.
- * @param indexName Loop-specific name for `$index`.
- * @param countName Loop-specific name for `$count`.
- */
- function getComputedForLoopVariableExpression(variable, indexName, countName) {
- switch (variable.value) {
- case '$index':
- return new LexicalReadExpr(indexName);
- case '$count':
- return new LexicalReadExpr(countName);
- case '$first':
- return new LexicalReadExpr(indexName).identical(literal$1(0));
- case '$last':
- return new LexicalReadExpr(indexName).identical(new LexicalReadExpr(countName).minus(literal$1(1)));
- case '$even':
- return new LexicalReadExpr(indexName).modulo(literal$1(2)).identical(literal$1(0));
- case '$odd':
- return new LexicalReadExpr(indexName).modulo(literal$1(2)).notIdentical(literal$1(0));
- default:
- throw new Error(`AssertionError: unknown @for loop variable ${variable.value}`);
- }
- }
- function ingestLetDeclaration(unit, node) {
- const target = unit.job.allocateXrefId();
- unit.create.push(createDeclareLetOp(target, node.name, node.sourceSpan));
- unit.update.push(createStoreLetOp(target, node.name, convertAst(node.value, unit.job, node.valueSpan), node.sourceSpan));
- }
- /**
- * Convert a template AST expression into an output AST expression.
- */
- function convertAst(ast, job, baseSourceSpan) {
- if (ast instanceof ASTWithSource) {
- return convertAst(ast.ast, job, baseSourceSpan);
- }
- else if (ast instanceof PropertyRead) {
- // Whether this is an implicit receiver, *excluding* explicit reads of `this`.
- const isImplicitReceiver = ast.receiver instanceof ImplicitReceiver && !(ast.receiver instanceof ThisReceiver);
- if (isImplicitReceiver) {
- return new LexicalReadExpr(ast.name);
- }
- else {
- return new ReadPropExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.name, null, convertSourceSpan(ast.span, baseSourceSpan));
- }
- }
- else if (ast instanceof PropertyWrite) {
- if (ast.receiver instanceof ImplicitReceiver) {
- return new WritePropExpr(
- // TODO: Is it correct to always use the root context in place of the implicit receiver?
- new ContextExpr(job.root.xref), ast.name, convertAst(ast.value, job, baseSourceSpan), null, convertSourceSpan(ast.span, baseSourceSpan));
- }
- return new WritePropExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.name, convertAst(ast.value, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
- }
- else if (ast instanceof KeyedWrite) {
- return new WriteKeyExpr(convertAst(ast.receiver, job, baseSourceSpan), convertAst(ast.key, job, baseSourceSpan), convertAst(ast.value, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
- }
- else if (ast instanceof Call) {
- if (ast.receiver instanceof ImplicitReceiver) {
- throw new Error(`Unexpected ImplicitReceiver`);
- }
- else {
- return new InvokeFunctionExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.args.map((arg) => convertAst(arg, job, baseSourceSpan)), undefined, convertSourceSpan(ast.span, baseSourceSpan));
- }
- }
- else if (ast instanceof LiteralPrimitive) {
- return literal$1(ast.value, undefined, convertSourceSpan(ast.span, baseSourceSpan));
- }
- else if (ast instanceof Unary) {
- switch (ast.operator) {
- case '+':
- return new UnaryOperatorExpr(UnaryOperator.Plus, convertAst(ast.expr, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
- case '-':
- return new UnaryOperatorExpr(UnaryOperator.Minus, convertAst(ast.expr, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
- default:
- throw new Error(`AssertionError: unknown unary operator ${ast.operator}`);
- }
- }
- else if (ast instanceof Binary) {
- const operator = BINARY_OPERATORS$3.get(ast.operation);
- if (operator === undefined) {
- throw new Error(`AssertionError: unknown binary operator ${ast.operation}`);
- }
- return new BinaryOperatorExpr(operator, convertAst(ast.left, job, baseSourceSpan), convertAst(ast.right, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
- }
- else if (ast instanceof ThisReceiver) {
- // TODO: should context expressions have source maps?
- return new ContextExpr(job.root.xref);
- }
- else if (ast instanceof KeyedRead) {
- return new ReadKeyExpr(convertAst(ast.receiver, job, baseSourceSpan), convertAst(ast.key, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
- }
- else if (ast instanceof Chain) {
- throw new Error(`AssertionError: Chain in unknown context`);
- }
- else if (ast instanceof LiteralMap) {
- const entries = ast.keys.map((key, idx) => {
- const value = ast.values[idx];
- // TODO: should literals have source maps, or do we just map the whole surrounding
- // expression?
- return new LiteralMapEntry(key.key, convertAst(value, job, baseSourceSpan), key.quoted);
- });
- return new LiteralMapExpr(entries, undefined, convertSourceSpan(ast.span, baseSourceSpan));
- }
- else if (ast instanceof LiteralArray) {
- // TODO: should literals have source maps, or do we just map the whole surrounding expression?
- return new LiteralArrayExpr(ast.expressions.map((expr) => convertAst(expr, job, baseSourceSpan)));
- }
- else if (ast instanceof Conditional) {
- return new ConditionalExpr(convertAst(ast.condition, job, baseSourceSpan), convertAst(ast.trueExp, job, baseSourceSpan), convertAst(ast.falseExp, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
- }
- else if (ast instanceof NonNullAssert) {
- // A non-null assertion shouldn't impact generated instructions, so we can just drop it.
- return convertAst(ast.expression, job, baseSourceSpan);
- }
- else if (ast instanceof BindingPipe) {
- // TODO: pipes should probably have source maps; figure out details.
- return new PipeBindingExpr(job.allocateXrefId(), new SlotHandle(), ast.name, [
- convertAst(ast.exp, job, baseSourceSpan),
- ...ast.args.map((arg) => convertAst(arg, job, baseSourceSpan)),
- ]);
- }
- else if (ast instanceof SafeKeyedRead) {
- return new SafeKeyedReadExpr(convertAst(ast.receiver, job, baseSourceSpan), convertAst(ast.key, job, baseSourceSpan), convertSourceSpan(ast.span, baseSourceSpan));
- }
- else if (ast instanceof SafePropertyRead) {
- // TODO: source span
- return new SafePropertyReadExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.name);
- }
- else if (ast instanceof SafeCall) {
- // TODO: source span
- return new SafeInvokeFunctionExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.args.map((a) => convertAst(a, job, baseSourceSpan)));
- }
- else if (ast instanceof EmptyExpr$1) {
- return new EmptyExpr(convertSourceSpan(ast.span, baseSourceSpan));
- }
- else if (ast instanceof PrefixNot) {
- return not(convertAst(ast.expression, job, baseSourceSpan), convertSourceSpan(ast.span, baseSourceSpan));
- }
- else if (ast instanceof TypeofExpression) {
- return typeofExpr(convertAst(ast.expression, job, baseSourceSpan));
- }
- else if (ast instanceof TemplateLiteral) {
- return new TemplateLiteralExpr(ast.elements.map((el) => {
- return new TemplateLiteralElementExpr(el.text, convertSourceSpan(el.span, baseSourceSpan));
- }), ast.expressions.map((expr) => convertAst(expr, job, baseSourceSpan)), convertSourceSpan(ast.span, baseSourceSpan));
- }
- else {
- throw new Error(`Unhandled expression type "${ast.constructor.name}" in file "${baseSourceSpan?.start.file.url}"`);
- }
- }
- function convertAstWithInterpolation(job, value, i18nMeta, sourceSpan) {
- let expression;
- if (value instanceof Interpolation$1) {
- expression = new Interpolation(value.strings, value.expressions.map((e) => convertAst(e, job, null)), Object.keys(asMessage(i18nMeta)?.placeholders ?? {}));
- }
- else if (value instanceof AST) {
- expression = convertAst(value, job, null);
- }
- else {
- expression = literal$1(value);
- }
- return expression;
- }
- // TODO: Can we populate Template binding kinds in ingest?
- const BINDING_KINDS = new Map([
- [exports.BindingType.Property, BindingKind.Property],
- [exports.BindingType.TwoWay, BindingKind.TwoWayProperty],
- [exports.BindingType.Attribute, BindingKind.Attribute],
- [exports.BindingType.Class, BindingKind.ClassName],
- [exports.BindingType.Style, BindingKind.StyleProperty],
- [exports.BindingType.Animation, BindingKind.Animation],
- ]);
- /**
- * Checks whether the given template is a plain ng-template (as opposed to another kind of template
- * such as a structural directive template or control flow template). This is checked based on the
- * tagName. We can expect that only plain ng-templates will come through with a tagName of
- * 'ng-template'.
- *
- * Here are some of the cases we expect:
- *
- * | Angular HTML | Template tagName |
- * | ---------------------------------- | ------------------ |
- * | `<ng-template>` | 'ng-template' |
- * | `<div *ngIf="true">` | 'div' |
- * | `<svg><ng-template>` | 'svg:ng-template' |
- * | `@if (true) {` | 'Conditional' |
- * | `<ng-template *ngIf>` (plain) | 'ng-template' |
- * | `<ng-template *ngIf>` (structural) | null |
- */
- function isPlainTemplate(tmpl) {
- return splitNsName(tmpl.tagName ?? '')[1] === NG_TEMPLATE_TAG_NAME;
- }
- /**
- * Ensures that the i18nMeta, if provided, is an i18n.Message.
- */
- function asMessage(i18nMeta) {
- if (i18nMeta == null) {
- return null;
- }
- if (!(i18nMeta instanceof Message)) {
- throw Error(`Expected i18n meta to be a Message, but got: ${i18nMeta.constructor.name}`);
- }
- return i18nMeta;
- }
- /**
- * Process all of the bindings on an element in the template AST and convert them to their IR
- * representation.
- */
- function ingestElementBindings(unit, op, element) {
- let bindings = new Array();
- let i18nAttributeBindingNames = new Set();
- for (const attr of element.attributes) {
- // Attribute literal bindings, such as `attr.foo="bar"`.
- const securityContext = domSchema.securityContext(element.name, attr.name, true);
- bindings.push(createBindingOp(op.xref, BindingKind.Attribute, attr.name, convertAstWithInterpolation(unit.job, attr.value, attr.i18n), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
- if (attr.i18n) {
- i18nAttributeBindingNames.add(attr.name);
- }
- }
- for (const input of element.inputs) {
- if (i18nAttributeBindingNames.has(input.name)) {
- console.error(`On component ${unit.job.componentName}, the binding ${input.name} is both an i18n attribute and a property. You may want to remove the property binding. This will become a compilation error in future versions of Angular.`);
- }
- // All dynamic bindings (both attribute and property bindings).
- bindings.push(createBindingOp(op.xref, BINDING_KINDS.get(input.type), input.name, convertAstWithInterpolation(unit.job, astOf(input.value), input.i18n), input.unit, input.securityContext, false, false, null, asMessage(input.i18n) ?? null, input.sourceSpan));
- }
- unit.create.push(bindings.filter((b) => b?.kind === OpKind.ExtractedAttribute));
- unit.update.push(bindings.filter((b) => b?.kind === OpKind.Binding));
- for (const output of element.outputs) {
- if (output.type === exports.ParsedEventType.Animation && output.phase === null) {
- throw Error('Animation listener should have a phase');
- }
- if (output.type === exports.ParsedEventType.TwoWay) {
- unit.create.push(createTwoWayListenerOp(op.xref, op.handle, output.name, op.tag, makeTwoWayListenerHandlerOps(unit, output.handler, output.handlerSpan), output.sourceSpan));
- }
- else {
- unit.create.push(createListenerOp(op.xref, op.handle, output.name, op.tag, makeListenerHandlerOps(unit, output.handler, output.handlerSpan), output.phase, output.target, false, output.sourceSpan));
- }
- }
- // If any of the bindings on this element have an i18n message, then an i18n attrs configuration
- // op is also required.
- if (bindings.some((b) => b?.i18nMessage) !== null) {
- unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
- }
- }
- /**
- * Process all of the bindings on a template in the template AST and convert them to their IR
- * representation.
- */
- function ingestTemplateBindings(unit, op, template, templateKind) {
- let bindings = new Array();
- for (const attr of template.templateAttrs) {
- if (attr instanceof TextAttribute) {
- const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
- bindings.push(createTemplateBinding(unit, op.xref, exports.BindingType.Attribute, attr.name, attr.value, null, securityContext, true, templateKind, asMessage(attr.i18n), attr.sourceSpan));
- }
- else {
- bindings.push(createTemplateBinding(unit, op.xref, attr.type, attr.name, astOf(attr.value), attr.unit, attr.securityContext, true, templateKind, asMessage(attr.i18n), attr.sourceSpan));
- }
- }
- for (const attr of template.attributes) {
- // Attribute literal bindings, such as `attr.foo="bar"`.
- const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
- bindings.push(createTemplateBinding(unit, op.xref, exports.BindingType.Attribute, attr.name, attr.value, null, securityContext, false, templateKind, asMessage(attr.i18n), attr.sourceSpan));
- }
- for (const input of template.inputs) {
- // Dynamic bindings (both attribute and property bindings).
- bindings.push(createTemplateBinding(unit, op.xref, input.type, input.name, astOf(input.value), input.unit, input.securityContext, false, templateKind, asMessage(input.i18n), input.sourceSpan));
- }
- unit.create.push(bindings.filter((b) => b?.kind === OpKind.ExtractedAttribute));
- unit.update.push(bindings.filter((b) => b?.kind === OpKind.Binding));
- for (const output of template.outputs) {
- if (output.type === exports.ParsedEventType.Animation && output.phase === null) {
- throw Error('Animation listener should have a phase');
- }
- if (templateKind === TemplateKind.NgTemplate) {
- if (output.type === exports.ParsedEventType.TwoWay) {
- unit.create.push(createTwoWayListenerOp(op.xref, op.handle, output.name, op.tag, makeTwoWayListenerHandlerOps(unit, output.handler, output.handlerSpan), output.sourceSpan));
- }
- else {
- unit.create.push(createListenerOp(op.xref, op.handle, output.name, op.tag, makeListenerHandlerOps(unit, output.handler, output.handlerSpan), output.phase, output.target, false, output.sourceSpan));
- }
- }
- if (templateKind === TemplateKind.Structural &&
- output.type !== exports.ParsedEventType.Animation) {
- // Animation bindings are excluded from the structural template's const array.
- const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, output.name, false);
- unit.create.push(createExtractedAttributeOp(op.xref, BindingKind.Property, null, output.name, null, null, null, securityContext));
- }
- }
- // TODO: Perhaps we could do this in a phase? (It likely wouldn't change the slot indices.)
- if (bindings.some((b) => b?.i18nMessage) !== null) {
- unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
- }
- }
- /**
- * Helper to ingest an individual binding on a template, either an explicit `ng-template`, or an
- * implicit template created via structural directive.
- *
- * Bindings on templates are *extremely* tricky. I have tried to isolate all of the confusing edge
- * cases into this function, and to comment it well to document the behavior.
- *
- * Some of this behavior is intuitively incorrect, and we should consider changing it in the future.
- *
- * @param view The compilation unit for the view containing the template.
- * @param xref The xref of the template op.
- * @param type The binding type, according to the parser. This is fairly reasonable, e.g. both
- * dynamic and static attributes have e.BindingType.Attribute.
- * @param name The binding's name.
- * @param value The bindings's value, which will either be an input AST expression, or a string
- * literal. Note that the input AST expression may or may not be const -- it will only be a
- * string literal if the parser considered it a text binding.
- * @param unit If the binding has a unit (e.g. `px` for style bindings), then this is the unit.
- * @param securityContext The security context of the binding.
- * @param isStructuralTemplateAttribute Whether this binding actually applies to the structural
- * ng-template. For example, an `ngFor` would actually apply to the structural template. (Most
- * bindings on structural elements target the inner element, not the template.)
- * @param templateKind Whether this is an explicit `ng-template` or an implicit template created by
- * a structural directive. This should never be a block template.
- * @param i18nMessage The i18n metadata for the binding, if any.
- * @param sourceSpan The source span of the binding.
- * @returns An IR binding op, or null if the binding should be skipped.
- */
- function createTemplateBinding(view, xref, type, name, value, unit, securityContext, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan) {
- const isTextBinding = typeof value === 'string';
- // If this is a structural template, then several kinds of bindings should not result in an
- // update instruction.
- if (templateKind === TemplateKind.Structural) {
- if (!isStructuralTemplateAttribute) {
- switch (type) {
- case exports.BindingType.Property:
- case exports.BindingType.Class:
- case exports.BindingType.Style:
- // Because this binding doesn't really target the ng-template, it must be a binding on an
- // inner node of a structural template. We can't skip it entirely, because we still need
- // it on the ng-template's consts (e.g. for the purposes of directive matching). However,
- // we should not generate an update instruction for it.
- return createExtractedAttributeOp(xref, BindingKind.Property, null, name, null, null, i18nMessage, securityContext);
- case exports.BindingType.TwoWay:
- return createExtractedAttributeOp(xref, BindingKind.TwoWayProperty, null, name, null, null, i18nMessage, securityContext);
- }
- }
- if (!isTextBinding && (type === exports.BindingType.Attribute || type === exports.BindingType.Animation)) {
- // Again, this binding doesn't really target the ng-template; it actually targets the element
- // inside the structural template. In the case of non-text attribute or animation bindings,
- // the binding doesn't even show up on the ng-template const array, so we just skip it
- // entirely.
- return null;
- }
- }
- let bindingType = BINDING_KINDS.get(type);
- if (templateKind === TemplateKind.NgTemplate) {
- // We know we are dealing with bindings directly on an explicit ng-template.
- // Static attribute bindings should be collected into the const array as k/v pairs. Property
- // bindings should result in a `property` instruction, and `AttributeMarker.Bindings` const
- // entries.
- //
- // The difficulty is with dynamic attribute, style, and class bindings. These don't really make
- // sense on an `ng-template` and should probably be parser errors. However,
- // TemplateDefinitionBuilder generates `property` instructions for them, and so we do that as
- // well.
- //
- // Note that we do have a slight behavior difference with TemplateDefinitionBuilder: although
- // TDB emits `property` instructions for dynamic attributes, styles, and classes, only styles
- // and classes also get const collected into the `AttributeMarker.Bindings` field. Dynamic
- // attribute bindings are missing from the consts entirely. We choose to emit them into the
- // consts field anyway, to avoid creating special cases for something so arcane and nonsensical.
- if (type === exports.BindingType.Class ||
- type === exports.BindingType.Style ||
- (type === exports.BindingType.Attribute && !isTextBinding)) {
- // TODO: These cases should be parse errors.
- bindingType = BindingKind.Property;
- }
- }
- return createBindingOp(xref, bindingType, name, convertAstWithInterpolation(view.job, value, i18nMessage), unit, securityContext, isTextBinding, isStructuralTemplateAttribute, templateKind, i18nMessage, sourceSpan);
- }
- function makeListenerHandlerOps(unit, handler, handlerSpan) {
- handler = astOf(handler);
- const handlerOps = new Array();
- let handlerExprs = handler instanceof Chain ? handler.expressions : [handler];
- if (handlerExprs.length === 0) {
- throw new Error('Expected listener to have non-empty expression list.');
- }
- const expressions = handlerExprs.map((expr) => convertAst(expr, unit.job, handlerSpan));
- const returnExpr = expressions.pop();
- handlerOps.push(...expressions.map((e) => createStatementOp(new ExpressionStatement(e, e.sourceSpan))));
- handlerOps.push(createStatementOp(new ReturnStatement(returnExpr, returnExpr.sourceSpan)));
- return handlerOps;
- }
- function makeTwoWayListenerHandlerOps(unit, handler, handlerSpan) {
- handler = astOf(handler);
- const handlerOps = new Array();
- if (handler instanceof Chain) {
- if (handler.expressions.length === 1) {
- handler = handler.expressions[0];
- }
- else {
- // This is validated during parsing already, but we do it here just in case.
- throw new Error('Expected two-way listener to have a single expression.');
- }
- }
- const handlerExpr = convertAst(handler, unit.job, handlerSpan);
- const eventReference = new LexicalReadExpr('$event');
- const twoWaySetExpr = new TwoWayBindingSetExpr(handlerExpr, eventReference);
- handlerOps.push(createStatementOp(new ExpressionStatement(twoWaySetExpr)));
- handlerOps.push(createStatementOp(new ReturnStatement(eventReference)));
- return handlerOps;
- }
- function astOf(ast) {
- return ast instanceof ASTWithSource ? ast.ast : ast;
- }
- /**
- * Process all of the local references on an element-like structure in the template AST and
- * convert them to their IR representation.
- */
- function ingestReferences(op, element) {
- assertIsArray(op.localRefs);
- for (const { name, value } of element.references) {
- op.localRefs.push({
- name,
- target: value,
- });
- }
- }
- /**
- * Assert that the given value is an array.
- */
- function assertIsArray(value) {
- if (!Array.isArray(value)) {
- throw new Error(`AssertionError: expected an array`);
- }
- }
- /**
- * Creates an absolute `ParseSourceSpan` from the relative `ParseSpan`.
- *
- * `ParseSpan` objects are relative to the start of the expression.
- * This method converts these to full `ParseSourceSpan` objects that
- * show where the span is within the overall source file.
- *
- * @param span the relative span to convert.
- * @param baseSourceSpan a span corresponding to the base of the expression tree.
- * @returns a `ParseSourceSpan` for the given span or null if no `baseSourceSpan` was provided.
- */
- function convertSourceSpan(span, baseSourceSpan) {
- if (baseSourceSpan === null) {
- return null;
- }
- const start = baseSourceSpan.start.moveBy(span.start);
- const end = baseSourceSpan.start.moveBy(span.end);
- const fullStart = baseSourceSpan.fullStart.moveBy(span.start);
- return new ParseSourceSpan(start, end, fullStart);
- }
- /**
- * With the directive-based control flow users were able to conditionally project content using
- * the `*` syntax. E.g. `<div *ngIf="expr" projectMe></div>` will be projected into
- * `<ng-content select="[projectMe]"/>`, because the attributes and tag name from the `div` are
- * copied to the template via the template creation instruction. With `@if` and `@for` that is
- * not the case, because the conditional is placed *around* elements, rather than *on* them.
- * The result is that content projection won't work in the same way if a user converts from
- * `*ngIf` to `@if`.
- *
- * This function aims to cover the most common case by doing the same copying when a control flow
- * node has *one and only one* root element or template node.
- *
- * This approach comes with some caveats:
- * 1. As soon as any other node is added to the root, the copying behavior won't work anymore.
- * A diagnostic will be added to flag cases like this and to explain how to work around it.
- * 2. If `preserveWhitespaces` is enabled, it's very likely that indentation will break this
- * workaround, because it'll include an additional text node as the first child. We can work
- * around it here, but in a discussion it was decided not to, because the user explicitly opted
- * into preserving the whitespace and we would have to drop it from the generated code.
- * The diagnostic mentioned point in #1 will flag such cases to users.
- *
- * @returns Tag name to be used for the control flow template.
- */
- function ingestControlFlowInsertionPoint(unit, xref, node) {
- let root = null;
- for (const child of node.children) {
- // Skip over comment nodes and @let declarations since
- // it doesn't matter where they end up in the DOM.
- if (child instanceof Comment$1 || child instanceof LetDeclaration$1) {
- continue;
- }
- // We can only infer the tag name/attributes if there's a single root node.
- if (root !== null) {
- return null;
- }
- // Root nodes can only elements or templates with a tag name (e.g. `<div *foo></div>`).
- if (child instanceof Element$1 || (child instanceof Template && child.tagName !== null)) {
- root = child;
- }
- else {
- return null;
- }
- }
- // If we've found a single root node, its tag name and attributes can be
- // copied to the surrounding template to be used for content projection.
- if (root !== null) {
- // Collect the static attributes for content projection purposes.
- for (const attr of root.attributes) {
- const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
- unit.update.push(createBindingOp(xref, BindingKind.Attribute, attr.name, literal$1(attr.value), null, securityContext, true, false, null, asMessage(attr.i18n), attr.sourceSpan));
- }
- // Also collect the inputs since they participate in content projection as well.
- // Note that TDB used to collect the outputs as well, but it wasn't passing them into
- // the template instruction. Here we just don't collect them.
- for (const attr of root.inputs) {
- if (attr.type !== exports.BindingType.Animation && attr.type !== exports.BindingType.Attribute) {
- const securityContext = domSchema.securityContext(NG_TEMPLATE_TAG_NAME, attr.name, true);
- unit.create.push(createExtractedAttributeOp(xref, BindingKind.Property, null, attr.name, null, null, null, securityContext));
- }
- }
- const tagName = root instanceof Element$1 ? root.name : root.tagName;
- // Don't pass along `ng-template` tag name since it enables directive matching.
- return tagName === NG_TEMPLATE_TAG_NAME ? null : tagName;
- }
- return null;
- }
- /*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
- /**
- * Whether to produce instructions that will attach the source location to each DOM node.
- *
- * !!!Important!!! at the time of writing this flag isn't exposed externally, but internal debug
- * tools enable it via a local change. Any modifications to this flag need to update the
- * internal tooling as well.
- */
- let ENABLE_TEMPLATE_SOURCE_LOCATIONS = false;
- /** Gets whether template source locations are enabled. */
- function getTemplateSourceLocationsEnabled() {
- return ENABLE_TEMPLATE_SOURCE_LOCATIONS;
- }
- // if (rf & flags) { .. }
- function renderFlagCheckIfStmt(flags, statements) {
- return ifStmt(variable(RENDER_FLAGS).bitwiseAnd(literal$1(flags), null, false), statements);
- }
- /**
- * Translates query flags into `TQueryFlags` type in
- * packages/core/src/render3/interfaces/query.ts
- * @param query
- */
- function toQueryFlags(query) {
- return ((query.descendants ? 1 /* QueryFlags.descendants */ : 0 /* QueryFlags.none */) |
- (query.static ? 2 /* QueryFlags.isStatic */ : 0 /* QueryFlags.none */) |
- (query.emitDistinctChangesOnly ? 4 /* QueryFlags.emitDistinctChangesOnly */ : 0 /* QueryFlags.none */));
- }
- function getQueryPredicate(query, constantPool) {
- if (Array.isArray(query.predicate)) {
- let predicate = [];
- query.predicate.forEach((selector) => {
- // Each item in predicates array may contain strings with comma-separated refs
- // (for ex. 'ref, ref1, ..., refN'), thus we extract individual refs and store them
- // as separate array entities
- const selectors = selector.split(',').map((token) => literal$1(token.trim()));
- predicate.push(...selectors);
- });
- return constantPool.getConstLiteral(literalArr(predicate), true);
- }
- else {
- // The original predicate may have been wrapped in a `forwardRef()` call.
- switch (query.predicate.forwardRef) {
- case 0 /* ForwardRefHandling.None */:
- case 2 /* ForwardRefHandling.Unwrapped */:
- return query.predicate.expression;
- case 1 /* ForwardRefHandling.Wrapped */:
- return importExpr(Identifiers.resolveForwardRef).callFn([query.predicate.expression]);
- }
- }
- }
- function createQueryCreateCall(query, constantPool, queryTypeFns, prependParams) {
- const parameters = [];
- if (prependParams !== undefined) {
- parameters.push(...prependParams);
- }
- if (query.isSignal) {
- parameters.push(new ReadPropExpr(variable(CONTEXT_NAME), query.propertyName));
- }
- parameters.push(getQueryPredicate(query, constantPool), literal$1(toQueryFlags(query)));
- if (query.read) {
- parameters.push(query.read);
- }
- const queryCreateFn = query.isSignal ? queryTypeFns.signalBased : queryTypeFns.nonSignal;
- return importExpr(queryCreateFn).callFn(parameters);
- }
- const queryAdvancePlaceholder = Symbol('queryAdvancePlaceholder');
- /**
- * Collapses query advance placeholders in a list of statements.
- *
- * This allows for less generated code because multiple sibling query advance
- * statements can be collapsed into a single call with the count as argument.
- *
- * e.g.
- *
- * ```ts
- * bla();
- * queryAdvance();
- * queryAdvance();
- * bla();
- * ```
- *
- * --> will turn into
- *
- * ```ts
- * bla();
- * queryAdvance(2);
- * bla();
- * ```
- */
- function collapseAdvanceStatements(statements) {
- const result = [];
- let advanceCollapseCount = 0;
- const flushAdvanceCount = () => {
- if (advanceCollapseCount > 0) {
- result.unshift(importExpr(Identifiers.queryAdvance)
- .callFn(advanceCollapseCount === 1 ? [] : [literal$1(advanceCollapseCount)])
- .toStmt());
- advanceCollapseCount = 0;
- }
- };
- // Iterate through statements in reverse and collapse advance placeholders.
- for (let i = statements.length - 1; i >= 0; i--) {
- const st = statements[i];
- if (st === queryAdvancePlaceholder) {
- advanceCollapseCount++;
- }
- else {
- flushAdvanceCount();
- result.unshift(st);
- }
- }
- flushAdvanceCount();
- return result;
- }
- // Define and update any view queries
- function createViewQueriesFunction(viewQueries, constantPool, name) {
- const createStatements = [];
- const updateStatements = [];
- const tempAllocator = temporaryAllocator((st) => updateStatements.push(st), TEMPORARY_NAME);
- viewQueries.forEach((query) => {
- // creation call, e.g. r3.viewQuery(somePredicate, true) or
- // r3.viewQuerySignal(ctx.prop, somePredicate, true);
- const queryDefinitionCall = createQueryCreateCall(query, constantPool, {
- signalBased: Identifiers.viewQuerySignal,
- nonSignal: Identifiers.viewQuery,
- });
- createStatements.push(queryDefinitionCall.toStmt());
- // Signal queries update lazily and we just advance the index.
- if (query.isSignal) {
- updateStatements.push(queryAdvancePlaceholder);
- return;
- }
- // update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
- const temporary = tempAllocator();
- const getQueryList = importExpr(Identifiers.loadQuery).callFn([]);
- const refresh = importExpr(Identifiers.queryRefresh).callFn([temporary.set(getQueryList)]);
- const updateDirective = variable(CONTEXT_NAME)
- .prop(query.propertyName)
- .set(query.first ? temporary.prop('first') : temporary);
- updateStatements.push(refresh.and(updateDirective).toStmt());
- });
- const viewQueryFnName = name ? `${name}_Query` : null;
- return fn([new FnParam(RENDER_FLAGS, NUMBER_TYPE), new FnParam(CONTEXT_NAME, null)], [
- renderFlagCheckIfStmt(1 /* core.RenderFlags.Create */, createStatements),
- renderFlagCheckIfStmt(2 /* core.RenderFlags.Update */, collapseAdvanceStatements(updateStatements)),
- ], INFERRED_TYPE, null, viewQueryFnName);
- }
- // Define and update any content queries
- function createContentQueriesFunction(queries, constantPool, name) {
- const createStatements = [];
- const updateStatements = [];
- const tempAllocator = temporaryAllocator((st) => updateStatements.push(st), TEMPORARY_NAME);
- for (const query of queries) {
- // creation, e.g. r3.contentQuery(dirIndex, somePredicate, true, null) or
- // r3.contentQuerySignal(dirIndex, propName, somePredicate, <flags>, <read>).
- createStatements.push(createQueryCreateCall(query, constantPool, { nonSignal: Identifiers.contentQuery, signalBased: Identifiers.contentQuerySignal },
- /* prependParams */ [variable('dirIndex')]).toStmt());
- // Signal queries update lazily and we just advance the index.
- if (query.isSignal) {
- updateStatements.push(queryAdvancePlaceholder);
- continue;
- }
- // update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
- const temporary = tempAllocator();
- const getQueryList = importExpr(Identifiers.loadQuery).callFn([]);
- const refresh = importExpr(Identifiers.queryRefresh).callFn([temporary.set(getQueryList)]);
- const updateDirective = variable(CONTEXT_NAME)
- .prop(query.propertyName)
- .set(query.first ? temporary.prop('first') : temporary);
- updateStatements.push(refresh.and(updateDirective).toStmt());
- }
- const contentQueriesFnName = name ? `${name}_ContentQueries` : null;
- return fn([
- new FnParam(RENDER_FLAGS, NUMBER_TYPE),
- new FnParam(CONTEXT_NAME, null),
- new FnParam('dirIndex', null),
- ], [
- renderFlagCheckIfStmt(1 /* core.RenderFlags.Create */, createStatements),
- renderFlagCheckIfStmt(2 /* core.RenderFlags.Update */, collapseAdvanceStatements(updateStatements)),
- ], INFERRED_TYPE, null, contentQueriesFnName);
- }
- class HtmlParser extends Parser$1 {
- constructor() {
- super(getHtmlTagDefinition);
- }
- parse(source, url, options) {
- return super.parse(source, url, options);
- }
- }
- const PROPERTY_PARTS_SEPARATOR = '.';
- const ATTRIBUTE_PREFIX = 'attr';
- const CLASS_PREFIX = 'class';
- const STYLE_PREFIX = 'style';
- const TEMPLATE_ATTR_PREFIX$1 = '*';
- const ANIMATE_PROP_PREFIX = 'animate-';
- /**
- * Parses bindings in templates and in the directive host area.
- */
- class BindingParser {
- _exprParser;
- _interpolationConfig;
- _schemaRegistry;
- errors;
- constructor(_exprParser, _interpolationConfig, _schemaRegistry, errors) {
- this._exprParser = _exprParser;
- this._interpolationConfig = _interpolationConfig;
- this._schemaRegistry = _schemaRegistry;
- this.errors = errors;
- }
- get interpolationConfig() {
- return this._interpolationConfig;
- }
- createBoundHostProperties(properties, sourceSpan) {
- const boundProps = [];
- for (const propName of Object.keys(properties)) {
- const expression = properties[propName];
- if (typeof expression === 'string') {
- this.parsePropertyBinding(propName, expression, true, false, sourceSpan, sourceSpan.start.offset, undefined, [],
- // Use the `sourceSpan` for `keySpan`. This isn't really accurate, but neither is the
- // sourceSpan, as it represents the sourceSpan of the host itself rather than the
- // source of the host binding (which doesn't exist in the template). Regardless,
- // neither of these values are used in Ivy but are only here to satisfy the function
- // signature. This should likely be refactored in the future so that `sourceSpan`
- // isn't being used inaccurately.
- boundProps, sourceSpan);
- }
- else {
- this._reportError(`Value of the host property binding "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, sourceSpan);
- }
- }
- return boundProps;
- }
- createDirectiveHostEventAsts(hostListeners, sourceSpan) {
- const targetEvents = [];
- for (const propName of Object.keys(hostListeners)) {
- const expression = hostListeners[propName];
- if (typeof expression === 'string') {
- // Use the `sourceSpan` for `keySpan` and `handlerSpan`. This isn't really accurate, but
- // neither is the `sourceSpan`, as it represents the `sourceSpan` of the host itself
- // rather than the source of the host binding (which doesn't exist in the template).
- // Regardless, neither of these values are used in Ivy but are only here to satisfy the
- // function signature. This should likely be refactored in the future so that `sourceSpan`
- // isn't being used inaccurately.
- this.parseEvent(propName, expression,
- /* isAssignmentEvent */ false, sourceSpan, sourceSpan, [], targetEvents, sourceSpan);
- }
- else {
- this._reportError(`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, sourceSpan);
- }
- }
- return targetEvents;
- }
- parseInterpolation(value, sourceSpan, interpolatedTokens) {
- const sourceInfo = sourceSpan.start.toString();
- const absoluteOffset = sourceSpan.fullStart.offset;
- try {
- const ast = this._exprParser.parseInterpolation(value, sourceInfo, absoluteOffset, interpolatedTokens, this._interpolationConfig);
- if (ast)
- this._reportExpressionParserErrors(ast.errors, sourceSpan);
- return ast;
- }
- catch (e) {
- this._reportError(`${e}`, sourceSpan);
- return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
- }
- }
- /**
- * Similar to `parseInterpolation`, but treats the provided string as a single expression
- * element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
- * This is used for parsing the switch expression in ICUs.
- */
- parseInterpolationExpression(expression, sourceSpan) {
- const sourceInfo = sourceSpan.start.toString();
- const absoluteOffset = sourceSpan.start.offset;
- try {
- const ast = this._exprParser.parseInterpolationExpression(expression, sourceInfo, absoluteOffset);
- if (ast)
- this._reportExpressionParserErrors(ast.errors, sourceSpan);
- return ast;
- }
- catch (e) {
- this._reportError(`${e}`, sourceSpan);
- return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
- }
- }
- /**
- * Parses the bindings in a microsyntax expression, and converts them to
- * `ParsedProperty` or `ParsedVariable`.
- *
- * @param tplKey template binding name
- * @param tplValue template binding value
- * @param sourceSpan span of template binding relative to entire the template
- * @param absoluteValueOffset start of the tplValue relative to the entire template
- * @param targetMatchableAttrs potential attributes to match in the template
- * @param targetProps target property bindings in the template
- * @param targetVars target variables in the template
- */
- parseInlineTemplateBinding(tplKey, tplValue, sourceSpan, absoluteValueOffset, targetMatchableAttrs, targetProps, targetVars, isIvyAst) {
- const absoluteKeyOffset = sourceSpan.start.offset + TEMPLATE_ATTR_PREFIX$1.length;
- const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset);
- for (const binding of bindings) {
- // sourceSpan is for the entire HTML attribute. bindingSpan is for a particular
- // binding within the microsyntax expression so it's more narrow than sourceSpan.
- const bindingSpan = moveParseSourceSpan(sourceSpan, binding.sourceSpan);
- const key = binding.key.source;
- const keySpan = moveParseSourceSpan(sourceSpan, binding.key.span);
- if (binding instanceof VariableBinding) {
- const value = binding.value ? binding.value.source : '$implicit';
- const valueSpan = binding.value
- ? moveParseSourceSpan(sourceSpan, binding.value.span)
- : undefined;
- targetVars.push(new ParsedVariable(key, value, bindingSpan, keySpan, valueSpan));
- }
- else if (binding.value) {
- const srcSpan = isIvyAst ? bindingSpan : sourceSpan;
- const valueSpan = moveParseSourceSpan(sourceSpan, binding.value.ast.sourceSpan);
- this._parsePropertyAst(key, binding.value, false, srcSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
- }
- else {
- targetMatchableAttrs.push([key, '' /* value */]);
- // Since this is a literal attribute with no RHS, source span should be
- // just the key span.
- this.parseLiteralAttr(key, null /* value */, keySpan, absoluteValueOffset, undefined /* valueSpan */, targetMatchableAttrs, targetProps, keySpan);
- }
- }
- }
- /**
- * Parses the bindings in a microsyntax expression, e.g.
- * ```html
- * <tag *tplKey="let value1 = prop; let value2 = localVar">
- * ```
- *
- * @param tplKey template binding name
- * @param tplValue template binding value
- * @param sourceSpan span of template binding relative to entire the template
- * @param absoluteKeyOffset start of the `tplKey`
- * @param absoluteValueOffset start of the `tplValue`
- */
- _parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset) {
- const sourceInfo = sourceSpan.start.toString();
- try {
- const bindingsResult = this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo, absoluteKeyOffset, absoluteValueOffset);
- this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
- bindingsResult.warnings.forEach((warning) => {
- this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING);
- });
- return bindingsResult.templateBindings;
- }
- catch (e) {
- this._reportError(`${e}`, sourceSpan);
- return [];
- }
- }
- parseLiteralAttr(name, value, sourceSpan, absoluteOffset, valueSpan, targetMatchableAttrs, targetProps, keySpan) {
- if (isAnimationLabel(name)) {
- name = name.substring(1);
- if (keySpan !== undefined) {
- keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
- }
- if (value) {
- this._reportError(`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
- ` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`, sourceSpan, ParseErrorLevel.ERROR);
- }
- this._parseAnimation(name, value, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps);
- }
- else {
- targetProps.push(new ParsedProperty(name, this._exprParser.wrapLiteralPrimitive(value, '', absoluteOffset), ParsedPropertyType.LITERAL_ATTR, sourceSpan, keySpan, valueSpan));
- }
- }
- parsePropertyBinding(name, expression, isHost, isPartOfAssignmentBinding, sourceSpan, absoluteOffset, valueSpan, targetMatchableAttrs, targetProps, keySpan) {
- if (name.length === 0) {
- this._reportError(`Property name is missing in binding`, sourceSpan);
- }
- let isAnimationProp = false;
- if (name.startsWith(ANIMATE_PROP_PREFIX)) {
- isAnimationProp = true;
- name = name.substring(ANIMATE_PROP_PREFIX.length);
- if (keySpan !== undefined) {
- keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + ANIMATE_PROP_PREFIX.length, keySpan.end.offset));
- }
- }
- else if (isAnimationLabel(name)) {
- isAnimationProp = true;
- name = name.substring(1);
- if (keySpan !== undefined) {
- keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
- }
- }
- if (isAnimationProp) {
- this._parseAnimation(name, expression, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps);
- }
- else {
- this._parsePropertyAst(name, this.parseBinding(expression, isHost, valueSpan || sourceSpan, absoluteOffset), isPartOfAssignmentBinding, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
- }
- }
- parsePropertyInterpolation(name, value, sourceSpan, valueSpan, targetMatchableAttrs, targetProps, keySpan, interpolatedTokens) {
- const expr = this.parseInterpolation(value, valueSpan || sourceSpan, interpolatedTokens);
- if (expr) {
- this._parsePropertyAst(name, expr, false, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
- return true;
- }
- return false;
- }
- _parsePropertyAst(name, ast, isPartOfAssignmentBinding, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
- targetMatchableAttrs.push([name, ast.source]);
- targetProps.push(new ParsedProperty(name, ast, isPartOfAssignmentBinding ? ParsedPropertyType.TWO_WAY : ParsedPropertyType.DEFAULT, sourceSpan, keySpan, valueSpan));
- }
- _parseAnimation(name, expression, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
- if (name.length === 0) {
- this._reportError('Animation trigger is missing', sourceSpan);
- }
- // This will occur when a @trigger is not paired with an expression.
- // For animations it is valid to not have an expression since */void
- // states will be applied by angular when the element is attached/detached
- const ast = this.parseBinding(expression || 'undefined', false, valueSpan || sourceSpan, absoluteOffset);
- targetMatchableAttrs.push([name, ast.source]);
- targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.ANIMATION, sourceSpan, keySpan, valueSpan));
- }
- parseBinding(value, isHostBinding, sourceSpan, absoluteOffset) {
- const sourceInfo = ((sourceSpan && sourceSpan.start) || '(unknown)').toString();
- try {
- const ast = isHostBinding
- ? this._exprParser.parseSimpleBinding(value, sourceInfo, absoluteOffset, this._interpolationConfig)
- : this._exprParser.parseBinding(value, sourceInfo, absoluteOffset, this._interpolationConfig);
- if (ast)
- this._reportExpressionParserErrors(ast.errors, sourceSpan);
- return ast;
- }
- catch (e) {
- this._reportError(`${e}`, sourceSpan);
- return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
- }
- }
- createBoundElementProperty(elementSelector, boundProp, skipValidation = false, mapPropertyName = true) {
- if (boundProp.isAnimation) {
- return new BoundElementProperty(boundProp.name, exports.BindingType.Animation, SecurityContext.NONE, boundProp.expression, null, boundProp.sourceSpan, boundProp.keySpan, boundProp.valueSpan);
- }
- let unit = null;
- let bindingType = undefined;
- let boundPropertyName = null;
- const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
- let securityContexts = undefined;
- // Check for special cases (prefix style, attr, class)
- if (parts.length > 1) {
- if (parts[0] == ATTRIBUTE_PREFIX) {
- boundPropertyName = parts.slice(1).join(PROPERTY_PARTS_SEPARATOR);
- if (!skipValidation) {
- this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true);
- }
- securityContexts = calcPossibleSecurityContexts(this._schemaRegistry, elementSelector, boundPropertyName, true);
- const nsSeparatorIdx = boundPropertyName.indexOf(':');
- if (nsSeparatorIdx > -1) {
- const ns = boundPropertyName.substring(0, nsSeparatorIdx);
- const name = boundPropertyName.substring(nsSeparatorIdx + 1);
- boundPropertyName = mergeNsAndName(ns, name);
- }
- bindingType = exports.BindingType.Attribute;
- }
- else if (parts[0] == CLASS_PREFIX) {
- boundPropertyName = parts[1];
- bindingType = exports.BindingType.Class;
- securityContexts = [SecurityContext.NONE];
- }
- else if (parts[0] == STYLE_PREFIX) {
- unit = parts.length > 2 ? parts[2] : null;
- boundPropertyName = parts[1];
- bindingType = exports.BindingType.Style;
- securityContexts = [SecurityContext.STYLE];
- }
- }
- // If not a special case, use the full property name
- if (boundPropertyName === null) {
- const mappedPropName = this._schemaRegistry.getMappedPropName(boundProp.name);
- boundPropertyName = mapPropertyName ? mappedPropName : boundProp.name;
- securityContexts = calcPossibleSecurityContexts(this._schemaRegistry, elementSelector, mappedPropName, false);
- bindingType =
- boundProp.type === ParsedPropertyType.TWO_WAY ? exports.BindingType.TwoWay : exports.BindingType.Property;
- if (!skipValidation) {
- this._validatePropertyOrAttributeName(mappedPropName, boundProp.sourceSpan, false);
- }
- }
- return new BoundElementProperty(boundPropertyName, bindingType, securityContexts[0], boundProp.expression, unit, boundProp.sourceSpan, boundProp.keySpan, boundProp.valueSpan);
- }
- // TODO: keySpan should be required but was made optional to avoid changing VE parser.
- parseEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
- if (name.length === 0) {
- this._reportError(`Event name is missing in binding`, sourceSpan);
- }
- if (isAnimationLabel(name)) {
- name = name.slice(1);
- if (keySpan !== undefined) {
- keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
- }
- this._parseAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan);
- }
- else {
- this._parseRegularEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan);
- }
- }
- calcPossibleSecurityContexts(selector, propName, isAttribute) {
- const prop = this._schemaRegistry.getMappedPropName(propName);
- return calcPossibleSecurityContexts(this._schemaRegistry, selector, prop, isAttribute);
- }
- _parseAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan) {
- const matches = splitAtPeriod(name, [name, '']);
- const eventName = matches[0];
- const phase = matches[1].toLowerCase();
- const ast = this._parseAction(expression, handlerSpan);
- targetEvents.push(new ParsedEvent(eventName, phase, exports.ParsedEventType.Animation, ast, sourceSpan, handlerSpan, keySpan));
- if (eventName.length === 0) {
- this._reportError(`Animation event name is missing in binding`, sourceSpan);
- }
- if (phase) {
- if (phase !== 'start' && phase !== 'done') {
- this._reportError(`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`, sourceSpan);
- }
- }
- else {
- this._reportError(`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`, sourceSpan);
- }
- }
- _parseRegularEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
- // long format: 'target: eventName'
- const [target, eventName] = splitAtColon(name, [null, name]);
- const prevErrorCount = this.errors.length;
- const ast = this._parseAction(expression, handlerSpan);
- const isValid = this.errors.length === prevErrorCount;
- targetMatchableAttrs.push([name, ast.source]);
- // Don't try to validate assignment events if there were other
- // parsing errors to avoid adding more noise to the error logs.
- if (isAssignmentEvent && isValid && !this._isAllowedAssignmentEvent(ast)) {
- this._reportError('Unsupported expression in a two-way binding', sourceSpan);
- }
- targetEvents.push(new ParsedEvent(eventName, target, isAssignmentEvent ? exports.ParsedEventType.TwoWay : exports.ParsedEventType.Regular, ast, sourceSpan, handlerSpan, keySpan));
- // Don't detect directives for event names for now,
- // so don't add the event name to the matchableAttrs
- }
- _parseAction(value, sourceSpan) {
- const sourceInfo = ((sourceSpan && sourceSpan.start) || '(unknown').toString();
- const absoluteOffset = sourceSpan && sourceSpan.start ? sourceSpan.start.offset : 0;
- try {
- const ast = this._exprParser.parseAction(value, sourceInfo, absoluteOffset, this._interpolationConfig);
- if (ast) {
- this._reportExpressionParserErrors(ast.errors, sourceSpan);
- }
- if (!ast || ast.ast instanceof EmptyExpr$1) {
- this._reportError(`Empty expressions are not allowed`, sourceSpan);
- return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
- }
- return ast;
- }
- catch (e) {
- this._reportError(`${e}`, sourceSpan);
- return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
- }
- }
- _reportError(message, sourceSpan, level = ParseErrorLevel.ERROR, relatedError) {
- this.errors.push(new ParseError(sourceSpan, message, level, relatedError));
- }
- _reportExpressionParserErrors(errors, sourceSpan) {
- for (const error of errors) {
- this._reportError(error.message, sourceSpan, undefined, error);
- }
- }
- /**
- * @param propName the name of the property / attribute
- * @param sourceSpan
- * @param isAttr true when binding to an attribute
- */
- _validatePropertyOrAttributeName(propName, sourceSpan, isAttr) {
- const report = isAttr
- ? this._schemaRegistry.validateAttribute(propName)
- : this._schemaRegistry.validateProperty(propName);
- if (report.error) {
- this._reportError(report.msg, sourceSpan, ParseErrorLevel.ERROR);
- }
- }
- /**
- * Returns whether a parsed AST is allowed to be used within the event side of a two-way binding.
- * @param ast Parsed AST to be checked.
- */
- _isAllowedAssignmentEvent(ast) {
- if (ast instanceof ASTWithSource) {
- return this._isAllowedAssignmentEvent(ast.ast);
- }
- if (ast instanceof NonNullAssert) {
- return this._isAllowedAssignmentEvent(ast.expression);
- }
- if (ast instanceof Call &&
- ast.args.length === 1 &&
- ast.receiver instanceof PropertyRead &&
- ast.receiver.name === '$any' &&
- ast.receiver.receiver instanceof ImplicitReceiver &&
- !(ast.receiver.receiver instanceof ThisReceiver)) {
- return this._isAllowedAssignmentEvent(ast.args[0]);
- }
- if (ast instanceof PropertyRead || ast instanceof KeyedRead) {
- return true;
- }
- return false;
- }
- }
- function isAnimationLabel(name) {
- return name[0] == '@';
- }
- function calcPossibleSecurityContexts(registry, selector, propName, isAttribute) {
- const ctxs = [];
- CssSelector.parse(selector).forEach((selector) => {
- const elementNames = selector.element ? [selector.element] : registry.allKnownElementNames();
- const notElementNames = new Set(selector.notSelectors
- .filter((selector) => selector.isElementSelector())
- .map((selector) => selector.element));
- const possibleElementNames = elementNames.filter((elementName) => !notElementNames.has(elementName));
- ctxs.push(...possibleElementNames.map((elementName) => registry.securityContext(elementName, propName, isAttribute)));
- });
- return ctxs.length === 0 ? [SecurityContext.NONE] : Array.from(new Set(ctxs)).sort();
- }
- /**
- * Compute a new ParseSourceSpan based off an original `sourceSpan` by using
- * absolute offsets from the specified `absoluteSpan`.
- *
- * @param sourceSpan original source span
- * @param absoluteSpan absolute source span to move to
- */
- function moveParseSourceSpan(sourceSpan, absoluteSpan) {
- // The difference of two absolute offsets provide the relative offset
- const startDiff = absoluteSpan.start - sourceSpan.start.offset;
- const endDiff = absoluteSpan.end - sourceSpan.end.offset;
- return new ParseSourceSpan(sourceSpan.start.moveBy(startDiff), sourceSpan.end.moveBy(endDiff), sourceSpan.fullStart.moveBy(startDiff), sourceSpan.details);
- }
- // Some of the code comes from WebComponents.JS
- // https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
- function isStyleUrlResolvable(url) {
- if (url == null || url.length === 0 || url[0] == '/')
- return false;
- const schemeMatch = url.match(URL_WITH_SCHEMA_REGEXP);
- return schemeMatch === null || schemeMatch[1] == 'package' || schemeMatch[1] == 'asset';
- }
- const URL_WITH_SCHEMA_REGEXP = /^([^:/?#]+):/;
- const NG_CONTENT_SELECT_ATTR = 'select';
- const LINK_ELEMENT = 'link';
- const LINK_STYLE_REL_ATTR = 'rel';
- const LINK_STYLE_HREF_ATTR = 'href';
- const LINK_STYLE_REL_VALUE = 'stylesheet';
- const STYLE_ELEMENT = 'style';
- const SCRIPT_ELEMENT = 'script';
- const NG_NON_BINDABLE_ATTR = 'ngNonBindable';
- const NG_PROJECT_AS = 'ngProjectAs';
- function preparseElement(ast) {
- let selectAttr = null;
- let hrefAttr = null;
- let relAttr = null;
- let nonBindable = false;
- let projectAs = '';
- ast.attrs.forEach((attr) => {
- const lcAttrName = attr.name.toLowerCase();
- if (lcAttrName == NG_CONTENT_SELECT_ATTR) {
- selectAttr = attr.value;
- }
- else if (lcAttrName == LINK_STYLE_HREF_ATTR) {
- hrefAttr = attr.value;
- }
- else if (lcAttrName == LINK_STYLE_REL_ATTR) {
- relAttr = attr.value;
- }
- else if (attr.name == NG_NON_BINDABLE_ATTR) {
- nonBindable = true;
- }
- else if (attr.name == NG_PROJECT_AS) {
- if (attr.value.length > 0) {
- projectAs = attr.value;
- }
- }
- });
- selectAttr = normalizeNgContentSelect(selectAttr);
- const nodeName = ast.name.toLowerCase();
- let type = PreparsedElementType.OTHER;
- if (isNgContent(nodeName)) {
- type = PreparsedElementType.NG_CONTENT;
- }
- else if (nodeName == STYLE_ELEMENT) {
- type = PreparsedElementType.STYLE;
- }
- else if (nodeName == SCRIPT_ELEMENT) {
- type = PreparsedElementType.SCRIPT;
- }
- else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
- type = PreparsedElementType.STYLESHEET;
- }
- return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable, projectAs);
- }
- var PreparsedElementType;
- (function (PreparsedElementType) {
- PreparsedElementType[PreparsedElementType["NG_CONTENT"] = 0] = "NG_CONTENT";
- PreparsedElementType[PreparsedElementType["STYLE"] = 1] = "STYLE";
- PreparsedElementType[PreparsedElementType["STYLESHEET"] = 2] = "STYLESHEET";
- PreparsedElementType[PreparsedElementType["SCRIPT"] = 3] = "SCRIPT";
- PreparsedElementType[PreparsedElementType["OTHER"] = 4] = "OTHER";
- })(PreparsedElementType || (PreparsedElementType = {}));
- class PreparsedElement {
- type;
- selectAttr;
- hrefAttr;
- nonBindable;
- projectAs;
- constructor(type, selectAttr, hrefAttr, nonBindable, projectAs) {
- this.type = type;
- this.selectAttr = selectAttr;
- this.hrefAttr = hrefAttr;
- this.nonBindable = nonBindable;
- this.projectAs = projectAs;
- }
- }
- function normalizeNgContentSelect(selectAttr) {
- if (selectAttr === null || selectAttr.length === 0) {
- return '*';
- }
- return selectAttr;
- }
- /** Pattern for the expression in a for loop block. */
- const FOR_LOOP_EXPRESSION_PATTERN = /^\s*([0-9A-Za-z_$]*)\s+of\s+([\S\s]*)/;
- /** Pattern for the tracking expression in a for loop block. */
- const FOR_LOOP_TRACK_PATTERN = /^track\s+([\S\s]*)/;
- /** Pattern for the `as` expression in a conditional block. */
- const CONDITIONAL_ALIAS_PATTERN = /^(as\s+)(.*)/;
- /** Pattern used to identify an `else if` block. */
- const ELSE_IF_PATTERN = /^else[^\S\r\n]+if/;
- /** Pattern used to identify a `let` parameter. */
- const FOR_LOOP_LET_PATTERN = /^let\s+([\S\s]*)/;
- /** Pattern used to validate a JavaScript identifier. */
- const IDENTIFIER_PATTERN = /^[$A-Z_][0-9A-Z_$]*$/i;
- /**
- * Pattern to group a string into leading whitespace, non whitespace, and trailing whitespace.
- * Useful for getting the variable name span when a span can contain leading and trailing space.
- */
- const CHARACTERS_IN_SURROUNDING_WHITESPACE_PATTERN = /(\s*)(\S+)(\s*)/;
- /** Names of variables that are allowed to be used in the `let` expression of a `for` loop. */
- const ALLOWED_FOR_LOOP_LET_VARIABLES = new Set([
- '$index',
- '$first',
- '$last',
- '$even',
- '$odd',
- '$count',
- ]);
- /**
- * Predicate function that determines if a block with
- * a specific name cam be connected to a `for` block.
- */
- function isConnectedForLoopBlock(name) {
- return name === 'empty';
- }
- /**
- * Predicate function that determines if a block with
- * a specific name cam be connected to an `if` block.
- */
- function isConnectedIfLoopBlock(name) {
- return name === 'else' || ELSE_IF_PATTERN.test(name);
- }
- /** Creates an `if` loop block from an HTML AST node. */
- function createIfBlock(ast, connectedBlocks, visitor, bindingParser) {
- const errors = validateIfConnectedBlocks(connectedBlocks);
- const branches = [];
- const mainBlockParams = parseConditionalBlockParameters(ast, errors, bindingParser);
- if (mainBlockParams !== null) {
- branches.push(new IfBlockBranch(mainBlockParams.expression, visitAll(visitor, ast.children, ast.children), mainBlockParams.expressionAlias, ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan, ast.nameSpan, ast.i18n));
- }
- for (const block of connectedBlocks) {
- if (ELSE_IF_PATTERN.test(block.name)) {
- const params = parseConditionalBlockParameters(block, errors, bindingParser);
- if (params !== null) {
- const children = visitAll(visitor, block.children, block.children);
- branches.push(new IfBlockBranch(params.expression, children, params.expressionAlias, block.sourceSpan, block.startSourceSpan, block.endSourceSpan, block.nameSpan, block.i18n));
- }
- }
- else if (block.name === 'else') {
- const children = visitAll(visitor, block.children, block.children);
- branches.push(new IfBlockBranch(null, children, null, block.sourceSpan, block.startSourceSpan, block.endSourceSpan, block.nameSpan, block.i18n));
- }
- }
- // The outer IfBlock should have a span that encapsulates all branches.
- const ifBlockStartSourceSpan = branches.length > 0 ? branches[0].startSourceSpan : ast.startSourceSpan;
- const ifBlockEndSourceSpan = branches.length > 0 ? branches[branches.length - 1].endSourceSpan : ast.endSourceSpan;
- let wholeSourceSpan = ast.sourceSpan;
- const lastBranch = branches[branches.length - 1];
- if (lastBranch !== undefined) {
- wholeSourceSpan = new ParseSourceSpan(ifBlockStartSourceSpan.start, lastBranch.sourceSpan.end);
- }
- return {
- node: new IfBlock(branches, wholeSourceSpan, ast.startSourceSpan, ifBlockEndSourceSpan, ast.nameSpan),
- errors,
- };
- }
- /** Creates a `for` loop block from an HTML AST node. */
- function createForLoop(ast, connectedBlocks, visitor, bindingParser) {
- const errors = [];
- const params = parseForLoopParameters(ast, errors, bindingParser);
- let node = null;
- let empty = null;
- for (const block of connectedBlocks) {
- if (block.name === 'empty') {
- if (empty !== null) {
- errors.push(new ParseError(block.sourceSpan, '@for loop can only have one @empty block'));
- }
- else if (block.parameters.length > 0) {
- errors.push(new ParseError(block.sourceSpan, '@empty block cannot have parameters'));
- }
- else {
- empty = new ForLoopBlockEmpty(visitAll(visitor, block.children, block.children), block.sourceSpan, block.startSourceSpan, block.endSourceSpan, block.nameSpan, block.i18n);
- }
- }
- else {
- errors.push(new ParseError(block.sourceSpan, `Unrecognized @for loop block "${block.name}"`));
- }
- }
- if (params !== null) {
- if (params.trackBy === null) {
- // TODO: We should not fail here, and instead try to produce some AST for the language
- // service.
- errors.push(new ParseError(ast.startSourceSpan, '@for loop must have a "track" expression'));
- }
- else {
- // The `for` block has a main span that includes the `empty` branch. For only the span of the
- // main `for` body, use `mainSourceSpan`.
- const endSpan = empty?.endSourceSpan ?? ast.endSourceSpan;
- const sourceSpan = new ParseSourceSpan(ast.sourceSpan.start, endSpan?.end ?? ast.sourceSpan.end);
- node = new ForLoopBlock(params.itemName, params.expression, params.trackBy.expression, params.trackBy.keywordSpan, params.context, visitAll(visitor, ast.children, ast.children), empty, sourceSpan, ast.sourceSpan, ast.startSourceSpan, endSpan, ast.nameSpan, ast.i18n);
- }
- }
- return { node, errors };
- }
- /** Creates a switch block from an HTML AST node. */
- function createSwitchBlock(ast, visitor, bindingParser) {
- const errors = validateSwitchBlock(ast);
- const primaryExpression = ast.parameters.length > 0
- ? parseBlockParameterToBinding(ast.parameters[0], bindingParser)
- : bindingParser.parseBinding('', false, ast.sourceSpan, 0);
- const cases = [];
- const unknownBlocks = [];
- let defaultCase = null;
- // Here we assume that all the blocks are valid given that we validated them above.
- for (const node of ast.children) {
- if (!(node instanceof Block)) {
- continue;
- }
- if ((node.name !== 'case' || node.parameters.length === 0) && node.name !== 'default') {
- unknownBlocks.push(new UnknownBlock(node.name, node.sourceSpan, node.nameSpan));
- continue;
- }
- const expression = node.name === 'case' ? parseBlockParameterToBinding(node.parameters[0], bindingParser) : null;
- const ast = new SwitchBlockCase(expression, visitAll(visitor, node.children, node.children), node.sourceSpan, node.startSourceSpan, node.endSourceSpan, node.nameSpan, node.i18n);
- if (expression === null) {
- defaultCase = ast;
- }
- else {
- cases.push(ast);
- }
- }
- // Ensure that the default case is last in the array.
- if (defaultCase !== null) {
- cases.push(defaultCase);
- }
- return {
- node: new SwitchBlock(primaryExpression, cases, unknownBlocks, ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan, ast.nameSpan),
- errors,
- };
- }
- /** Parses the parameters of a `for` loop block. */
- function parseForLoopParameters(block, errors, bindingParser) {
- if (block.parameters.length === 0) {
- errors.push(new ParseError(block.startSourceSpan, '@for loop does not have an expression'));
- return null;
- }
- const [expressionParam, ...secondaryParams] = block.parameters;
- const match = stripOptionalParentheses(expressionParam, errors)?.match(FOR_LOOP_EXPRESSION_PATTERN);
- if (!match || match[2].trim().length === 0) {
- errors.push(new ParseError(expressionParam.sourceSpan, 'Cannot parse expression. @for loop expression must match the pattern "<identifier> of <expression>"'));
- return null;
- }
- const [, itemName, rawExpression] = match;
- if (ALLOWED_FOR_LOOP_LET_VARIABLES.has(itemName)) {
- errors.push(new ParseError(expressionParam.sourceSpan, `@for loop item name cannot be one of ${Array.from(ALLOWED_FOR_LOOP_LET_VARIABLES).join(', ')}.`));
- }
- // `expressionParam.expression` contains the variable declaration and the expression of the
- // for...of statement, i.e. 'user of users' The variable of a ForOfStatement is _only_ the "const
- // user" part and does not include "of x".
- const variableName = expressionParam.expression.split(' ')[0];
- const variableSpan = new ParseSourceSpan(expressionParam.sourceSpan.start, expressionParam.sourceSpan.start.moveBy(variableName.length));
- const result = {
- itemName: new Variable(itemName, '$implicit', variableSpan, variableSpan),
- trackBy: null,
- expression: parseBlockParameterToBinding(expressionParam, bindingParser, rawExpression),
- context: Array.from(ALLOWED_FOR_LOOP_LET_VARIABLES, (variableName) => {
- // Give ambiently-available context variables empty spans at the end of
- // the start of the `for` block, since they are not explicitly defined.
- const emptySpanAfterForBlockStart = new ParseSourceSpan(block.startSourceSpan.end, block.startSourceSpan.end);
- return new Variable(variableName, variableName, emptySpanAfterForBlockStart, emptySpanAfterForBlockStart);
- }),
- };
- for (const param of secondaryParams) {
- const letMatch = param.expression.match(FOR_LOOP_LET_PATTERN);
- if (letMatch !== null) {
- const variablesSpan = new ParseSourceSpan(param.sourceSpan.start.moveBy(letMatch[0].length - letMatch[1].length), param.sourceSpan.end);
- parseLetParameter(param.sourceSpan, letMatch[1], variablesSpan, itemName, result.context, errors);
- continue;
- }
- const trackMatch = param.expression.match(FOR_LOOP_TRACK_PATTERN);
- if (trackMatch !== null) {
- if (result.trackBy !== null) {
- errors.push(new ParseError(param.sourceSpan, '@for loop can only have one "track" expression'));
- }
- else {
- const expression = parseBlockParameterToBinding(param, bindingParser, trackMatch[1]);
- if (expression.ast instanceof EmptyExpr$1) {
- errors.push(new ParseError(block.startSourceSpan, '@for loop must have a "track" expression'));
- }
- const keywordSpan = new ParseSourceSpan(param.sourceSpan.start, param.sourceSpan.start.moveBy('track'.length));
- result.trackBy = { expression, keywordSpan };
- }
- continue;
- }
- errors.push(new ParseError(param.sourceSpan, `Unrecognized @for loop parameter "${param.expression}"`));
- }
- return result;
- }
- /** Parses the `let` parameter of a `for` loop block. */
- function parseLetParameter(sourceSpan, expression, span, loopItemName, context, errors) {
- const parts = expression.split(',');
- let startSpan = span.start;
- for (const part of parts) {
- const expressionParts = part.split('=');
- const name = expressionParts.length === 2 ? expressionParts[0].trim() : '';
- const variableName = expressionParts.length === 2 ? expressionParts[1].trim() : '';
- if (name.length === 0 || variableName.length === 0) {
- errors.push(new ParseError(sourceSpan, `Invalid @for loop "let" parameter. Parameter should match the pattern "<name> = <variable name>"`));
- }
- else if (!ALLOWED_FOR_LOOP_LET_VARIABLES.has(variableName)) {
- errors.push(new ParseError(sourceSpan, `Unknown "let" parameter variable "${variableName}". The allowed variables are: ${Array.from(ALLOWED_FOR_LOOP_LET_VARIABLES).join(', ')}`));
- }
- else if (name === loopItemName) {
- errors.push(new ParseError(sourceSpan, `Invalid @for loop "let" parameter. Variable cannot be called "${loopItemName}"`));
- }
- else if (context.some((v) => v.name === name)) {
- errors.push(new ParseError(sourceSpan, `Duplicate "let" parameter variable "${variableName}"`));
- }
- else {
- const [, keyLeadingWhitespace, keyName] = expressionParts[0].match(CHARACTERS_IN_SURROUNDING_WHITESPACE_PATTERN) ?? [];
- const keySpan = keyLeadingWhitespace !== undefined && expressionParts.length === 2
- ? new ParseSourceSpan(
- /* strip leading spaces */
- startSpan.moveBy(keyLeadingWhitespace.length),
- /* advance to end of the variable name */
- startSpan.moveBy(keyLeadingWhitespace.length + keyName.length))
- : span;
- let valueSpan = undefined;
- if (expressionParts.length === 2) {
- const [, valueLeadingWhitespace, implicit] = expressionParts[1].match(CHARACTERS_IN_SURROUNDING_WHITESPACE_PATTERN) ?? [];
- valueSpan =
- valueLeadingWhitespace !== undefined
- ? new ParseSourceSpan(startSpan.moveBy(expressionParts[0].length + 1 + valueLeadingWhitespace.length), startSpan.moveBy(expressionParts[0].length + 1 + valueLeadingWhitespace.length + implicit.length))
- : undefined;
- }
- const sourceSpan = new ParseSourceSpan(keySpan.start, valueSpan?.end ?? keySpan.end);
- context.push(new Variable(name, variableName, sourceSpan, keySpan, valueSpan));
- }
- startSpan = startSpan.moveBy(part.length + 1 /* add 1 to move past the comma */);
- }
- }
- /**
- * Checks that the shape of the blocks connected to an
- * `@if` block is correct. Returns an array of errors.
- */
- function validateIfConnectedBlocks(connectedBlocks) {
- const errors = [];
- let hasElse = false;
- for (let i = 0; i < connectedBlocks.length; i++) {
- const block = connectedBlocks[i];
- if (block.name === 'else') {
- if (hasElse) {
- errors.push(new ParseError(block.startSourceSpan, 'Conditional can only have one @else block'));
- }
- else if (connectedBlocks.length > 1 && i < connectedBlocks.length - 1) {
- errors.push(new ParseError(block.startSourceSpan, '@else block must be last inside the conditional'));
- }
- else if (block.parameters.length > 0) {
- errors.push(new ParseError(block.startSourceSpan, '@else block cannot have parameters'));
- }
- hasElse = true;
- }
- else if (!ELSE_IF_PATTERN.test(block.name)) {
- errors.push(new ParseError(block.startSourceSpan, `Unrecognized conditional block @${block.name}`));
- }
- }
- return errors;
- }
- /** Checks that the shape of a `switch` block is valid. Returns an array of errors. */
- function validateSwitchBlock(ast) {
- const errors = [];
- let hasDefault = false;
- if (ast.parameters.length !== 1) {
- errors.push(new ParseError(ast.startSourceSpan, '@switch block must have exactly one parameter'));
- return errors;
- }
- for (const node of ast.children) {
- // Skip over comments and empty text nodes inside the switch block.
- // Empty text nodes can be used for formatting while comments don't affect the runtime.
- if (node instanceof Comment ||
- (node instanceof Text && node.value.trim().length === 0)) {
- continue;
- }
- if (!(node instanceof Block) || (node.name !== 'case' && node.name !== 'default')) {
- errors.push(new ParseError(node.sourceSpan, '@switch block can only contain @case and @default blocks'));
- continue;
- }
- if (node.name === 'default') {
- if (hasDefault) {
- errors.push(new ParseError(node.startSourceSpan, '@switch block can only have one @default block'));
- }
- else if (node.parameters.length > 0) {
- errors.push(new ParseError(node.startSourceSpan, '@default block cannot have parameters'));
- }
- hasDefault = true;
- }
- else if (node.name === 'case' && node.parameters.length !== 1) {
- errors.push(new ParseError(node.startSourceSpan, '@case block must have exactly one parameter'));
- }
- }
- return errors;
- }
- /**
- * Parses a block parameter into a binding AST.
- * @param ast Block parameter that should be parsed.
- * @param bindingParser Parser that the expression should be parsed with.
- * @param part Specific part of the expression that should be parsed.
- */
- function parseBlockParameterToBinding(ast, bindingParser, part) {
- let start;
- let end;
- if (typeof part === 'string') {
- // Note: `lastIndexOf` here should be enough to know the start index of the expression,
- // because we know that it'll be at the end of the param. Ideally we could use the `d`
- // flag when matching via regex and get the index from `match.indices`, but it's unclear
- // if we can use it yet since it's a relatively new feature. See:
- // https://github.com/tc39/proposal-regexp-match-indices
- start = Math.max(0, ast.expression.lastIndexOf(part));
- end = start + part.length;
- }
- else {
- start = 0;
- end = ast.expression.length;
- }
- return bindingParser.parseBinding(ast.expression.slice(start, end), false, ast.sourceSpan, ast.sourceSpan.start.offset + start);
- }
- /** Parses the parameter of a conditional block (`if` or `else if`). */
- function parseConditionalBlockParameters(block, errors, bindingParser) {
- if (block.parameters.length === 0) {
- errors.push(new ParseError(block.startSourceSpan, 'Conditional block does not have an expression'));
- return null;
- }
- const expression = parseBlockParameterToBinding(block.parameters[0], bindingParser);
- let expressionAlias = null;
- // Start from 1 since we processed the first parameter already.
- for (let i = 1; i < block.parameters.length; i++) {
- const param = block.parameters[i];
- const aliasMatch = param.expression.match(CONDITIONAL_ALIAS_PATTERN);
- // For now conditionals can only have an `as` parameter.
- // We may want to rework this later if we add more.
- if (aliasMatch === null) {
- errors.push(new ParseError(param.sourceSpan, `Unrecognized conditional parameter "${param.expression}"`));
- }
- else if (block.name !== 'if') {
- errors.push(new ParseError(param.sourceSpan, '"as" expression is only allowed on the primary @if block'));
- }
- else if (expressionAlias !== null) {
- errors.push(new ParseError(param.sourceSpan, 'Conditional can only have one "as" expression'));
- }
- else {
- const name = aliasMatch[2].trim();
- if (IDENTIFIER_PATTERN.test(name)) {
- const variableStart = param.sourceSpan.start.moveBy(aliasMatch[1].length);
- const variableSpan = new ParseSourceSpan(variableStart, variableStart.moveBy(name.length));
- expressionAlias = new Variable(name, name, variableSpan, variableSpan);
- }
- else {
- errors.push(new ParseError(param.sourceSpan, '"as" expression must be a valid JavaScript identifier'));
- }
- }
- }
- return { expression, expressionAlias };
- }
- /** Strips optional parentheses around from a control from expression parameter. */
- function stripOptionalParentheses(param, errors) {
- const expression = param.expression;
- const spaceRegex = /^\s$/;
- let openParens = 0;
- let start = 0;
- let end = expression.length - 1;
- for (let i = 0; i < expression.length; i++) {
- const char = expression[i];
- if (char === '(') {
- start = i + 1;
- openParens++;
- }
- else if (spaceRegex.test(char)) {
- continue;
- }
- else {
- break;
- }
- }
- if (openParens === 0) {
- return expression;
- }
- for (let i = expression.length - 1; i > -1; i--) {
- const char = expression[i];
- if (char === ')') {
- end = i;
- openParens--;
- if (openParens === 0) {
- break;
- }
- }
- else if (spaceRegex.test(char)) {
- continue;
- }
- else {
- break;
- }
- }
- if (openParens !== 0) {
- errors.push(new ParseError(param.sourceSpan, 'Unclosed parentheses in expression'));
- return null;
- }
- return expression.slice(start, end);
- }
- /** Pattern for a timing value in a trigger. */
- const TIME_PATTERN = /^\d+\.?\d*(ms|s)?$/;
- /** Pattern for a separator between keywords in a trigger expression. */
- const SEPARATOR_PATTERN = /^\s$/;
- /** Pairs of characters that form syntax that is comma-delimited. */
- const COMMA_DELIMITED_SYNTAX = new Map([
- [$LBRACE, $RBRACE], // Object literals
- [$LBRACKET, $RBRACKET], // Array literals
- [$LPAREN, $RPAREN], // Function calls
- ]);
- /** Possible types of `on` triggers. */
- var OnTriggerType;
- (function (OnTriggerType) {
- OnTriggerType["IDLE"] = "idle";
- OnTriggerType["TIMER"] = "timer";
- OnTriggerType["INTERACTION"] = "interaction";
- OnTriggerType["IMMEDIATE"] = "immediate";
- OnTriggerType["HOVER"] = "hover";
- OnTriggerType["VIEWPORT"] = "viewport";
- OnTriggerType["NEVER"] = "never";
- })(OnTriggerType || (OnTriggerType = {}));
- /** Parses a `when` deferred trigger. */
- function parseNeverTrigger({ expression, sourceSpan }, triggers, errors) {
- const neverIndex = expression.indexOf('never');
- const neverSourceSpan = new ParseSourceSpan(sourceSpan.start.moveBy(neverIndex), sourceSpan.start.moveBy(neverIndex + 'never'.length));
- const prefetchSpan = getPrefetchSpan(expression, sourceSpan);
- const hydrateSpan = getHydrateSpan(expression, sourceSpan);
- // This is here just to be safe, we shouldn't enter this function
- // in the first place if a block doesn't have the "on" keyword.
- if (neverIndex === -1) {
- errors.push(new ParseError(sourceSpan, `Could not find "never" keyword in expression`));
- }
- else {
- trackTrigger('never', triggers, errors, new NeverDeferredTrigger(neverSourceSpan, sourceSpan, prefetchSpan, null, hydrateSpan));
- }
- }
- /** Parses a `when` deferred trigger. */
- function parseWhenTrigger({ expression, sourceSpan }, bindingParser, triggers, errors) {
- const whenIndex = expression.indexOf('when');
- const whenSourceSpan = new ParseSourceSpan(sourceSpan.start.moveBy(whenIndex), sourceSpan.start.moveBy(whenIndex + 'when'.length));
- const prefetchSpan = getPrefetchSpan(expression, sourceSpan);
- const hydrateSpan = getHydrateSpan(expression, sourceSpan);
- // This is here just to be safe, we shouldn't enter this function
- // in the first place if a block doesn't have the "when" keyword.
- if (whenIndex === -1) {
- errors.push(new ParseError(sourceSpan, `Could not find "when" keyword in expression`));
- }
- else {
- const start = getTriggerParametersStart(expression, whenIndex + 1);
- const parsed = bindingParser.parseBinding(expression.slice(start), false, sourceSpan, sourceSpan.start.offset + start);
- trackTrigger('when', triggers, errors, new BoundDeferredTrigger(parsed, sourceSpan, prefetchSpan, whenSourceSpan, hydrateSpan));
- }
- }
- /** Parses an `on` trigger */
- function parseOnTrigger({ expression, sourceSpan }, triggers, errors, placeholder) {
- const onIndex = expression.indexOf('on');
- const onSourceSpan = new ParseSourceSpan(sourceSpan.start.moveBy(onIndex), sourceSpan.start.moveBy(onIndex + 'on'.length));
- const prefetchSpan = getPrefetchSpan(expression, sourceSpan);
- const hydrateSpan = getHydrateSpan(expression, sourceSpan);
- // This is here just to be safe, we shouldn't enter this function
- // in the first place if a block doesn't have the "on" keyword.
- if (onIndex === -1) {
- errors.push(new ParseError(sourceSpan, `Could not find "on" keyword in expression`));
- }
- else {
- const start = getTriggerParametersStart(expression, onIndex + 1);
- const parser = new OnTriggerParser(expression, start, sourceSpan, triggers, errors, expression.startsWith('hydrate')
- ? validateHydrateReferenceBasedTrigger
- : validatePlainReferenceBasedTrigger, placeholder, prefetchSpan, onSourceSpan, hydrateSpan);
- parser.parse();
- }
- }
- function getPrefetchSpan(expression, sourceSpan) {
- if (!expression.startsWith('prefetch')) {
- return null;
- }
- return new ParseSourceSpan(sourceSpan.start, sourceSpan.start.moveBy('prefetch'.length));
- }
- function getHydrateSpan(expression, sourceSpan) {
- if (!expression.startsWith('hydrate')) {
- return null;
- }
- return new ParseSourceSpan(sourceSpan.start, sourceSpan.start.moveBy('hydrate'.length));
- }
- class OnTriggerParser {
- expression;
- start;
- span;
- triggers;
- errors;
- validator;
- placeholder;
- prefetchSpan;
- onSourceSpan;
- hydrateSpan;
- index = 0;
- tokens;
- constructor(expression, start, span, triggers, errors, validator, placeholder, prefetchSpan, onSourceSpan, hydrateSpan) {
- this.expression = expression;
- this.start = start;
- this.span = span;
- this.triggers = triggers;
- this.errors = errors;
- this.validator = validator;
- this.placeholder = placeholder;
- this.prefetchSpan = prefetchSpan;
- this.onSourceSpan = onSourceSpan;
- this.hydrateSpan = hydrateSpan;
- this.tokens = new Lexer().tokenize(expression.slice(start));
- }
- parse() {
- while (this.tokens.length > 0 && this.index < this.tokens.length) {
- const token = this.token();
- if (!token.isIdentifier()) {
- this.unexpectedToken(token);
- break;
- }
- // An identifier immediately followed by a comma or the end of
- // the expression cannot have parameters so we can exit early.
- if (this.isFollowedByOrLast($COMMA)) {
- this.consumeTrigger(token, []);
- this.advance();
- }
- else if (this.isFollowedByOrLast($LPAREN)) {
- this.advance(); // Advance to the opening paren.
- const prevErrors = this.errors.length;
- const parameters = this.consumeParameters();
- if (this.errors.length !== prevErrors) {
- break;
- }
- this.consumeTrigger(token, parameters);
- this.advance(); // Advance past the closing paren.
- }
- else if (this.index < this.tokens.length - 1) {
- this.unexpectedToken(this.tokens[this.index + 1]);
- }
- this.advance();
- }
- }
- advance() {
- this.index++;
- }
- isFollowedByOrLast(char) {
- if (this.index === this.tokens.length - 1) {
- return true;
- }
- return this.tokens[this.index + 1].isCharacter(char);
- }
- token() {
- return this.tokens[Math.min(this.index, this.tokens.length - 1)];
- }
- consumeTrigger(identifier, parameters) {
- const triggerNameStartSpan = this.span.start.moveBy(this.start + identifier.index - this.tokens[0].index);
- const nameSpan = new ParseSourceSpan(triggerNameStartSpan, triggerNameStartSpan.moveBy(identifier.strValue.length));
- const endSpan = triggerNameStartSpan.moveBy(this.token().end - identifier.index);
- // Put the prefetch and on spans with the first trigger
- // This should maybe be refactored to have something like an outer OnGroup AST
- // Since triggers can be grouped with commas "on hover(x), interaction(y)"
- const isFirstTrigger = identifier.index === 0;
- const onSourceSpan = isFirstTrigger ? this.onSourceSpan : null;
- const prefetchSourceSpan = isFirstTrigger ? this.prefetchSpan : null;
- const hydrateSourceSpan = isFirstTrigger ? this.hydrateSpan : null;
- const sourceSpan = new ParseSourceSpan(isFirstTrigger ? this.span.start : triggerNameStartSpan, endSpan);
- try {
- switch (identifier.toString()) {
- case OnTriggerType.IDLE:
- this.trackTrigger('idle', createIdleTrigger(parameters, nameSpan, sourceSpan, prefetchSourceSpan, onSourceSpan, hydrateSourceSpan));
- break;
- case OnTriggerType.TIMER:
- this.trackTrigger('timer', createTimerTrigger(parameters, nameSpan, sourceSpan, this.prefetchSpan, this.onSourceSpan, this.hydrateSpan));
- break;
- case OnTriggerType.INTERACTION:
- this.trackTrigger('interaction', createInteractionTrigger(parameters, nameSpan, sourceSpan, this.prefetchSpan, this.onSourceSpan, this.hydrateSpan, this.placeholder, this.validator));
- break;
- case OnTriggerType.IMMEDIATE:
- this.trackTrigger('immediate', createImmediateTrigger(parameters, nameSpan, sourceSpan, this.prefetchSpan, this.onSourceSpan, this.hydrateSpan));
- break;
- case OnTriggerType.HOVER:
- this.trackTrigger('hover', createHoverTrigger(parameters, nameSpan, sourceSpan, this.prefetchSpan, this.onSourceSpan, this.hydrateSpan, this.placeholder, this.validator));
- break;
- case OnTriggerType.VIEWPORT:
- this.trackTrigger('viewport', createViewportTrigger(parameters, nameSpan, sourceSpan, this.prefetchSpan, this.onSourceSpan, this.hydrateSpan, this.placeholder, this.validator));
- break;
- default:
- throw new Error(`Unrecognized trigger type "${identifier}"`);
- }
- }
- catch (e) {
- this.error(identifier, e.message);
- }
- }
- consumeParameters() {
- const parameters = [];
- if (!this.token().isCharacter($LPAREN)) {
- this.unexpectedToken(this.token());
- return parameters;
- }
- this.advance();
- const commaDelimStack = [];
- let current = '';
- while (this.index < this.tokens.length) {
- const token = this.token();
- // Stop parsing if we've hit the end character and we're outside of a comma-delimited syntax.
- // Note that we don't need to account for strings here since the lexer already parsed them
- // into string tokens.
- if (token.isCharacter($RPAREN) && commaDelimStack.length === 0) {
- if (current.length) {
- parameters.push(current);
- }
- break;
- }
- // In the `on` microsyntax "top-level" commas (e.g. ones outside of an parameters) separate
- // the different triggers (e.g. `on idle,timer(500)`). This is problematic, because the
- // function-like syntax also implies that multiple parameters can be passed into the
- // individual trigger (e.g. `on foo(a, b)`). To avoid tripping up the parser with commas that
- // are part of other sorts of syntax (object literals, arrays), we treat anything inside
- // a comma-delimited syntax block as plain text.
- if (token.type === TokenType.Character && COMMA_DELIMITED_SYNTAX.has(token.numValue)) {
- commaDelimStack.push(COMMA_DELIMITED_SYNTAX.get(token.numValue));
- }
- if (commaDelimStack.length > 0 &&
- token.isCharacter(commaDelimStack[commaDelimStack.length - 1])) {
- commaDelimStack.pop();
- }
- // If we hit a comma outside of a comma-delimited syntax, it means
- // that we're at the top level and we're starting a new parameter.
- if (commaDelimStack.length === 0 && token.isCharacter($COMMA) && current.length > 0) {
- parameters.push(current);
- current = '';
- this.advance();
- continue;
- }
- // Otherwise treat the token as a plain text character in the current parameter.
- current += this.tokenText();
- this.advance();
- }
- if (!this.token().isCharacter($RPAREN) || commaDelimStack.length > 0) {
- this.error(this.token(), 'Unexpected end of expression');
- }
- if (this.index < this.tokens.length - 1 &&
- !this.tokens[this.index + 1].isCharacter($COMMA)) {
- this.unexpectedToken(this.tokens[this.index + 1]);
- }
- return parameters;
- }
- tokenText() {
- // Tokens have a toString already which we could use, but for string tokens it omits the quotes.
- // Eventually we could expose this information on the token directly.
- return this.expression.slice(this.start + this.token().index, this.start + this.token().end);
- }
- trackTrigger(name, trigger) {
- trackTrigger(name, this.triggers, this.errors, trigger);
- }
- error(token, message) {
- const newStart = this.span.start.moveBy(this.start + token.index);
- const newEnd = newStart.moveBy(token.end - token.index);
- this.errors.push(new ParseError(new ParseSourceSpan(newStart, newEnd), message));
- }
- unexpectedToken(token) {
- this.error(token, `Unexpected token "${token}"`);
- }
- }
- /** Adds a trigger to a map of triggers. */
- function trackTrigger(name, allTriggers, errors, trigger) {
- if (allTriggers[name]) {
- errors.push(new ParseError(trigger.sourceSpan, `Duplicate "${name}" trigger is not allowed`));
- }
- else {
- allTriggers[name] = trigger;
- }
- }
- function createIdleTrigger(parameters, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan) {
- if (parameters.length > 0) {
- throw new Error(`"${OnTriggerType.IDLE}" trigger cannot have parameters`);
- }
- return new IdleDeferredTrigger(nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan);
- }
- function createTimerTrigger(parameters, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan) {
- if (parameters.length !== 1) {
- throw new Error(`"${OnTriggerType.TIMER}" trigger must have exactly one parameter`);
- }
- const delay = parseDeferredTime(parameters[0]);
- if (delay === null) {
- throw new Error(`Could not parse time value of trigger "${OnTriggerType.TIMER}"`);
- }
- return new TimerDeferredTrigger(delay, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan);
- }
- function createImmediateTrigger(parameters, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan) {
- if (parameters.length > 0) {
- throw new Error(`"${OnTriggerType.IMMEDIATE}" trigger cannot have parameters`);
- }
- return new ImmediateDeferredTrigger(nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan);
- }
- function createHoverTrigger(parameters, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan, placeholder, validator) {
- validator(OnTriggerType.HOVER, parameters, placeholder);
- return new HoverDeferredTrigger(parameters[0] ?? null, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan);
- }
- function createInteractionTrigger(parameters, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan, placeholder, validator) {
- validator(OnTriggerType.INTERACTION, parameters, placeholder);
- return new InteractionDeferredTrigger(parameters[0] ?? null, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan);
- }
- function createViewportTrigger(parameters, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan, placeholder, validator) {
- validator(OnTriggerType.VIEWPORT, parameters, placeholder);
- return new ViewportDeferredTrigger(parameters[0] ?? null, nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan);
- }
- /**
- * Checks whether the structure of a non-hydrate reference-based trigger is valid.
- * @param type Type of the trigger being validated.
- * @param parameters Parameters of the trigger.
- * @param placeholder Placeholder of the defer block.
- */
- function validatePlainReferenceBasedTrigger(type, parameters, placeholder) {
- if (parameters.length > 1) {
- throw new Error(`"${type}" trigger can only have zero or one parameters`);
- }
- if (parameters.length === 0) {
- if (placeholder === null) {
- throw new Error(`"${type}" trigger with no parameters can only be placed on an @defer that has a @placeholder block`);
- }
- if (placeholder.children.length !== 1 || !(placeholder.children[0] instanceof Element$1)) {
- throw new Error(`"${type}" trigger with no parameters can only be placed on an @defer that has a ` +
- `@placeholder block with exactly one root element node`);
- }
- }
- }
- /**
- * Checks whether the structure of a hydrate trigger is valid.
- * @param type Type of the trigger being validated.
- * @param parameters Parameters of the trigger.
- */
- function validateHydrateReferenceBasedTrigger(type, parameters) {
- if (parameters.length > 0) {
- throw new Error(`Hydration trigger "${type}" cannot have parameters`);
- }
- }
- /** Gets the index within an expression at which the trigger parameters start. */
- function getTriggerParametersStart(value, startPosition = 0) {
- let hasFoundSeparator = false;
- for (let i = startPosition; i < value.length; i++) {
- if (SEPARATOR_PATTERN.test(value[i])) {
- hasFoundSeparator = true;
- }
- else if (hasFoundSeparator) {
- return i;
- }
- }
- return -1;
- }
- /**
- * Parses a time expression from a deferred trigger to
- * milliseconds. Returns null if it cannot be parsed.
- */
- function parseDeferredTime(value) {
- const match = value.match(TIME_PATTERN);
- if (!match) {
- return null;
- }
- const [time, units] = match;
- return parseFloat(time) * (units === 's' ? 1000 : 1);
- }
- /** Pattern to identify a `prefetch when` trigger. */
- const PREFETCH_WHEN_PATTERN = /^prefetch\s+when\s/;
- /** Pattern to identify a `prefetch on` trigger. */
- const PREFETCH_ON_PATTERN = /^prefetch\s+on\s/;
- /** Pattern to identify a `hydrate when` trigger. */
- const HYDRATE_WHEN_PATTERN = /^hydrate\s+when\s/;
- /** Pattern to identify a `hydrate on` trigger. */
- const HYDRATE_ON_PATTERN = /^hydrate\s+on\s/;
- /** Pattern to identify a `hydrate never` trigger. */
- const HYDRATE_NEVER_PATTERN = /^hydrate\s+never(\s*)$/;
- /** Pattern to identify a `minimum` parameter in a block. */
- const MINIMUM_PARAMETER_PATTERN = /^minimum\s/;
- /** Pattern to identify a `after` parameter in a block. */
- const AFTER_PARAMETER_PATTERN = /^after\s/;
- /** Pattern to identify a `when` parameter in a block. */
- const WHEN_PARAMETER_PATTERN = /^when\s/;
- /** Pattern to identify a `on` parameter in a block. */
- const ON_PARAMETER_PATTERN = /^on\s/;
- /**
- * Predicate function that determines if a block with
- * a specific name cam be connected to a `defer` block.
- */
- function isConnectedDeferLoopBlock(name) {
- return name === 'placeholder' || name === 'loading' || name === 'error';
- }
- /** Creates a deferred block from an HTML AST node. */
- function createDeferredBlock(ast, connectedBlocks, visitor, bindingParser) {
- const errors = [];
- const { placeholder, loading, error } = parseConnectedBlocks(connectedBlocks, errors, visitor);
- const { triggers, prefetchTriggers, hydrateTriggers } = parsePrimaryTriggers(ast, bindingParser, errors, placeholder);
- // The `defer` block has a main span encompassing all of the connected branches as well.
- let lastEndSourceSpan = ast.endSourceSpan;
- let endOfLastSourceSpan = ast.sourceSpan.end;
- if (connectedBlocks.length > 0) {
- const lastConnectedBlock = connectedBlocks[connectedBlocks.length - 1];
- lastEndSourceSpan = lastConnectedBlock.endSourceSpan;
- endOfLastSourceSpan = lastConnectedBlock.sourceSpan.end;
- }
- const sourceSpanWithConnectedBlocks = new ParseSourceSpan(ast.sourceSpan.start, endOfLastSourceSpan);
- const node = new DeferredBlock(visitAll(visitor, ast.children, ast.children), triggers, prefetchTriggers, hydrateTriggers, placeholder, loading, error, ast.nameSpan, sourceSpanWithConnectedBlocks, ast.sourceSpan, ast.startSourceSpan, lastEndSourceSpan, ast.i18n);
- return { node, errors };
- }
- function parseConnectedBlocks(connectedBlocks, errors, visitor) {
- let placeholder = null;
- let loading = null;
- let error = null;
- for (const block of connectedBlocks) {
- try {
- if (!isConnectedDeferLoopBlock(block.name)) {
- errors.push(new ParseError(block.startSourceSpan, `Unrecognized block "@${block.name}"`));
- break;
- }
- switch (block.name) {
- case 'placeholder':
- if (placeholder !== null) {
- errors.push(new ParseError(block.startSourceSpan, `@defer block can only have one @placeholder block`));
- }
- else {
- placeholder = parsePlaceholderBlock(block, visitor);
- }
- break;
- case 'loading':
- if (loading !== null) {
- errors.push(new ParseError(block.startSourceSpan, `@defer block can only have one @loading block`));
- }
- else {
- loading = parseLoadingBlock(block, visitor);
- }
- break;
- case 'error':
- if (error !== null) {
- errors.push(new ParseError(block.startSourceSpan, `@defer block can only have one @error block`));
- }
- else {
- error = parseErrorBlock(block, visitor);
- }
- break;
- }
- }
- catch (e) {
- errors.push(new ParseError(block.startSourceSpan, e.message));
- }
- }
- return { placeholder, loading, error };
- }
- function parsePlaceholderBlock(ast, visitor) {
- let minimumTime = null;
- for (const param of ast.parameters) {
- if (MINIMUM_PARAMETER_PATTERN.test(param.expression)) {
- if (minimumTime != null) {
- throw new Error(`@placeholder block can only have one "minimum" parameter`);
- }
- const parsedTime = parseDeferredTime(param.expression.slice(getTriggerParametersStart(param.expression)));
- if (parsedTime === null) {
- throw new Error(`Could not parse time value of parameter "minimum"`);
- }
- minimumTime = parsedTime;
- }
- else {
- throw new Error(`Unrecognized parameter in @placeholder block: "${param.expression}"`);
- }
- }
- return new DeferredBlockPlaceholder(visitAll(visitor, ast.children, ast.children), minimumTime, ast.nameSpan, ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan, ast.i18n);
- }
- function parseLoadingBlock(ast, visitor) {
- let afterTime = null;
- let minimumTime = null;
- for (const param of ast.parameters) {
- if (AFTER_PARAMETER_PATTERN.test(param.expression)) {
- if (afterTime != null) {
- throw new Error(`@loading block can only have one "after" parameter`);
- }
- const parsedTime = parseDeferredTime(param.expression.slice(getTriggerParametersStart(param.expression)));
- if (parsedTime === null) {
- throw new Error(`Could not parse time value of parameter "after"`);
- }
- afterTime = parsedTime;
- }
- else if (MINIMUM_PARAMETER_PATTERN.test(param.expression)) {
- if (minimumTime != null) {
- throw new Error(`@loading block can only have one "minimum" parameter`);
- }
- const parsedTime = parseDeferredTime(param.expression.slice(getTriggerParametersStart(param.expression)));
- if (parsedTime === null) {
- throw new Error(`Could not parse time value of parameter "minimum"`);
- }
- minimumTime = parsedTime;
- }
- else {
- throw new Error(`Unrecognized parameter in @loading block: "${param.expression}"`);
- }
- }
- return new DeferredBlockLoading(visitAll(visitor, ast.children, ast.children), afterTime, minimumTime, ast.nameSpan, ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan, ast.i18n);
- }
- function parseErrorBlock(ast, visitor) {
- if (ast.parameters.length > 0) {
- throw new Error(`@error block cannot have parameters`);
- }
- return new DeferredBlockError(visitAll(visitor, ast.children, ast.children), ast.nameSpan, ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan, ast.i18n);
- }
- function parsePrimaryTriggers(ast, bindingParser, errors, placeholder) {
- const triggers = {};
- const prefetchTriggers = {};
- const hydrateTriggers = {};
- for (const param of ast.parameters) {
- // The lexer ignores the leading spaces so we can assume
- // that the expression starts with a keyword.
- if (WHEN_PARAMETER_PATTERN.test(param.expression)) {
- parseWhenTrigger(param, bindingParser, triggers, errors);
- }
- else if (ON_PARAMETER_PATTERN.test(param.expression)) {
- parseOnTrigger(param, triggers, errors, placeholder);
- }
- else if (PREFETCH_WHEN_PATTERN.test(param.expression)) {
- parseWhenTrigger(param, bindingParser, prefetchTriggers, errors);
- }
- else if (PREFETCH_ON_PATTERN.test(param.expression)) {
- parseOnTrigger(param, prefetchTriggers, errors, placeholder);
- }
- else if (HYDRATE_WHEN_PATTERN.test(param.expression)) {
- parseWhenTrigger(param, bindingParser, hydrateTriggers, errors);
- }
- else if (HYDRATE_ON_PATTERN.test(param.expression)) {
- parseOnTrigger(param, hydrateTriggers, errors, placeholder);
- }
- else if (HYDRATE_NEVER_PATTERN.test(param.expression)) {
- parseNeverTrigger(param, hydrateTriggers, errors);
- }
- else {
- errors.push(new ParseError(param.sourceSpan, 'Unrecognized trigger'));
- }
- }
- if (hydrateTriggers.never && Object.keys(hydrateTriggers).length > 1) {
- errors.push(new ParseError(ast.startSourceSpan, 'Cannot specify additional `hydrate` triggers if `hydrate never` is present'));
- }
- return { triggers, prefetchTriggers, hydrateTriggers };
- }
- const BIND_NAME_REGEXP = /^(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.*)$/;
- // Group 1 = "bind-"
- const KW_BIND_IDX = 1;
- // Group 2 = "let-"
- const KW_LET_IDX = 2;
- // Group 3 = "ref-/#"
- const KW_REF_IDX = 3;
- // Group 4 = "on-"
- const KW_ON_IDX = 4;
- // Group 5 = "bindon-"
- const KW_BINDON_IDX = 5;
- // Group 6 = "@"
- const KW_AT_IDX = 6;
- // Group 7 = the identifier after "bind-", "let-", "ref-/#", "on-", "bindon-" or "@"
- const IDENT_KW_IDX = 7;
- const BINDING_DELIMS = {
- BANANA_BOX: { start: '[(', end: ')]' },
- PROPERTY: { start: '[', end: ']' },
- EVENT: { start: '(', end: ')' },
- };
- const TEMPLATE_ATTR_PREFIX = '*';
- function htmlAstToRender3Ast(htmlNodes, bindingParser, options) {
- const transformer = new HtmlAstToIvyAst(bindingParser, options);
- const ivyNodes = visitAll(transformer, htmlNodes, htmlNodes);
- // Errors might originate in either the binding parser or the html to ivy transformer
- const allErrors = bindingParser.errors.concat(transformer.errors);
- const result = {
- nodes: ivyNodes,
- errors: allErrors,
- styleUrls: transformer.styleUrls,
- styles: transformer.styles,
- ngContentSelectors: transformer.ngContentSelectors,
- };
- if (options.collectCommentNodes) {
- result.commentNodes = transformer.commentNodes;
- }
- return result;
- }
- class HtmlAstToIvyAst {
- bindingParser;
- options;
- errors = [];
- styles = [];
- styleUrls = [];
- ngContentSelectors = [];
- // This array will be populated if `Render3ParseOptions['collectCommentNodes']` is true
- commentNodes = [];
- inI18nBlock = false;
- /**
- * Keeps track of the nodes that have been processed already when previous nodes were visited.
- * These are typically blocks connected to other blocks or text nodes between connected blocks.
- */
- processedNodes = new Set();
- constructor(bindingParser, options) {
- this.bindingParser = bindingParser;
- this.options = options;
- }
- // HTML visitor
- visitElement(element) {
- const isI18nRootElement = isI18nRootNode(element.i18n);
- if (isI18nRootElement) {
- if (this.inI18nBlock) {
- this.reportError('Cannot mark an element as translatable inside of a translatable section. Please remove the nested i18n marker.', element.sourceSpan);
- }
- this.inI18nBlock = true;
- }
- const preparsedElement = preparseElement(element);
- if (preparsedElement.type === PreparsedElementType.SCRIPT) {
- return null;
- }
- else if (preparsedElement.type === PreparsedElementType.STYLE) {
- const contents = textContents(element);
- if (contents !== null) {
- this.styles.push(contents);
- }
- return null;
- }
- else if (preparsedElement.type === PreparsedElementType.STYLESHEET &&
- isStyleUrlResolvable(preparsedElement.hrefAttr)) {
- this.styleUrls.push(preparsedElement.hrefAttr);
- return null;
- }
- // Whether the element is a `<ng-template>`
- const isTemplateElement = isNgTemplate(element.name);
- const parsedProperties = [];
- const boundEvents = [];
- const variables = [];
- const references = [];
- const attributes = [];
- const i18nAttrsMeta = {};
- const templateParsedProperties = [];
- const templateVariables = [];
- // Whether the element has any *-attribute
- let elementHasInlineTemplate = false;
- for (const attribute of element.attrs) {
- let hasBinding = false;
- const normalizedName = normalizeAttributeName(attribute.name);
- // `*attr` defines template bindings
- let isTemplateBinding = false;
- if (attribute.i18n) {
- i18nAttrsMeta[attribute.name] = attribute.i18n;
- }
- if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
- // *-attributes
- if (elementHasInlineTemplate) {
- this.reportError(`Can't have multiple template bindings on one element. Use only one attribute prefixed with *`, attribute.sourceSpan);
- }
- isTemplateBinding = true;
- elementHasInlineTemplate = true;
- const templateValue = attribute.value;
- const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
- const parsedVariables = [];
- const absoluteValueOffset = attribute.valueSpan
- ? attribute.valueSpan.start.offset
- : // If there is no value span the attribute does not have a value, like `attr` in
- //`<div attr></div>`. In this case, point to one character beyond the last character of
- // the attribute name.
- attribute.sourceSpan.start.offset + attribute.name.length;
- this.bindingParser.parseInlineTemplateBinding(templateKey, templateValue, attribute.sourceSpan, absoluteValueOffset, [], templateParsedProperties, parsedVariables, true /* isIvyAst */);
- templateVariables.push(...parsedVariables.map((v) => new Variable(v.name, v.value, v.sourceSpan, v.keySpan, v.valueSpan)));
- }
- else {
- // Check for variables, events, property bindings, interpolation
- hasBinding = this.parseAttribute(isTemplateElement, attribute, [], parsedProperties, boundEvents, variables, references);
- }
- if (!hasBinding && !isTemplateBinding) {
- // don't include the bindings as attributes as well in the AST
- attributes.push(this.visitAttribute(attribute));
- }
- }
- let children;
- if (preparsedElement.nonBindable) {
- // The `NonBindableVisitor` may need to return an array of nodes for blocks so we need
- // to flatten the array here. Avoid doing this for the `HtmlAstToIvyAst` since `flat` creates
- // a new array.
- children = visitAll(NON_BINDABLE_VISITOR, element.children).flat(Infinity);
- }
- else {
- children = visitAll(this, element.children, element.children);
- }
- let parsedElement;
- if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
- const selector = preparsedElement.selectAttr;
- const attrs = element.attrs.map((attr) => this.visitAttribute(attr));
- parsedElement = new Content(selector, attrs, children, element.sourceSpan, element.i18n);
- this.ngContentSelectors.push(selector);
- }
- else if (isTemplateElement) {
- // `<ng-template>`
- const attrs = this.extractAttributes(element.name, parsedProperties, i18nAttrsMeta);
- parsedElement = new Template(element.name, attributes, attrs.bound, boundEvents, [
- /* no template attributes */
- ], children, references, variables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
- }
- else {
- const attrs = this.extractAttributes(element.name, parsedProperties, i18nAttrsMeta);
- parsedElement = new Element$1(element.name, attributes, attrs.bound, boundEvents, children, references, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
- }
- if (elementHasInlineTemplate) {
- // If this node is an inline-template (e.g. has *ngFor) then we need to create a template
- // node that contains this node.
- // Moreover, if the node is an element, then we need to hoist its attributes to the template
- // node for matching against content projection selectors.
- const attrs = this.extractAttributes('ng-template', templateParsedProperties, i18nAttrsMeta);
- const templateAttrs = [];
- attrs.literal.forEach((attr) => templateAttrs.push(attr));
- attrs.bound.forEach((attr) => templateAttrs.push(attr));
- const hoistedAttrs = parsedElement instanceof Element$1
- ? {
- attributes: parsedElement.attributes,
- inputs: parsedElement.inputs,
- outputs: parsedElement.outputs,
- }
- : { attributes: [], inputs: [], outputs: [] };
- // For <ng-template>s with structural directives on them, avoid passing i18n information to
- // the wrapping template to prevent unnecessary i18n instructions from being generated. The
- // necessary i18n meta information will be extracted from child elements.
- const i18n = isTemplateElement && isI18nRootElement ? undefined : element.i18n;
- const name = parsedElement instanceof Template ? null : parsedElement.name;
- parsedElement = new Template(name, hoistedAttrs.attributes, hoistedAttrs.inputs, hoistedAttrs.outputs, templateAttrs, [parsedElement], [
- /* no references */
- ], templateVariables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, i18n);
- }
- if (isI18nRootElement) {
- this.inI18nBlock = false;
- }
- return parsedElement;
- }
- visitAttribute(attribute) {
- return new TextAttribute(attribute.name, attribute.value, attribute.sourceSpan, attribute.keySpan, attribute.valueSpan, attribute.i18n);
- }
- visitText(text) {
- return this.processedNodes.has(text)
- ? null
- : this._visitTextWithInterpolation(text.value, text.sourceSpan, text.tokens, text.i18n);
- }
- visitExpansion(expansion) {
- if (!expansion.i18n) {
- // do not generate Icu in case it was created
- // outside of i18n block in a template
- return null;
- }
- if (!isI18nRootNode(expansion.i18n)) {
- throw new Error(`Invalid type "${expansion.i18n.constructor}" for "i18n" property of ${expansion.sourceSpan.toString()}. Expected a "Message"`);
- }
- const message = expansion.i18n;
- const vars = {};
- const placeholders = {};
- // extract VARs from ICUs - we process them separately while
- // assembling resulting message via goog.getMsg function, since
- // we need to pass them to top-level goog.getMsg call
- Object.keys(message.placeholders).forEach((key) => {
- const value = message.placeholders[key];
- if (key.startsWith(I18N_ICU_VAR_PREFIX)) {
- // Currently when the `plural` or `select` keywords in an ICU contain trailing spaces (e.g.
- // `{count, select , ...}`), these spaces are also included into the key names in ICU vars
- // (e.g. "VAR_SELECT "). These trailing spaces are not desirable, since they will later be
- // converted into `_` symbols while normalizing placeholder names, which might lead to
- // mismatches at runtime (i.e. placeholder will not be replaced with the correct value).
- const formattedKey = key.trim();
- const ast = this.bindingParser.parseInterpolationExpression(value.text, value.sourceSpan);
- vars[formattedKey] = new BoundText(ast, value.sourceSpan);
- }
- else {
- placeholders[key] = this._visitTextWithInterpolation(value.text, value.sourceSpan, null);
- }
- });
- return new Icu$1(vars, placeholders, expansion.sourceSpan, message);
- }
- visitExpansionCase(expansionCase) {
- return null;
- }
- visitComment(comment) {
- if (this.options.collectCommentNodes) {
- this.commentNodes.push(new Comment$1(comment.value || '', comment.sourceSpan));
- }
- return null;
- }
- visitLetDeclaration(decl, context) {
- const value = this.bindingParser.parseBinding(decl.value, false, decl.valueSpan, decl.valueSpan.start.offset);
- if (value.errors.length === 0 && value.ast instanceof EmptyExpr$1) {
- this.reportError('@let declaration value cannot be empty', decl.valueSpan);
- }
- return new LetDeclaration$1(decl.name, value, decl.sourceSpan, decl.nameSpan, decl.valueSpan);
- }
- visitBlockParameter() {
- return null;
- }
- visitBlock(block, context) {
- const index = Array.isArray(context) ? context.indexOf(block) : -1;
- if (index === -1) {
- throw new Error('Visitor invoked incorrectly. Expecting visitBlock to be invoked siblings array as its context');
- }
- // Connected blocks may have been processed as a part of the previous block.
- if (this.processedNodes.has(block)) {
- return null;
- }
- let result = null;
- switch (block.name) {
- case 'defer':
- result = createDeferredBlock(block, this.findConnectedBlocks(index, context, isConnectedDeferLoopBlock), this, this.bindingParser);
- break;
- case 'switch':
- result = createSwitchBlock(block, this, this.bindingParser);
- break;
- case 'for':
- result = createForLoop(block, this.findConnectedBlocks(index, context, isConnectedForLoopBlock), this, this.bindingParser);
- break;
- case 'if':
- result = createIfBlock(block, this.findConnectedBlocks(index, context, isConnectedIfLoopBlock), this, this.bindingParser);
- break;
- default:
- let errorMessage;
- if (isConnectedDeferLoopBlock(block.name)) {
- errorMessage = `@${block.name} block can only be used after an @defer block.`;
- this.processedNodes.add(block);
- }
- else if (isConnectedForLoopBlock(block.name)) {
- errorMessage = `@${block.name} block can only be used after an @for block.`;
- this.processedNodes.add(block);
- }
- else if (isConnectedIfLoopBlock(block.name)) {
- errorMessage = `@${block.name} block can only be used after an @if or @else if block.`;
- this.processedNodes.add(block);
- }
- else {
- errorMessage = `Unrecognized block @${block.name}.`;
- }
- result = {
- node: new UnknownBlock(block.name, block.sourceSpan, block.nameSpan),
- errors: [new ParseError(block.sourceSpan, errorMessage)],
- };
- break;
- }
- this.errors.push(...result.errors);
- return result.node;
- }
- findConnectedBlocks(primaryBlockIndex, siblings, predicate) {
- const relatedBlocks = [];
- for (let i = primaryBlockIndex + 1; i < siblings.length; i++) {
- const node = siblings[i];
- // Skip over comments.
- if (node instanceof Comment) {
- continue;
- }
- // Ignore empty text nodes between blocks.
- if (node instanceof Text && node.value.trim().length === 0) {
- // Add the text node to the processed nodes since we don't want
- // it to be generated between the connected nodes.
- this.processedNodes.add(node);
- continue;
- }
- // Stop searching as soon as we hit a non-block node or a block that is unrelated.
- if (!(node instanceof Block) || !predicate(node.name)) {
- break;
- }
- relatedBlocks.push(node);
- this.processedNodes.add(node);
- }
- return relatedBlocks;
- }
- // convert view engine `ParsedProperty` to a format suitable for IVY
- extractAttributes(elementName, properties, i18nPropsMeta) {
- const bound = [];
- const literal = [];
- properties.forEach((prop) => {
- const i18n = i18nPropsMeta[prop.name];
- if (prop.isLiteral) {
- literal.push(new TextAttribute(prop.name, prop.expression.source || '', prop.sourceSpan, prop.keySpan, prop.valueSpan, i18n));
- }
- else {
- // Note that validation is skipped and property mapping is disabled
- // due to the fact that we need to make sure a given prop is not an
- // input of a directive and directive matching happens at runtime.
- const bep = this.bindingParser.createBoundElementProperty(elementName, prop,
- /* skipValidation */ true,
- /* mapPropertyName */ false);
- bound.push(BoundAttribute.fromBoundElementProperty(bep, i18n));
- }
- });
- return { bound, literal };
- }
- parseAttribute(isTemplateElement, attribute, matchableAttributes, parsedProperties, boundEvents, variables, references) {
- const name = normalizeAttributeName(attribute.name);
- const value = attribute.value;
- const srcSpan = attribute.sourceSpan;
- const absoluteOffset = attribute.valueSpan
- ? attribute.valueSpan.start.offset
- : srcSpan.start.offset;
- function createKeySpan(srcSpan, prefix, identifier) {
- // We need to adjust the start location for the keySpan to account for the removed 'data-'
- // prefix from `normalizeAttributeName`.
- const normalizationAdjustment = attribute.name.length - name.length;
- const keySpanStart = srcSpan.start.moveBy(prefix.length + normalizationAdjustment);
- const keySpanEnd = keySpanStart.moveBy(identifier.length);
- return new ParseSourceSpan(keySpanStart, keySpanEnd, keySpanStart, identifier);
- }
- const bindParts = name.match(BIND_NAME_REGEXP);
- if (bindParts) {
- if (bindParts[KW_BIND_IDX] != null) {
- const identifier = bindParts[IDENT_KW_IDX];
- const keySpan = createKeySpan(srcSpan, bindParts[KW_BIND_IDX], identifier);
- this.bindingParser.parsePropertyBinding(identifier, value, false, false, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
- }
- else if (bindParts[KW_LET_IDX]) {
- if (isTemplateElement) {
- const identifier = bindParts[IDENT_KW_IDX];
- const keySpan = createKeySpan(srcSpan, bindParts[KW_LET_IDX], identifier);
- this.parseVariable(identifier, value, srcSpan, keySpan, attribute.valueSpan, variables);
- }
- else {
- this.reportError(`"let-" is only supported on ng-template elements.`, srcSpan);
- }
- }
- else if (bindParts[KW_REF_IDX]) {
- const identifier = bindParts[IDENT_KW_IDX];
- const keySpan = createKeySpan(srcSpan, bindParts[KW_REF_IDX], identifier);
- this.parseReference(identifier, value, srcSpan, keySpan, attribute.valueSpan, references);
- }
- else if (bindParts[KW_ON_IDX]) {
- const events = [];
- const identifier = bindParts[IDENT_KW_IDX];
- const keySpan = createKeySpan(srcSpan, bindParts[KW_ON_IDX], identifier);
- this.bindingParser.parseEvent(identifier, value,
- /* isAssignmentEvent */ false, srcSpan, attribute.valueSpan || srcSpan, matchableAttributes, events, keySpan);
- addEvents(events, boundEvents);
- }
- else if (bindParts[KW_BINDON_IDX]) {
- const identifier = bindParts[IDENT_KW_IDX];
- const keySpan = createKeySpan(srcSpan, bindParts[KW_BINDON_IDX], identifier);
- this.bindingParser.parsePropertyBinding(identifier, value, false, true, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
- this.parseAssignmentEvent(identifier, value, srcSpan, attribute.valueSpan, matchableAttributes, boundEvents, keySpan);
- }
- else if (bindParts[KW_AT_IDX]) {
- const keySpan = createKeySpan(srcSpan, '', name);
- this.bindingParser.parseLiteralAttr(name, value, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
- }
- return true;
- }
- // We didn't see a kw-prefixed property binding, but we have not yet checked
- // for the []/()/[()] syntax.
- let delims = null;
- if (name.startsWith(BINDING_DELIMS.BANANA_BOX.start)) {
- delims = BINDING_DELIMS.BANANA_BOX;
- }
- else if (name.startsWith(BINDING_DELIMS.PROPERTY.start)) {
- delims = BINDING_DELIMS.PROPERTY;
- }
- else if (name.startsWith(BINDING_DELIMS.EVENT.start)) {
- delims = BINDING_DELIMS.EVENT;
- }
- if (delims !== null &&
- // NOTE: older versions of the parser would match a start/end delimited
- // binding iff the property name was terminated by the ending delimiter
- // and the identifier in the binding was non-empty.
- // TODO(ayazhafiz): update this to handle malformed bindings.
- name.endsWith(delims.end) &&
- name.length > delims.start.length + delims.end.length) {
- const identifier = name.substring(delims.start.length, name.length - delims.end.length);
- const keySpan = createKeySpan(srcSpan, delims.start, identifier);
- if (delims.start === BINDING_DELIMS.BANANA_BOX.start) {
- this.bindingParser.parsePropertyBinding(identifier, value, false, true, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
- this.parseAssignmentEvent(identifier, value, srcSpan, attribute.valueSpan, matchableAttributes, boundEvents, keySpan);
- }
- else if (delims.start === BINDING_DELIMS.PROPERTY.start) {
- this.bindingParser.parsePropertyBinding(identifier, value, false, false, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
- }
- else {
- const events = [];
- this.bindingParser.parseEvent(identifier, value,
- /* isAssignmentEvent */ false, srcSpan, attribute.valueSpan || srcSpan, matchableAttributes, events, keySpan);
- addEvents(events, boundEvents);
- }
- return true;
- }
- // No explicit binding found.
- const keySpan = createKeySpan(srcSpan, '' /* prefix */, name);
- const hasBinding = this.bindingParser.parsePropertyInterpolation(name, value, srcSpan, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan, attribute.valueTokens ?? null);
- return hasBinding;
- }
- _visitTextWithInterpolation(value, sourceSpan, interpolatedTokens, i18n) {
- const valueNoNgsp = replaceNgsp(value);
- const expr = this.bindingParser.parseInterpolation(valueNoNgsp, sourceSpan, interpolatedTokens);
- return expr ? new BoundText(expr, sourceSpan, i18n) : new Text$3(valueNoNgsp, sourceSpan);
- }
- parseVariable(identifier, value, sourceSpan, keySpan, valueSpan, variables) {
- if (identifier.indexOf('-') > -1) {
- this.reportError(`"-" is not allowed in variable names`, sourceSpan);
- }
- else if (identifier.length === 0) {
- this.reportError(`Variable does not have a name`, sourceSpan);
- }
- variables.push(new Variable(identifier, value, sourceSpan, keySpan, valueSpan));
- }
- parseReference(identifier, value, sourceSpan, keySpan, valueSpan, references) {
- if (identifier.indexOf('-') > -1) {
- this.reportError(`"-" is not allowed in reference names`, sourceSpan);
- }
- else if (identifier.length === 0) {
- this.reportError(`Reference does not have a name`, sourceSpan);
- }
- else if (references.some((reference) => reference.name === identifier)) {
- this.reportError(`Reference "#${identifier}" is defined more than once`, sourceSpan);
- }
- references.push(new Reference$1(identifier, value, sourceSpan, keySpan, valueSpan));
- }
- parseAssignmentEvent(name, expression, sourceSpan, valueSpan, targetMatchableAttrs, boundEvents, keySpan) {
- const events = [];
- this.bindingParser.parseEvent(`${name}Change`, expression,
- /* isAssignmentEvent */ true, sourceSpan, valueSpan || sourceSpan, targetMatchableAttrs, events, keySpan);
- addEvents(events, boundEvents);
- }
- reportError(message, sourceSpan, level = ParseErrorLevel.ERROR) {
- this.errors.push(new ParseError(sourceSpan, message, level));
- }
- }
- class NonBindableVisitor {
- visitElement(ast) {
- const preparsedElement = preparseElement(ast);
- if (preparsedElement.type === PreparsedElementType.SCRIPT ||
- preparsedElement.type === PreparsedElementType.STYLE ||
- preparsedElement.type === PreparsedElementType.STYLESHEET) {
- // Skipping <script> for security reasons
- // Skipping <style> and stylesheets as we already processed them
- // in the StyleCompiler
- return null;
- }
- const children = visitAll(this, ast.children, null);
- return new Element$1(ast.name, visitAll(this, ast.attrs),
- /* inputs */ [],
- /* outputs */ [], children,
- /* references */ [], ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan);
- }
- visitComment(comment) {
- return null;
- }
- visitAttribute(attribute) {
- return new TextAttribute(attribute.name, attribute.value, attribute.sourceSpan, attribute.keySpan, attribute.valueSpan, attribute.i18n);
- }
- visitText(text) {
- return new Text$3(text.value, text.sourceSpan);
- }
- visitExpansion(expansion) {
- return null;
- }
- visitExpansionCase(expansionCase) {
- return null;
- }
- visitBlock(block, context) {
- const nodes = [
- // In an ngNonBindable context we treat the opening/closing tags of block as plain text.
- // This is the as if the `tokenizeBlocks` option was disabled.
- new Text$3(block.startSourceSpan.toString(), block.startSourceSpan),
- ...visitAll(this, block.children),
- ];
- if (block.endSourceSpan !== null) {
- nodes.push(new Text$3(block.endSourceSpan.toString(), block.endSourceSpan));
- }
- return nodes;
- }
- visitBlockParameter(parameter, context) {
- return null;
- }
- visitLetDeclaration(decl, context) {
- return new Text$3(`@let ${decl.name} = ${decl.value};`, decl.sourceSpan);
- }
- }
- const NON_BINDABLE_VISITOR = new NonBindableVisitor();
- function normalizeAttributeName(attrName) {
- return /^data-/i.test(attrName) ? attrName.substring(5) : attrName;
- }
- function addEvents(events, boundEvents) {
- boundEvents.push(...events.map((e) => BoundEvent.fromParsedEvent(e)));
- }
- function textContents(node) {
- if (node.children.length !== 1 || !(node.children[0] instanceof Text)) {
- return null;
- }
- else {
- return node.children[0].value;
- }
- }
- const LEADING_TRIVIA_CHARS = [' ', '\n', '\r', '\t'];
- /**
- * Parse a template into render3 `Node`s and additional metadata, with no other dependencies.
- *
- * @param template text of the template to parse
- * @param templateUrl URL to use for source mapping of the parsed template
- * @param options options to modify how the template is parsed
- */
- function parseTemplate(template, templateUrl, options = {}) {
- const { interpolationConfig, preserveWhitespaces, enableI18nLegacyMessageIdFormat } = options;
- const bindingParser = makeBindingParser(interpolationConfig);
- const htmlParser = new HtmlParser();
- const parseResult = htmlParser.parse(template, templateUrl, {
- leadingTriviaChars: LEADING_TRIVIA_CHARS,
- ...options,
- tokenizeExpansionForms: true,
- tokenizeBlocks: options.enableBlockSyntax ?? true,
- tokenizeLet: options.enableLetSyntax ?? true,
- });
- if (!options.alwaysAttemptHtmlToR3AstConversion &&
- parseResult.errors &&
- parseResult.errors.length > 0) {
- const parsedTemplate = {
- interpolationConfig,
- preserveWhitespaces,
- errors: parseResult.errors,
- nodes: [],
- styleUrls: [],
- styles: [],
- ngContentSelectors: [],
- };
- if (options.collectCommentNodes) {
- parsedTemplate.commentNodes = [];
- }
- return parsedTemplate;
- }
- let rootNodes = parseResult.rootNodes;
- // We need to use the same `retainEmptyTokens` value for both parses to avoid
- // causing a mismatch when reusing source spans, even if the
- // `preserveSignificantWhitespace` behavior is different between the two
- // parses.
- const retainEmptyTokens = !(options.preserveSignificantWhitespace ?? true);
- // process i18n meta information (scan attributes, generate ids)
- // before we run whitespace removal process, because existing i18n
- // extraction process (ng extract-i18n) relies on a raw content to generate
- // message ids
- const i18nMetaVisitor = new I18nMetaVisitor(interpolationConfig,
- /* keepI18nAttrs */ !preserveWhitespaces, enableI18nLegacyMessageIdFormat,
- /* containerBlocks */ undefined, options.preserveSignificantWhitespace, retainEmptyTokens);
- const i18nMetaResult = i18nMetaVisitor.visitAllWithErrors(rootNodes);
- if (!options.alwaysAttemptHtmlToR3AstConversion &&
- i18nMetaResult.errors &&
- i18nMetaResult.errors.length > 0) {
- const parsedTemplate = {
- interpolationConfig,
- preserveWhitespaces,
- errors: i18nMetaResult.errors,
- nodes: [],
- styleUrls: [],
- styles: [],
- ngContentSelectors: [],
- };
- if (options.collectCommentNodes) {
- parsedTemplate.commentNodes = [];
- }
- return parsedTemplate;
- }
- rootNodes = i18nMetaResult.rootNodes;
- if (!preserveWhitespaces) {
- // Always preserve significant whitespace here because this is used to generate the `goog.getMsg`
- // and `$localize` calls which should retain significant whitespace in order to render the
- // correct output. We let this diverge from the message IDs generated earlier which might not
- // have preserved significant whitespace.
- //
- // This should use `visitAllWithSiblings` to set `WhitespaceVisitor` context correctly, however
- // there is an existing bug where significant whitespace is not properly retained in the JS
- // output of leading/trailing whitespace for ICU messages due to the existing lack of context\
- // in `WhitespaceVisitor`. Using `visitAllWithSiblings` here would fix that bug and retain the
- // whitespace, however it would also change the runtime representation which we don't want to do
- // right now.
- rootNodes = visitAll(new WhitespaceVisitor(
- /* preserveSignificantWhitespace */ true,
- /* originalNodeMap */ undefined,
- /* requireContext */ false), rootNodes);
- // run i18n meta visitor again in case whitespaces are removed (because that might affect
- // generated i18n message content) and first pass indicated that i18n content is present in a
- // template. During this pass i18n IDs generated at the first pass will be preserved, so we can
- // mimic existing extraction process (ng extract-i18n)
- if (i18nMetaVisitor.hasI18nMeta) {
- rootNodes = visitAll(new I18nMetaVisitor(interpolationConfig,
- /* keepI18nAttrs */ false,
- /* enableI18nLegacyMessageIdFormat */ undefined,
- /* containerBlocks */ undefined,
- /* preserveSignificantWhitespace */ true, retainEmptyTokens), rootNodes);
- }
- }
- const { nodes, errors, styleUrls, styles, ngContentSelectors, commentNodes } = htmlAstToRender3Ast(rootNodes, bindingParser, { collectCommentNodes: !!options.collectCommentNodes });
- errors.push(...parseResult.errors, ...i18nMetaResult.errors);
- const parsedTemplate = {
- interpolationConfig,
- preserveWhitespaces,
- errors: errors.length > 0 ? errors : null,
- nodes,
- styleUrls,
- styles,
- ngContentSelectors,
- };
- if (options.collectCommentNodes) {
- parsedTemplate.commentNodes = commentNodes;
- }
- return parsedTemplate;
- }
- const elementRegistry = new DomElementSchemaRegistry();
- /**
- * Construct a `BindingParser` with a default configuration.
- */
- function makeBindingParser(interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
- return new BindingParser(new Parser(new Lexer()), interpolationConfig, elementRegistry, []);
- }
- const COMPONENT_VARIABLE = '%COMP%';
- const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
- const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
- function baseDirectiveFields(meta, constantPool, bindingParser) {
- const definitionMap = new DefinitionMap();
- const selectors = parseSelectorToR3Selector(meta.selector);
- // e.g. `type: MyDirective`
- definitionMap.set('type', meta.type.value);
- // e.g. `selectors: [['', 'someDir', '']]`
- if (selectors.length > 0) {
- definitionMap.set('selectors', asLiteral(selectors));
- }
- if (meta.queries.length > 0) {
- // e.g. `contentQueries: (rf, ctx, dirIndex) => { ... }
- definitionMap.set('contentQueries', createContentQueriesFunction(meta.queries, constantPool, meta.name));
- }
- if (meta.viewQueries.length) {
- definitionMap.set('viewQuery', createViewQueriesFunction(meta.viewQueries, constantPool, meta.name));
- }
- // e.g. `hostBindings: (rf, ctx) => { ... }
- definitionMap.set('hostBindings', createHostBindingsFunction(meta.host, meta.typeSourceSpan, bindingParser, constantPool, meta.selector || '', meta.name, definitionMap));
- // e.g 'inputs: {a: 'a'}`
- definitionMap.set('inputs', conditionallyCreateDirectiveBindingLiteral(meta.inputs, true));
- // e.g 'outputs: {a: 'a'}`
- definitionMap.set('outputs', conditionallyCreateDirectiveBindingLiteral(meta.outputs));
- if (meta.exportAs !== null) {
- definitionMap.set('exportAs', literalArr(meta.exportAs.map((e) => literal$1(e))));
- }
- if (meta.isStandalone === false) {
- definitionMap.set('standalone', literal$1(false));
- }
- if (meta.isSignal) {
- definitionMap.set('signals', literal$1(true));
- }
- return definitionMap;
- }
- /**
- * Add features to the definition map.
- */
- function addFeatures(definitionMap, meta) {
- // e.g. `features: [NgOnChangesFeature]`
- const features = [];
- const providers = meta.providers;
- const viewProviders = meta.viewProviders;
- if (providers || viewProviders) {
- const args = [providers || new LiteralArrayExpr([])];
- if (viewProviders) {
- args.push(viewProviders);
- }
- features.push(importExpr(Identifiers.ProvidersFeature).callFn(args));
- }
- // Note: host directives feature needs to be inserted before the
- // inheritance feature to ensure the correct execution order.
- if (meta.hostDirectives?.length) {
- features.push(importExpr(Identifiers.HostDirectivesFeature)
- .callFn([createHostDirectivesFeatureArg(meta.hostDirectives)]));
- }
- if (meta.usesInheritance) {
- features.push(importExpr(Identifiers.InheritDefinitionFeature));
- }
- if (meta.fullInheritance) {
- features.push(importExpr(Identifiers.CopyDefinitionFeature));
- }
- if (meta.lifecycle.usesOnChanges) {
- features.push(importExpr(Identifiers.NgOnChangesFeature));
- }
- if ('externalStyles' in meta && meta.externalStyles?.length) {
- const externalStyleNodes = meta.externalStyles.map((externalStyle) => literal$1(externalStyle));
- features.push(importExpr(Identifiers.ExternalStylesFeature).callFn([literalArr(externalStyleNodes)]));
- }
- if (features.length) {
- definitionMap.set('features', literalArr(features));
- }
- }
- /**
- * Compile a directive for the render3 runtime as defined by the `R3DirectiveMetadata`.
- */
- function compileDirectiveFromMetadata(meta, constantPool, bindingParser) {
- const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
- addFeatures(definitionMap, meta);
- const expression = importExpr(Identifiers.defineDirective)
- .callFn([definitionMap.toLiteralMap()], undefined, true);
- const type = createDirectiveType(meta);
- return { expression, type, statements: [] };
- }
- /**
- * Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`.
- */
- function compileComponentFromMetadata(meta, constantPool, bindingParser) {
- const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
- addFeatures(definitionMap, meta);
- const selector = meta.selector && CssSelector.parse(meta.selector);
- const firstSelector = selector && selector[0];
- // e.g. `attr: ["class", ".my.app"]`
- // This is optional an only included if the first selector of a component specifies attributes.
- if (firstSelector) {
- const selectorAttributes = firstSelector.getAttrs();
- if (selectorAttributes.length) {
- definitionMap.set('attrs', constantPool.getConstLiteral(literalArr(selectorAttributes.map((value) => value != null ? literal$1(value) : literal$1(undefined))),
- /* forceShared */ true));
- }
- }
- // e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
- const templateTypeName = meta.name;
- let allDeferrableDepsFn = null;
- if (meta.defer.mode === 1 /* DeferBlockDepsEmitMode.PerComponent */ &&
- meta.defer.dependenciesFn !== null) {
- const fnName = `${templateTypeName}_DeferFn`;
- constantPool.statements.push(new DeclareVarStmt(fnName, meta.defer.dependenciesFn, undefined, exports.StmtModifier.Final));
- allDeferrableDepsFn = variable(fnName);
- }
- // First the template is ingested into IR:
- const tpl = ingestComponent(meta.name, meta.template.nodes, constantPool, meta.relativeContextFilePath, meta.i18nUseExternalIds, meta.defer, allDeferrableDepsFn, meta.relativeTemplatePath, getTemplateSourceLocationsEnabled());
- // Then the IR is transformed to prepare it for cod egeneration.
- transform(tpl, CompilationJobKind.Tmpl);
- // Finally we emit the template function:
- const templateFn = emitTemplateFn(tpl, constantPool);
- if (tpl.contentSelectors !== null) {
- definitionMap.set('ngContentSelectors', tpl.contentSelectors);
- }
- definitionMap.set('decls', literal$1(tpl.root.decls));
- definitionMap.set('vars', literal$1(tpl.root.vars));
- if (tpl.consts.length > 0) {
- if (tpl.constsInitializers.length > 0) {
- definitionMap.set('consts', arrowFn([], [...tpl.constsInitializers, new ReturnStatement(literalArr(tpl.consts))]));
- }
- else {
- definitionMap.set('consts', literalArr(tpl.consts));
- }
- }
- definitionMap.set('template', templateFn);
- if (meta.declarationListEmitMode !== 3 /* DeclarationListEmitMode.RuntimeResolved */ &&
- meta.declarations.length > 0) {
- definitionMap.set('dependencies', compileDeclarationList(literalArr(meta.declarations.map((decl) => decl.type)), meta.declarationListEmitMode));
- }
- else if (meta.declarationListEmitMode === 3 /* DeclarationListEmitMode.RuntimeResolved */) {
- const args = [meta.type.value];
- if (meta.rawImports) {
- args.push(meta.rawImports);
- }
- definitionMap.set('dependencies', importExpr(Identifiers.getComponentDepsFactory).callFn(args));
- }
- if (meta.encapsulation === null) {
- meta.encapsulation = exports.ViewEncapsulation.Emulated;
- }
- let hasStyles = !!meta.externalStyles?.length;
- // e.g. `styles: [str1, str2]`
- if (meta.styles && meta.styles.length) {
- const styleValues = meta.encapsulation == exports.ViewEncapsulation.Emulated
- ? compileStyles(meta.styles, CONTENT_ATTR, HOST_ATTR)
- : meta.styles;
- const styleNodes = styleValues.reduce((result, style) => {
- if (style.trim().length > 0) {
- result.push(constantPool.getConstLiteral(literal$1(style)));
- }
- return result;
- }, []);
- if (styleNodes.length > 0) {
- hasStyles = true;
- definitionMap.set('styles', literalArr(styleNodes));
- }
- }
- if (!hasStyles && meta.encapsulation === exports.ViewEncapsulation.Emulated) {
- // If there is no style, don't generate css selectors on elements
- meta.encapsulation = exports.ViewEncapsulation.None;
- }
- // Only set view encapsulation if it's not the default value
- if (meta.encapsulation !== exports.ViewEncapsulation.Emulated) {
- definitionMap.set('encapsulation', literal$1(meta.encapsulation));
- }
- // e.g. `animation: [trigger('123', [])]`
- if (meta.animations !== null) {
- definitionMap.set('data', literalMap([{ key: 'animation', value: meta.animations, quoted: false }]));
- }
- // Setting change detection flag
- if (meta.changeDetection !== null) {
- if (typeof meta.changeDetection === 'number' &&
- meta.changeDetection !== exports.ChangeDetectionStrategy.Default) {
- // changeDetection is resolved during analysis. Only set it if not the default.
- definitionMap.set('changeDetection', literal$1(meta.changeDetection));
- }
- else if (typeof meta.changeDetection === 'object') {
- // changeDetection is not resolved during analysis (e.g., we are in local compilation mode).
- // So place it as is.
- definitionMap.set('changeDetection', meta.changeDetection);
- }
- }
- const expression = importExpr(Identifiers.defineComponent)
- .callFn([definitionMap.toLiteralMap()], undefined, true);
- const type = createComponentType(meta);
- return { expression, type, statements: [] };
- }
- /**
- * Creates the type specification from the component meta. This type is inserted into .d.ts files
- * to be consumed by upstream compilations.
- */
- function createComponentType(meta) {
- const typeParams = createBaseDirectiveTypeParams(meta);
- typeParams.push(stringArrayAsType(meta.template.ngContentSelectors));
- typeParams.push(expressionType(literal$1(meta.isStandalone)));
- typeParams.push(createHostDirectivesType(meta));
- // TODO(signals): Always include this metadata starting with v17. Right
- // now Angular v16.0.x does not support this field and library distributions
- // would then be incompatible with v16.0.x framework users.
- if (meta.isSignal) {
- typeParams.push(expressionType(literal$1(meta.isSignal)));
- }
- return expressionType(importExpr(Identifiers.ComponentDeclaration, typeParams));
- }
- /**
- * Compiles the array literal of declarations into an expression according to the provided emit
- * mode.
- */
- function compileDeclarationList(list, mode) {
- switch (mode) {
- case 0 /* DeclarationListEmitMode.Direct */:
- // directives: [MyDir],
- return list;
- case 1 /* DeclarationListEmitMode.Closure */:
- // directives: function () { return [MyDir]; }
- return arrowFn([], list);
- case 2 /* DeclarationListEmitMode.ClosureResolved */:
- // directives: function () { return [MyDir].map(ng.resolveForwardRef); }
- const resolvedList = list.prop('map').callFn([importExpr(Identifiers.resolveForwardRef)]);
- return arrowFn([], resolvedList);
- case 3 /* DeclarationListEmitMode.RuntimeResolved */:
- throw new Error(`Unsupported with an array of pre-resolved dependencies`);
- }
- }
- function stringAsType(str) {
- return expressionType(literal$1(str));
- }
- function stringMapAsLiteralExpression(map) {
- const mapValues = Object.keys(map).map((key) => {
- const value = Array.isArray(map[key]) ? map[key][0] : map[key];
- return {
- key,
- value: literal$1(value),
- quoted: true,
- };
- });
- return literalMap(mapValues);
- }
- function stringArrayAsType(arr) {
- return arr.length > 0
- ? expressionType(literalArr(arr.map((value) => literal$1(value))))
- : NONE_TYPE;
- }
- function createBaseDirectiveTypeParams(meta) {
- // On the type side, remove newlines from the selector as it will need to fit into a TypeScript
- // string literal, which must be on one line.
- const selectorForType = meta.selector !== null ? meta.selector.replace(/\n/g, '') : null;
- return [
- typeWithParameters(meta.type.type, meta.typeArgumentCount),
- selectorForType !== null ? stringAsType(selectorForType) : NONE_TYPE,
- meta.exportAs !== null ? stringArrayAsType(meta.exportAs) : NONE_TYPE,
- expressionType(getInputsTypeExpression(meta)),
- expressionType(stringMapAsLiteralExpression(meta.outputs)),
- stringArrayAsType(meta.queries.map((q) => q.propertyName)),
- ];
- }
- function getInputsTypeExpression(meta) {
- return literalMap(Object.keys(meta.inputs).map((key) => {
- const value = meta.inputs[key];
- const values = [
- { key: 'alias', value: literal$1(value.bindingPropertyName), quoted: true },
- { key: 'required', value: literal$1(value.required), quoted: true },
- ];
- // TODO(legacy-partial-output-inputs): Consider always emitting this information,
- // or leaving it as is.
- if (value.isSignal) {
- values.push({ key: 'isSignal', value: literal$1(value.isSignal), quoted: true });
- }
- return { key, value: literalMap(values), quoted: true };
- }));
- }
- /**
- * Creates the type specification from the directive meta. This type is inserted into .d.ts files
- * to be consumed by upstream compilations.
- */
- function createDirectiveType(meta) {
- const typeParams = createBaseDirectiveTypeParams(meta);
- // Directives have no NgContentSelectors slot, but instead express a `never` type
- // so that future fields align.
- typeParams.push(NONE_TYPE);
- typeParams.push(expressionType(literal$1(meta.isStandalone)));
- typeParams.push(createHostDirectivesType(meta));
- // TODO(signals): Always include this metadata starting with v17. Right
- // now Angular v16.0.x does not support this field and library distributions
- // would then be incompatible with v16.0.x framework users.
- if (meta.isSignal) {
- typeParams.push(expressionType(literal$1(meta.isSignal)));
- }
- return expressionType(importExpr(Identifiers.DirectiveDeclaration, typeParams));
- }
- // Return a host binding function or null if one is not necessary.
- function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindingParser, constantPool, selector, name, definitionMap) {
- const bindings = bindingParser.createBoundHostProperties(hostBindingsMetadata.properties, typeSourceSpan);
- // Calculate host event bindings
- const eventBindings = bindingParser.createDirectiveHostEventAsts(hostBindingsMetadata.listeners, typeSourceSpan);
- // The parser for host bindings treats class and style attributes specially -- they are
- // extracted into these separate fields. This is not the case for templates, so the compiler can
- // actually already handle these special attributes internally. Therefore, we just drop them
- // into the attributes map.
- if (hostBindingsMetadata.specialAttributes.styleAttr) {
- hostBindingsMetadata.attributes['style'] = literal$1(hostBindingsMetadata.specialAttributes.styleAttr);
- }
- if (hostBindingsMetadata.specialAttributes.classAttr) {
- hostBindingsMetadata.attributes['class'] = literal$1(hostBindingsMetadata.specialAttributes.classAttr);
- }
- const hostJob = ingestHostBinding({
- componentName: name,
- componentSelector: selector,
- properties: bindings,
- events: eventBindings,
- attributes: hostBindingsMetadata.attributes,
- }, bindingParser, constantPool);
- transform(hostJob, CompilationJobKind.Host);
- definitionMap.set('hostAttrs', hostJob.root.attributes);
- const varCount = hostJob.root.vars;
- if (varCount !== null && varCount > 0) {
- definitionMap.set('hostVars', literal$1(varCount));
- }
- return emitHostBindingFunction(hostJob);
- }
- const HOST_REG_EXP = /^(?:\[([^\]]+)\])|(?:\(([^\)]+)\))$/;
- function parseHostBindings(host) {
- const attributes = {};
- const listeners = {};
- const properties = {};
- const specialAttributes = {};
- for (const key of Object.keys(host)) {
- const value = host[key];
- const matches = key.match(HOST_REG_EXP);
- if (matches === null) {
- switch (key) {
- case 'class':
- if (typeof value !== 'string') {
- // TODO(alxhub): make this a diagnostic.
- throw new Error(`Class binding must be string`);
- }
- specialAttributes.classAttr = value;
- break;
- case 'style':
- if (typeof value !== 'string') {
- // TODO(alxhub): make this a diagnostic.
- throw new Error(`Style binding must be string`);
- }
- specialAttributes.styleAttr = value;
- break;
- default:
- if (typeof value === 'string') {
- attributes[key] = literal$1(value);
- }
- else {
- attributes[key] = value;
- }
- }
- }
- else if (matches[1 /* HostBindingGroup.Binding */] != null) {
- if (typeof value !== 'string') {
- // TODO(alxhub): make this a diagnostic.
- throw new Error(`Property binding must be string`);
- }
- // synthetic properties (the ones that have a `@` as a prefix)
- // are still treated the same as regular properties. Therefore
- // there is no point in storing them in a separate map.
- properties[matches[1 /* HostBindingGroup.Binding */]] = value;
- }
- else if (matches[2 /* HostBindingGroup.Event */] != null) {
- if (typeof value !== 'string') {
- // TODO(alxhub): make this a diagnostic.
- throw new Error(`Event binding must be string`);
- }
- listeners[matches[2 /* HostBindingGroup.Event */]] = value;
- }
- }
- return { attributes, listeners, properties, specialAttributes };
- }
- /**
- * Verifies host bindings and returns the list of errors (if any). Empty array indicates that a
- * given set of host bindings has no errors.
- *
- * @param bindings set of host bindings to verify.
- * @param sourceSpan source span where host bindings were defined.
- * @returns array of errors associated with a given set of host bindings.
- */
- function verifyHostBindings(bindings, sourceSpan) {
- // TODO: abstract out host bindings verification logic and use it instead of
- // creating events and properties ASTs to detect errors (FW-996)
- const bindingParser = makeBindingParser();
- bindingParser.createDirectiveHostEventAsts(bindings.listeners, sourceSpan);
- bindingParser.createBoundHostProperties(bindings.properties, sourceSpan);
- return bindingParser.errors;
- }
- function compileStyles(styles, selector, hostSelector) {
- const shadowCss = new ShadowCss();
- return styles.map((style) => {
- return shadowCss.shimCssText(style, selector, hostSelector);
- });
- }
- function createHostDirectivesType(meta) {
- if (!meta.hostDirectives?.length) {
- return NONE_TYPE;
- }
- return expressionType(literalArr(meta.hostDirectives.map((hostMeta) => literalMap([
- { key: 'directive', value: typeofExpr(hostMeta.directive.type), quoted: false },
- {
- key: 'inputs',
- value: stringMapAsLiteralExpression(hostMeta.inputs || {}),
- quoted: false,
- },
- {
- key: 'outputs',
- value: stringMapAsLiteralExpression(hostMeta.outputs || {}),
- quoted: false,
- },
- ]))));
- }
- function createHostDirectivesFeatureArg(hostDirectives) {
- const expressions = [];
- let hasForwardRef = false;
- for (const current of hostDirectives) {
- // Use a shorthand if there are no inputs or outputs.
- if (!current.inputs && !current.outputs) {
- expressions.push(current.directive.type);
- }
- else {
- const keys = [{ key: 'directive', value: current.directive.type, quoted: false }];
- if (current.inputs) {
- const inputsLiteral = createHostDirectivesMappingArray(current.inputs);
- if (inputsLiteral) {
- keys.push({ key: 'inputs', value: inputsLiteral, quoted: false });
- }
- }
- if (current.outputs) {
- const outputsLiteral = createHostDirectivesMappingArray(current.outputs);
- if (outputsLiteral) {
- keys.push({ key: 'outputs', value: outputsLiteral, quoted: false });
- }
- }
- expressions.push(literalMap(keys));
- }
- if (current.isForwardReference) {
- hasForwardRef = true;
- }
- }
- // If there's a forward reference, we generate a `function() { return [HostDir] }`,
- // otherwise we can save some bytes by using a plain array, e.g. `[HostDir]`.
- return hasForwardRef
- ? new FunctionExpr([], [new ReturnStatement(literalArr(expressions))])
- : literalArr(expressions);
- }
- /**
- * Converts an input/output mapping object literal into an array where the even keys are the
- * public name of the binding and the odd ones are the name it was aliased to. E.g.
- * `{inputOne: 'aliasOne', inputTwo: 'aliasTwo'}` will become
- * `['inputOne', 'aliasOne', 'inputTwo', 'aliasTwo']`.
- *
- * This conversion is necessary, because hosts bind to the public name of the host directive and
- * keeping the mapping in an object literal will break for apps using property renaming.
- */
- function createHostDirectivesMappingArray(mapping) {
- const elements = [];
- for (const publicName in mapping) {
- if (mapping.hasOwnProperty(publicName)) {
- elements.push(literal$1(publicName), literal$1(mapping[publicName]));
- }
- }
- return elements.length > 0 ? literalArr(elements) : null;
- }
- /**
- * Compiles the dependency resolver function for a defer block.
- */
- function compileDeferResolverFunction(meta) {
- const depExpressions = [];
- if (meta.mode === 0 /* DeferBlockDepsEmitMode.PerBlock */) {
- for (const dep of meta.dependencies) {
- if (dep.isDeferrable) {
- // Callback function, e.g. `m () => m.MyCmp;`.
- const innerFn = arrowFn(
- // Default imports are always accessed through the `default` property.
- [new FnParam('m', DYNAMIC_TYPE)], variable('m').prop(dep.isDefaultImport ? 'default' : dep.symbolName));
- // Dynamic import, e.g. `import('./a').then(...)`.
- const importExpr = new DynamicImportExpr(dep.importPath).prop('then').callFn([innerFn]);
- depExpressions.push(importExpr);
- }
- else {
- // Non-deferrable symbol, just use a reference to the type. Note that it's important to
- // go through `typeReference`, rather than `symbolName` in order to preserve the
- // original reference within the source file.
- depExpressions.push(dep.typeReference);
- }
- }
- }
- else {
- for (const { symbolName, importPath, isDefaultImport } of meta.dependencies) {
- // Callback function, e.g. `m () => m.MyCmp;`.
- const innerFn = arrowFn([new FnParam('m', DYNAMIC_TYPE)], variable('m').prop(isDefaultImport ? 'default' : symbolName));
- // Dynamic import, e.g. `import('./a').then(...)`.
- const importExpr = new DynamicImportExpr(importPath).prop('then').callFn([innerFn]);
- depExpressions.push(importExpr);
- }
- }
- return arrowFn([], literalArr(depExpressions));
- }
- /**
- * Processes `Target`s with a given set of directives and performs a binding operation, which
- * returns an object similar to TypeScript's `ts.TypeChecker` that contains knowledge about the
- * target.
- */
- class R3TargetBinder {
- directiveMatcher;
- constructor(directiveMatcher) {
- this.directiveMatcher = directiveMatcher;
- }
- /**
- * Perform a binding operation on the given `Target` and return a `BoundTarget` which contains
- * metadata about the types referenced in the template.
- */
- bind(target) {
- if (!target.template) {
- throw new Error('Empty bound targets are not supported');
- }
- const directives = new Map();
- const eagerDirectives = [];
- const bindings = new Map();
- const references = new Map();
- const scopedNodeEntities = new Map();
- const expressions = new Map();
- const symbols = new Map();
- const nestingLevel = new Map();
- const usedPipes = new Set();
- const eagerPipes = new Set();
- const deferBlocks = [];
- if (target.template) {
- // First, parse the template into a `Scope` structure. This operation captures the syntactic
- // scopes in the template and makes them available for later use.
- const scope = Scope$1.apply(target.template);
- // Use the `Scope` to extract the entities present at every level of the template.
- extractScopedNodeEntities(scope, scopedNodeEntities);
- // Next, perform directive matching on the template using the `DirectiveBinder`. This returns:
- // - directives: Map of nodes (elements & ng-templates) to the directives on them.
- // - bindings: Map of inputs, outputs, and attributes to the directive/element that claims
- // them. TODO(alxhub): handle multiple directives claiming an input/output/etc.
- // - references: Map of #references to their targets.
- DirectiveBinder.apply(target.template, this.directiveMatcher, directives, eagerDirectives, bindings, references);
- // Finally, run the TemplateBinder to bind references, variables, and other entities within the
- // template. This extracts all the metadata that doesn't depend on directive matching.
- TemplateBinder.applyWithScope(target.template, scope, expressions, symbols, nestingLevel, usedPipes, eagerPipes, deferBlocks);
- }
- return new R3BoundTarget(target, directives, eagerDirectives, bindings, references, expressions, symbols, nestingLevel, scopedNodeEntities, usedPipes, eagerPipes, deferBlocks);
- }
- }
- /**
- * Represents a binding scope within a template.
- *
- * Any variables, references, or other named entities declared within the template will
- * be captured and available by name in `namedEntities`. Additionally, child templates will
- * be analyzed and have their child `Scope`s available in `childScopes`.
- */
- let Scope$1 = class Scope {
- parentScope;
- rootNode;
- /**
- * Named members of the `Scope`, such as `Reference`s or `Variable`s.
- */
- namedEntities = new Map();
- /**
- * Set of elements that belong to this scope.
- */
- elementsInScope = new Set();
- /**
- * Child `Scope`s for immediately nested `ScopedNode`s.
- */
- childScopes = new Map();
- /** Whether this scope is deferred or if any of its ancestors are deferred. */
- isDeferred;
- constructor(parentScope, rootNode) {
- this.parentScope = parentScope;
- this.rootNode = rootNode;
- this.isDeferred =
- parentScope !== null && parentScope.isDeferred ? true : rootNode instanceof DeferredBlock;
- }
- static newRootScope() {
- return new Scope(null, null);
- }
- /**
- * Process a template (either as a `Template` sub-template with variables, or a plain array of
- * template `Node`s) and construct its `Scope`.
- */
- static apply(template) {
- const scope = Scope.newRootScope();
- scope.ingest(template);
- return scope;
- }
- /**
- * Internal method to process the scoped node and populate the `Scope`.
- */
- ingest(nodeOrNodes) {
- if (nodeOrNodes instanceof Template) {
- // Variables on an <ng-template> are defined in the inner scope.
- nodeOrNodes.variables.forEach((node) => this.visitVariable(node));
- // Process the nodes of the template.
- nodeOrNodes.children.forEach((node) => node.visit(this));
- }
- else if (nodeOrNodes instanceof IfBlockBranch) {
- if (nodeOrNodes.expressionAlias !== null) {
- this.visitVariable(nodeOrNodes.expressionAlias);
- }
- nodeOrNodes.children.forEach((node) => node.visit(this));
- }
- else if (nodeOrNodes instanceof ForLoopBlock) {
- this.visitVariable(nodeOrNodes.item);
- nodeOrNodes.contextVariables.forEach((v) => this.visitVariable(v));
- nodeOrNodes.children.forEach((node) => node.visit(this));
- }
- else if (nodeOrNodes instanceof SwitchBlockCase ||
- nodeOrNodes instanceof ForLoopBlockEmpty ||
- nodeOrNodes instanceof DeferredBlock ||
- nodeOrNodes instanceof DeferredBlockError ||
- nodeOrNodes instanceof DeferredBlockPlaceholder ||
- nodeOrNodes instanceof DeferredBlockLoading ||
- nodeOrNodes instanceof Content) {
- nodeOrNodes.children.forEach((node) => node.visit(this));
- }
- else {
- // No overarching `Template` instance, so process the nodes directly.
- nodeOrNodes.forEach((node) => node.visit(this));
- }
- }
- visitElement(element) {
- // `Element`s in the template may have `Reference`s which are captured in the scope.
- element.references.forEach((node) => this.visitReference(node));
- // Recurse into the `Element`'s children.
- element.children.forEach((node) => node.visit(this));
- this.elementsInScope.add(element);
- }
- visitTemplate(template) {
- // References on a <ng-template> are defined in the outer scope, so capture them before
- // processing the template's child scope.
- template.references.forEach((node) => this.visitReference(node));
- // Next, create an inner scope and process the template within it.
- this.ingestScopedNode(template);
- }
- visitVariable(variable) {
- // Declare the variable if it's not already.
- this.maybeDeclare(variable);
- }
- visitReference(reference) {
- // Declare the variable if it's not already.
- this.maybeDeclare(reference);
- }
- visitDeferredBlock(deferred) {
- this.ingestScopedNode(deferred);
- deferred.placeholder?.visit(this);
- deferred.loading?.visit(this);
- deferred.error?.visit(this);
- }
- visitDeferredBlockPlaceholder(block) {
- this.ingestScopedNode(block);
- }
- visitDeferredBlockError(block) {
- this.ingestScopedNode(block);
- }
- visitDeferredBlockLoading(block) {
- this.ingestScopedNode(block);
- }
- visitSwitchBlock(block) {
- block.cases.forEach((node) => node.visit(this));
- }
- visitSwitchBlockCase(block) {
- this.ingestScopedNode(block);
- }
- visitForLoopBlock(block) {
- this.ingestScopedNode(block);
- block.empty?.visit(this);
- }
- visitForLoopBlockEmpty(block) {
- this.ingestScopedNode(block);
- }
- visitIfBlock(block) {
- block.branches.forEach((node) => node.visit(this));
- }
- visitIfBlockBranch(block) {
- this.ingestScopedNode(block);
- }
- visitContent(content) {
- this.ingestScopedNode(content);
- }
- visitLetDeclaration(decl) {
- this.maybeDeclare(decl);
- }
- // Unused visitors.
- visitBoundAttribute(attr) { }
- visitBoundEvent(event) { }
- visitBoundText(text) { }
- visitText(text) { }
- visitTextAttribute(attr) { }
- visitIcu(icu) { }
- visitDeferredTrigger(trigger) { }
- visitUnknownBlock(block) { }
- maybeDeclare(thing) {
- // Declare something with a name, as long as that name isn't taken.
- if (!this.namedEntities.has(thing.name)) {
- this.namedEntities.set(thing.name, thing);
- }
- }
- /**
- * Look up a variable within this `Scope`.
- *
- * This can recurse into a parent `Scope` if it's available.
- */
- lookup(name) {
- if (this.namedEntities.has(name)) {
- // Found in the local scope.
- return this.namedEntities.get(name);
- }
- else if (this.parentScope !== null) {
- // Not in the local scope, but there's a parent scope so check there.
- return this.parentScope.lookup(name);
- }
- else {
- // At the top level and it wasn't found.
- return null;
- }
- }
- /**
- * Get the child scope for a `ScopedNode`.
- *
- * This should always be defined.
- */
- getChildScope(node) {
- const res = this.childScopes.get(node);
- if (res === undefined) {
- throw new Error(`Assertion error: child scope for ${node} not found`);
- }
- return res;
- }
- ingestScopedNode(node) {
- const scope = new Scope(this, node);
- scope.ingest(node);
- this.childScopes.set(node, scope);
- }
- };
- /**
- * Processes a template and matches directives on nodes (elements and templates).
- *
- * Usually used via the static `apply()` method.
- */
- class DirectiveBinder {
- matcher;
- directives;
- eagerDirectives;
- bindings;
- references;
- // Indicates whether we are visiting elements within a `defer` block
- isInDeferBlock = false;
- constructor(matcher, directives, eagerDirectives, bindings, references) {
- this.matcher = matcher;
- this.directives = directives;
- this.eagerDirectives = eagerDirectives;
- this.bindings = bindings;
- this.references = references;
- }
- /**
- * Process a template (list of `Node`s) and perform directive matching against each node.
- *
- * @param template the list of template `Node`s to match (recursively).
- * @param selectorMatcher a `SelectorMatcher` containing the directives that are in scope for
- * this template.
- * @returns three maps which contain information about directives in the template: the
- * `directives` map which lists directives matched on each node, the `bindings` map which
- * indicates which directives claimed which bindings (inputs, outputs, etc), and the `references`
- * map which resolves #references (`Reference`s) within the template to the named directive or
- * template node.
- */
- static apply(template, selectorMatcher, directives, eagerDirectives, bindings, references) {
- const matcher = new DirectiveBinder(selectorMatcher, directives, eagerDirectives, bindings, references);
- matcher.ingest(template);
- }
- ingest(template) {
- template.forEach((node) => node.visit(this));
- }
- visitElement(element) {
- this.visitElementOrTemplate(element);
- }
- visitTemplate(template) {
- this.visitElementOrTemplate(template);
- }
- visitElementOrTemplate(node) {
- // First, determine the HTML shape of the node for the purpose of directive matching.
- // Do this by building up a `CssSelector` for the node.
- const cssSelector = createCssSelectorFromNode(node);
- // Next, use the `SelectorMatcher` to get the list of directives on the node.
- const directives = [];
- this.matcher.match(cssSelector, (_selector, results) => directives.push(...results));
- if (directives.length > 0) {
- this.directives.set(node, directives);
- if (!this.isInDeferBlock) {
- this.eagerDirectives.push(...directives);
- }
- }
- // Resolve any references that are created on this node.
- node.references.forEach((ref) => {
- let dirTarget = null;
- // If the reference expression is empty, then it matches the "primary" directive on the node
- // (if there is one). Otherwise it matches the host node itself (either an element or
- // <ng-template> node).
- if (ref.value.trim() === '') {
- // This could be a reference to a component if there is one.
- dirTarget = directives.find((dir) => dir.isComponent) || null;
- }
- else {
- // This should be a reference to a directive exported via exportAs.
- dirTarget =
- directives.find((dir) => dir.exportAs !== null && dir.exportAs.some((value) => value === ref.value)) || null;
- // Check if a matching directive was found.
- if (dirTarget === null) {
- // No matching directive was found - this reference points to an unknown target. Leave it
- // unmapped.
- return;
- }
- }
- if (dirTarget !== null) {
- // This reference points to a directive.
- this.references.set(ref, { directive: dirTarget, node });
- }
- else {
- // This reference points to the node itself.
- this.references.set(ref, node);
- }
- });
- const setAttributeBinding = (attribute, ioType) => {
- const dir = directives.find((dir) => dir[ioType].hasBindingPropertyName(attribute.name));
- const binding = dir !== undefined ? dir : node;
- this.bindings.set(attribute, binding);
- };
- // Node inputs (bound attributes) and text attributes can be bound to an
- // input on a directive.
- node.inputs.forEach((input) => setAttributeBinding(input, 'inputs'));
- node.attributes.forEach((attr) => setAttributeBinding(attr, 'inputs'));
- if (node instanceof Template) {
- node.templateAttrs.forEach((attr) => setAttributeBinding(attr, 'inputs'));
- }
- // Node outputs (bound events) can be bound to an output on a directive.
- node.outputs.forEach((output) => setAttributeBinding(output, 'outputs'));
- // Recurse into the node's children.
- node.children.forEach((child) => child.visit(this));
- }
- visitDeferredBlock(deferred) {
- const wasInDeferBlock = this.isInDeferBlock;
- this.isInDeferBlock = true;
- deferred.children.forEach((child) => child.visit(this));
- this.isInDeferBlock = wasInDeferBlock;
- deferred.placeholder?.visit(this);
- deferred.loading?.visit(this);
- deferred.error?.visit(this);
- }
- visitDeferredBlockPlaceholder(block) {
- block.children.forEach((child) => child.visit(this));
- }
- visitDeferredBlockError(block) {
- block.children.forEach((child) => child.visit(this));
- }
- visitDeferredBlockLoading(block) {
- block.children.forEach((child) => child.visit(this));
- }
- visitSwitchBlock(block) {
- block.cases.forEach((node) => node.visit(this));
- }
- visitSwitchBlockCase(block) {
- block.children.forEach((node) => node.visit(this));
- }
- visitForLoopBlock(block) {
- block.item.visit(this);
- block.contextVariables.forEach((v) => v.visit(this));
- block.children.forEach((node) => node.visit(this));
- block.empty?.visit(this);
- }
- visitForLoopBlockEmpty(block) {
- block.children.forEach((node) => node.visit(this));
- }
- visitIfBlock(block) {
- block.branches.forEach((node) => node.visit(this));
- }
- visitIfBlockBranch(block) {
- block.expressionAlias?.visit(this);
- block.children.forEach((node) => node.visit(this));
- }
- visitContent(content) {
- content.children.forEach((child) => child.visit(this));
- }
- // Unused visitors.
- visitVariable(variable) { }
- visitReference(reference) { }
- visitTextAttribute(attribute) { }
- visitBoundAttribute(attribute) { }
- visitBoundEvent(attribute) { }
- visitBoundAttributeOrEvent(node) { }
- visitText(text) { }
- visitBoundText(text) { }
- visitIcu(icu) { }
- visitDeferredTrigger(trigger) { }
- visitUnknownBlock(block) { }
- visitLetDeclaration(decl) { }
- }
- /**
- * Processes a template and extract metadata about expressions and symbols within.
- *
- * This is a companion to the `DirectiveBinder` that doesn't require knowledge of directives matched
- * within the template in order to operate.
- *
- * Expressions are visited by the superclass `RecursiveAstVisitor`, with custom logic provided
- * by overridden methods from that visitor.
- */
- class TemplateBinder extends RecursiveAstVisitor {
- bindings;
- symbols;
- usedPipes;
- eagerPipes;
- deferBlocks;
- nestingLevel;
- scope;
- rootNode;
- level;
- visitNode;
- constructor(bindings, symbols, usedPipes, eagerPipes, deferBlocks, nestingLevel, scope, rootNode, level) {
- super();
- this.bindings = bindings;
- this.symbols = symbols;
- this.usedPipes = usedPipes;
- this.eagerPipes = eagerPipes;
- this.deferBlocks = deferBlocks;
- this.nestingLevel = nestingLevel;
- this.scope = scope;
- this.rootNode = rootNode;
- this.level = level;
- // Save a bit of processing time by constructing this closure in advance.
- this.visitNode = (node) => node.visit(this);
- }
- // This method is defined to reconcile the type of TemplateBinder since both
- // RecursiveAstVisitor and Visitor define the visit() method in their
- // interfaces.
- visit(node, context) {
- if (node instanceof AST) {
- node.visit(this, context);
- }
- else {
- node.visit(this);
- }
- }
- /**
- * Process a template and extract metadata about expressions and symbols within.
- *
- * @param nodes the nodes of the template to process
- * @param scope the `Scope` of the template being processed.
- * @returns three maps which contain metadata about the template: `expressions` which interprets
- * special `AST` nodes in expressions as pointing to references or variables declared within the
- * template, `symbols` which maps those variables and references to the nested `Template` which
- * declares them, if any, and `nestingLevel` which associates each `Template` with a integer
- * nesting level (how many levels deep within the template structure the `Template` is), starting
- * at 1.
- */
- static applyWithScope(nodes, scope, expressions, symbols, nestingLevel, usedPipes, eagerPipes, deferBlocks) {
- const template = nodes instanceof Template ? nodes : null;
- // The top-level template has nesting level 0.
- const binder = new TemplateBinder(expressions, symbols, usedPipes, eagerPipes, deferBlocks, nestingLevel, scope, template, 0);
- binder.ingest(nodes);
- }
- ingest(nodeOrNodes) {
- if (nodeOrNodes instanceof Template) {
- // For <ng-template>s, process only variables and child nodes. Inputs, outputs, templateAttrs,
- // and references were all processed in the scope of the containing template.
- nodeOrNodes.variables.forEach(this.visitNode);
- nodeOrNodes.children.forEach(this.visitNode);
- // Set the nesting level.
- this.nestingLevel.set(nodeOrNodes, this.level);
- }
- else if (nodeOrNodes instanceof IfBlockBranch) {
- if (nodeOrNodes.expressionAlias !== null) {
- this.visitNode(nodeOrNodes.expressionAlias);
- }
- nodeOrNodes.children.forEach(this.visitNode);
- this.nestingLevel.set(nodeOrNodes, this.level);
- }
- else if (nodeOrNodes instanceof ForLoopBlock) {
- this.visitNode(nodeOrNodes.item);
- nodeOrNodes.contextVariables.forEach((v) => this.visitNode(v));
- nodeOrNodes.trackBy.visit(this);
- nodeOrNodes.children.forEach(this.visitNode);
- this.nestingLevel.set(nodeOrNodes, this.level);
- }
- else if (nodeOrNodes instanceof DeferredBlock) {
- if (this.scope.rootNode !== nodeOrNodes) {
- throw new Error(`Assertion error: resolved incorrect scope for deferred block ${nodeOrNodes}`);
- }
- this.deferBlocks.push([nodeOrNodes, this.scope]);
- nodeOrNodes.children.forEach((node) => node.visit(this));
- this.nestingLevel.set(nodeOrNodes, this.level);
- }
- else if (nodeOrNodes instanceof SwitchBlockCase ||
- nodeOrNodes instanceof ForLoopBlockEmpty ||
- nodeOrNodes instanceof DeferredBlockError ||
- nodeOrNodes instanceof DeferredBlockPlaceholder ||
- nodeOrNodes instanceof DeferredBlockLoading ||
- nodeOrNodes instanceof Content) {
- nodeOrNodes.children.forEach((node) => node.visit(this));
- this.nestingLevel.set(nodeOrNodes, this.level);
- }
- else {
- // Visit each node from the top-level template.
- nodeOrNodes.forEach(this.visitNode);
- }
- }
- visitElement(element) {
- // Visit the inputs, outputs, and children of the element.
- element.inputs.forEach(this.visitNode);
- element.outputs.forEach(this.visitNode);
- element.children.forEach(this.visitNode);
- element.references.forEach(this.visitNode);
- }
- visitTemplate(template) {
- // First, visit inputs, outputs and template attributes of the template node.
- template.inputs.forEach(this.visitNode);
- template.outputs.forEach(this.visitNode);
- template.templateAttrs.forEach(this.visitNode);
- template.references.forEach(this.visitNode);
- // Next, recurse into the template.
- this.ingestScopedNode(template);
- }
- visitVariable(variable) {
- // Register the `Variable` as a symbol in the current `Template`.
- if (this.rootNode !== null) {
- this.symbols.set(variable, this.rootNode);
- }
- }
- visitReference(reference) {
- // Register the `Reference` as a symbol in the current `Template`.
- if (this.rootNode !== null) {
- this.symbols.set(reference, this.rootNode);
- }
- }
- // Unused template visitors
- visitText(text) { }
- visitTextAttribute(attribute) { }
- visitUnknownBlock(block) { }
- visitDeferredTrigger() { }
- visitIcu(icu) {
- Object.keys(icu.vars).forEach((key) => icu.vars[key].visit(this));
- Object.keys(icu.placeholders).forEach((key) => icu.placeholders[key].visit(this));
- }
- // The remaining visitors are concerned with processing AST expressions within template bindings
- visitBoundAttribute(attribute) {
- attribute.value.visit(this);
- }
- visitBoundEvent(event) {
- event.handler.visit(this);
- }
- visitDeferredBlock(deferred) {
- this.ingestScopedNode(deferred);
- deferred.triggers.when?.value.visit(this);
- deferred.prefetchTriggers.when?.value.visit(this);
- deferred.hydrateTriggers.when?.value.visit(this);
- deferred.hydrateTriggers.never?.visit(this);
- deferred.placeholder && this.visitNode(deferred.placeholder);
- deferred.loading && this.visitNode(deferred.loading);
- deferred.error && this.visitNode(deferred.error);
- }
- visitDeferredBlockPlaceholder(block) {
- this.ingestScopedNode(block);
- }
- visitDeferredBlockError(block) {
- this.ingestScopedNode(block);
- }
- visitDeferredBlockLoading(block) {
- this.ingestScopedNode(block);
- }
- visitSwitchBlock(block) {
- block.expression.visit(this);
- block.cases.forEach(this.visitNode);
- }
- visitSwitchBlockCase(block) {
- block.expression?.visit(this);
- this.ingestScopedNode(block);
- }
- visitForLoopBlock(block) {
- block.expression.visit(this);
- this.ingestScopedNode(block);
- block.empty?.visit(this);
- }
- visitForLoopBlockEmpty(block) {
- this.ingestScopedNode(block);
- }
- visitIfBlock(block) {
- block.branches.forEach((node) => node.visit(this));
- }
- visitIfBlockBranch(block) {
- block.expression?.visit(this);
- this.ingestScopedNode(block);
- }
- visitContent(content) {
- this.ingestScopedNode(content);
- }
- visitBoundText(text) {
- text.value.visit(this);
- }
- visitLetDeclaration(decl) {
- decl.value.visit(this);
- if (this.rootNode !== null) {
- this.symbols.set(decl, this.rootNode);
- }
- }
- visitPipe(ast, context) {
- this.usedPipes.add(ast.name);
- if (!this.scope.isDeferred) {
- this.eagerPipes.add(ast.name);
- }
- return super.visitPipe(ast, context);
- }
- // These five types of AST expressions can refer to expression roots, which could be variables
- // or references in the current scope.
- visitPropertyRead(ast, context) {
- this.maybeMap(ast, ast.name);
- return super.visitPropertyRead(ast, context);
- }
- visitSafePropertyRead(ast, context) {
- this.maybeMap(ast, ast.name);
- return super.visitSafePropertyRead(ast, context);
- }
- visitPropertyWrite(ast, context) {
- this.maybeMap(ast, ast.name);
- return super.visitPropertyWrite(ast, context);
- }
- ingestScopedNode(node) {
- const childScope = this.scope.getChildScope(node);
- const binder = new TemplateBinder(this.bindings, this.symbols, this.usedPipes, this.eagerPipes, this.deferBlocks, this.nestingLevel, childScope, node, this.level + 1);
- binder.ingest(node);
- }
- maybeMap(ast, name) {
- // If the receiver of the expression isn't the `ImplicitReceiver`, this isn't the root of an
- // `AST` expression that maps to a `Variable` or `Reference`.
- if (!(ast.receiver instanceof ImplicitReceiver) || ast.receiver instanceof ThisReceiver) {
- return;
- }
- // Check whether the name exists in the current scope. If so, map it. Otherwise, the name is
- // probably a property on the top-level component context.
- const target = this.scope.lookup(name);
- if (target !== null) {
- this.bindings.set(ast, target);
- }
- }
- }
- /**
- * Metadata container for a `Target` that allows queries for specific bits of metadata.
- *
- * See `BoundTarget` for documentation on the individual methods.
- */
- class R3BoundTarget {
- target;
- directives;
- eagerDirectives;
- bindings;
- references;
- exprTargets;
- symbols;
- nestingLevel;
- scopedNodeEntities;
- usedPipes;
- eagerPipes;
- /** Deferred blocks, ordered as they appear in the template. */
- deferredBlocks;
- /** Map of deferred blocks to their scope. */
- deferredScopes;
- constructor(target, directives, eagerDirectives, bindings, references, exprTargets, symbols, nestingLevel, scopedNodeEntities, usedPipes, eagerPipes, rawDeferred) {
- this.target = target;
- this.directives = directives;
- this.eagerDirectives = eagerDirectives;
- this.bindings = bindings;
- this.references = references;
- this.exprTargets = exprTargets;
- this.symbols = symbols;
- this.nestingLevel = nestingLevel;
- this.scopedNodeEntities = scopedNodeEntities;
- this.usedPipes = usedPipes;
- this.eagerPipes = eagerPipes;
- this.deferredBlocks = rawDeferred.map((current) => current[0]);
- this.deferredScopes = new Map(rawDeferred);
- }
- getEntitiesInScope(node) {
- return this.scopedNodeEntities.get(node) ?? new Set();
- }
- getDirectivesOfNode(node) {
- return this.directives.get(node) || null;
- }
- getReferenceTarget(ref) {
- return this.references.get(ref) || null;
- }
- getConsumerOfBinding(binding) {
- return this.bindings.get(binding) || null;
- }
- getExpressionTarget(expr) {
- return this.exprTargets.get(expr) || null;
- }
- getDefinitionNodeOfSymbol(symbol) {
- return this.symbols.get(symbol) || null;
- }
- getNestingLevel(node) {
- return this.nestingLevel.get(node) || 0;
- }
- getUsedDirectives() {
- const set = new Set();
- this.directives.forEach((dirs) => dirs.forEach((dir) => set.add(dir)));
- return Array.from(set.values());
- }
- getEagerlyUsedDirectives() {
- const set = new Set(this.eagerDirectives);
- return Array.from(set.values());
- }
- getUsedPipes() {
- return Array.from(this.usedPipes);
- }
- getEagerlyUsedPipes() {
- return Array.from(this.eagerPipes);
- }
- getDeferBlocks() {
- return this.deferredBlocks;
- }
- getDeferredTriggerTarget(block, trigger) {
- // Only triggers that refer to DOM nodes can be resolved.
- if (!(trigger instanceof InteractionDeferredTrigger) &&
- !(trigger instanceof ViewportDeferredTrigger) &&
- !(trigger instanceof HoverDeferredTrigger)) {
- return null;
- }
- const name = trigger.reference;
- if (name === null) {
- let trigger = null;
- if (block.placeholder !== null) {
- for (const child of block.placeholder.children) {
- // Skip over comment nodes. Currently by default the template parser doesn't capture
- // comments, but we have a safeguard here just in case since it can be enabled.
- if (child instanceof Comment$1) {
- continue;
- }
- // We can only infer the trigger if there's one root element node. Any other
- // nodes at the root make it so that we can't infer the trigger anymore.
- if (trigger !== null) {
- return null;
- }
- if (child instanceof Element$1) {
- trigger = child;
- }
- }
- }
- return trigger;
- }
- const outsideRef = this.findEntityInScope(block, name);
- // First try to resolve the target in the scope of the main deferred block. Note that we
- // skip triggers defined inside the main block itself, because they might not exist yet.
- if (outsideRef instanceof Reference$1 && this.getDefinitionNodeOfSymbol(outsideRef) !== block) {
- const target = this.getReferenceTarget(outsideRef);
- if (target !== null) {
- return this.referenceTargetToElement(target);
- }
- }
- // If the trigger couldn't be found in the main block, check the
- // placeholder block which is shown before the main block has loaded.
- if (block.placeholder !== null) {
- const refInPlaceholder = this.findEntityInScope(block.placeholder, name);
- const targetInPlaceholder = refInPlaceholder instanceof Reference$1 ? this.getReferenceTarget(refInPlaceholder) : null;
- if (targetInPlaceholder !== null) {
- return this.referenceTargetToElement(targetInPlaceholder);
- }
- }
- return null;
- }
- isDeferred(element) {
- for (const block of this.deferredBlocks) {
- if (!this.deferredScopes.has(block)) {
- continue;
- }
- const stack = [this.deferredScopes.get(block)];
- while (stack.length > 0) {
- const current = stack.pop();
- if (current.elementsInScope.has(element)) {
- return true;
- }
- stack.push(...current.childScopes.values());
- }
- }
- return false;
- }
- /**
- * Finds an entity with a specific name in a scope.
- * @param rootNode Root node of the scope.
- * @param name Name of the entity.
- */
- findEntityInScope(rootNode, name) {
- const entities = this.getEntitiesInScope(rootNode);
- for (const entity of entities) {
- if (entity.name === name) {
- return entity;
- }
- }
- return null;
- }
- /** Coerces a `ReferenceTarget` to an `Element`, if possible. */
- referenceTargetToElement(target) {
- if (target instanceof Element$1) {
- return target;
- }
- if (target instanceof Template) {
- return null;
- }
- return this.referenceTargetToElement(target.node);
- }
- }
- function extractScopedNodeEntities(rootScope, templateEntities) {
- const entityMap = new Map();
- function extractScopeEntities(scope) {
- if (entityMap.has(scope.rootNode)) {
- return entityMap.get(scope.rootNode);
- }
- const currentEntities = scope.namedEntities;
- let entities;
- if (scope.parentScope !== null) {
- entities = new Map([...extractScopeEntities(scope.parentScope), ...currentEntities]);
- }
- else {
- entities = new Map(currentEntities);
- }
- entityMap.set(scope.rootNode, entities);
- return entities;
- }
- const scopesToProcess = [rootScope];
- while (scopesToProcess.length > 0) {
- const scope = scopesToProcess.pop();
- for (const childScope of scope.childScopes.values()) {
- scopesToProcess.push(childScope);
- }
- extractScopeEntities(scope);
- }
- for (const [template, entities] of entityMap) {
- templateEntities.set(template, new Set(entities.values()));
- }
- }
- /**
- * An interface for retrieving documents by URL that the compiler uses to
- * load templates.
- *
- * This is an abstract class, rather than an interface, so that it can be used
- * as injection token.
- */
- class ResourceLoader {
- }
- class CompilerFacadeImpl {
- jitEvaluator;
- FactoryTarget = exports.FactoryTarget;
- ResourceLoader = ResourceLoader;
- elementSchemaRegistry = new DomElementSchemaRegistry();
- constructor(jitEvaluator = new JitEvaluator()) {
- this.jitEvaluator = jitEvaluator;
- }
- compilePipe(angularCoreEnv, sourceMapUrl, facade) {
- const metadata = {
- name: facade.name,
- type: wrapReference(facade.type),
- typeArgumentCount: 0,
- pipeName: facade.pipeName,
- pure: facade.pure,
- isStandalone: facade.isStandalone,
- };
- const res = compilePipeFromMetadata(metadata);
- return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);
- }
- compilePipeDeclaration(angularCoreEnv, sourceMapUrl, declaration) {
- const meta = convertDeclarePipeFacadeToMetadata(declaration);
- const res = compilePipeFromMetadata(meta);
- return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);
- }
- compileInjectable(angularCoreEnv, sourceMapUrl, facade) {
- const { expression, statements } = compileInjectable({
- name: facade.name,
- type: wrapReference(facade.type),
- typeArgumentCount: facade.typeArgumentCount,
- providedIn: computeProvidedIn(facade.providedIn),
- useClass: convertToProviderExpression(facade, 'useClass'),
- useFactory: wrapExpression(facade, 'useFactory'),
- useValue: convertToProviderExpression(facade, 'useValue'),
- useExisting: convertToProviderExpression(facade, 'useExisting'),
- deps: facade.deps?.map(convertR3DependencyMetadata),
- },
- /* resolveForwardRefs */ true);
- return this.jitExpression(expression, angularCoreEnv, sourceMapUrl, statements);
- }
- compileInjectableDeclaration(angularCoreEnv, sourceMapUrl, facade) {
- const { expression, statements } = compileInjectable({
- name: facade.type.name,
- type: wrapReference(facade.type),
- typeArgumentCount: 0,
- providedIn: computeProvidedIn(facade.providedIn),
- useClass: convertToProviderExpression(facade, 'useClass'),
- useFactory: wrapExpression(facade, 'useFactory'),
- useValue: convertToProviderExpression(facade, 'useValue'),
- useExisting: convertToProviderExpression(facade, 'useExisting'),
- deps: facade.deps?.map(convertR3DeclareDependencyMetadata),
- },
- /* resolveForwardRefs */ true);
- return this.jitExpression(expression, angularCoreEnv, sourceMapUrl, statements);
- }
- compileInjector(angularCoreEnv, sourceMapUrl, facade) {
- const meta = {
- name: facade.name,
- type: wrapReference(facade.type),
- providers: facade.providers && facade.providers.length > 0
- ? new WrappedNodeExpr(facade.providers)
- : null,
- imports: facade.imports.map((i) => new WrappedNodeExpr(i)),
- };
- const res = compileInjector(meta);
- return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);
- }
- compileInjectorDeclaration(angularCoreEnv, sourceMapUrl, declaration) {
- const meta = convertDeclareInjectorFacadeToMetadata(declaration);
- const res = compileInjector(meta);
- return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);
- }
- compileNgModule(angularCoreEnv, sourceMapUrl, facade) {
- const meta = {
- kind: exports.R3NgModuleMetadataKind.Global,
- type: wrapReference(facade.type),
- bootstrap: facade.bootstrap.map(wrapReference),
- declarations: facade.declarations.map(wrapReference),
- publicDeclarationTypes: null, // only needed for types in AOT
- imports: facade.imports.map(wrapReference),
- includeImportTypes: true,
- exports: facade.exports.map(wrapReference),
- selectorScopeMode: exports.R3SelectorScopeMode.Inline,
- containsForwardDecls: false,
- schemas: facade.schemas ? facade.schemas.map(wrapReference) : null,
- id: facade.id ? new WrappedNodeExpr(facade.id) : null,
- };
- const res = compileNgModule(meta);
- return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);
- }
- compileNgModuleDeclaration(angularCoreEnv, sourceMapUrl, declaration) {
- const expression = compileNgModuleDeclarationExpression(declaration);
- return this.jitExpression(expression, angularCoreEnv, sourceMapUrl, []);
- }
- compileDirective(angularCoreEnv, sourceMapUrl, facade) {
- const meta = convertDirectiveFacadeToMetadata(facade);
- return this.compileDirectiveFromMeta(angularCoreEnv, sourceMapUrl, meta);
- }
- compileDirectiveDeclaration(angularCoreEnv, sourceMapUrl, declaration) {
- const typeSourceSpan = this.createParseSourceSpan('Directive', declaration.type.name, sourceMapUrl);
- const meta = convertDeclareDirectiveFacadeToMetadata(declaration, typeSourceSpan);
- return this.compileDirectiveFromMeta(angularCoreEnv, sourceMapUrl, meta);
- }
- compileDirectiveFromMeta(angularCoreEnv, sourceMapUrl, meta) {
- const constantPool = new ConstantPool();
- const bindingParser = makeBindingParser();
- const res = compileDirectiveFromMetadata(meta, constantPool, bindingParser);
- return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, constantPool.statements);
- }
- compileComponent(angularCoreEnv, sourceMapUrl, facade) {
- // Parse the template and check for errors.
- const { template, interpolation, defer } = parseJitTemplate(facade.template, facade.name, sourceMapUrl, facade.preserveWhitespaces, facade.interpolation, undefined);
- // Compile the component metadata, including template, into an expression.
- const meta = {
- ...facade,
- ...convertDirectiveFacadeToMetadata(facade),
- selector: facade.selector || this.elementSchemaRegistry.getDefaultComponentElementName(),
- template,
- declarations: facade.declarations.map(convertDeclarationFacadeToMetadata),
- declarationListEmitMode: 0 /* DeclarationListEmitMode.Direct */,
- defer,
- styles: [...facade.styles, ...template.styles],
- encapsulation: facade.encapsulation,
- interpolation,
- changeDetection: facade.changeDetection ?? null,
- animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null,
- viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) : null,
- relativeContextFilePath: '',
- i18nUseExternalIds: true,
- relativeTemplatePath: null,
- };
- const jitExpressionSourceMap = `ng:///${facade.name}.js`;
- return this.compileComponentFromMeta(angularCoreEnv, jitExpressionSourceMap, meta);
- }
- compileComponentDeclaration(angularCoreEnv, sourceMapUrl, declaration) {
- const typeSourceSpan = this.createParseSourceSpan('Component', declaration.type.name, sourceMapUrl);
- const meta = convertDeclareComponentFacadeToMetadata(declaration, typeSourceSpan, sourceMapUrl);
- return this.compileComponentFromMeta(angularCoreEnv, sourceMapUrl, meta);
- }
- compileComponentFromMeta(angularCoreEnv, sourceMapUrl, meta) {
- const constantPool = new ConstantPool();
- const bindingParser = makeBindingParser(meta.interpolation);
- const res = compileComponentFromMetadata(meta, constantPool, bindingParser);
- return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, constantPool.statements);
- }
- compileFactory(angularCoreEnv, sourceMapUrl, meta) {
- const factoryRes = compileFactoryFunction({
- name: meta.name,
- type: wrapReference(meta.type),
- typeArgumentCount: meta.typeArgumentCount,
- deps: convertR3DependencyMetadataArray(meta.deps),
- target: meta.target,
- });
- return this.jitExpression(factoryRes.expression, angularCoreEnv, sourceMapUrl, factoryRes.statements);
- }
- compileFactoryDeclaration(angularCoreEnv, sourceMapUrl, meta) {
- const factoryRes = compileFactoryFunction({
- name: meta.type.name,
- type: wrapReference(meta.type),
- typeArgumentCount: 0,
- deps: Array.isArray(meta.deps)
- ? meta.deps.map(convertR3DeclareDependencyMetadata)
- : meta.deps,
- target: meta.target,
- });
- return this.jitExpression(factoryRes.expression, angularCoreEnv, sourceMapUrl, factoryRes.statements);
- }
- createParseSourceSpan(kind, typeName, sourceUrl) {
- return r3JitTypeSourceSpan(kind, typeName, sourceUrl);
- }
- /**
- * JIT compiles an expression and returns the result of executing that expression.
- *
- * @param def the definition which will be compiled and executed to get the value to patch
- * @param context an object map of @angular/core symbol names to symbols which will be available
- * in the context of the compiled expression
- * @param sourceUrl a URL to use for the source map of the compiled expression
- * @param preStatements a collection of statements that should be evaluated before the expression.
- */
- jitExpression(def, context, sourceUrl, preStatements) {
- // The ConstantPool may contain Statements which declare variables used in the final expression.
- // Therefore, its statements need to precede the actual JIT operation. The final statement is a
- // declaration of $def which is set to the expression being compiled.
- const statements = [
- ...preStatements,
- new DeclareVarStmt('$def', def, undefined, exports.StmtModifier.Exported),
- ];
- const res = this.jitEvaluator.evaluateStatements(sourceUrl, statements, new R3JitReflector(context),
- /* enableSourceMaps */ true);
- return res['$def'];
- }
- }
- function convertToR3QueryMetadata(facade) {
- return {
- ...facade,
- isSignal: facade.isSignal,
- predicate: convertQueryPredicate(facade.predicate),
- read: facade.read ? new WrappedNodeExpr(facade.read) : null,
- static: facade.static,
- emitDistinctChangesOnly: facade.emitDistinctChangesOnly,
- };
- }
- function convertQueryDeclarationToMetadata(declaration) {
- return {
- propertyName: declaration.propertyName,
- first: declaration.first ?? false,
- predicate: convertQueryPredicate(declaration.predicate),
- descendants: declaration.descendants ?? false,
- read: declaration.read ? new WrappedNodeExpr(declaration.read) : null,
- static: declaration.static ?? false,
- emitDistinctChangesOnly: declaration.emitDistinctChangesOnly ?? true,
- isSignal: !!declaration.isSignal,
- };
- }
- function convertQueryPredicate(predicate) {
- return Array.isArray(predicate)
- ? // The predicate is an array of strings so pass it through.
- predicate
- : // The predicate is a type - assume that we will need to unwrap any `forwardRef()` calls.
- createMayBeForwardRefExpression(new WrappedNodeExpr(predicate), 1 /* ForwardRefHandling.Wrapped */);
- }
- function convertDirectiveFacadeToMetadata(facade) {
- const inputsFromMetadata = parseInputsArray$1(facade.inputs || []);
- const outputsFromMetadata = parseMappingStringArray$1(facade.outputs || []);
- const propMetadata = facade.propMetadata;
- const inputsFromType = {};
- const outputsFromType = {};
- for (const field in propMetadata) {
- if (propMetadata.hasOwnProperty(field)) {
- propMetadata[field].forEach((ann) => {
- if (isInput(ann)) {
- inputsFromType[field] = {
- bindingPropertyName: ann.alias || field,
- classPropertyName: field,
- required: ann.required || false,
- // For JIT, decorators are used to declare signal inputs. That is because of
- // a technical limitation where it's not possible to statically reflect class
- // members of a directive/component at runtime before instantiating the class.
- isSignal: !!ann.isSignal,
- transformFunction: ann.transform != null ? new WrappedNodeExpr(ann.transform) : null,
- };
- }
- else if (isOutput(ann)) {
- outputsFromType[field] = ann.alias || field;
- }
- });
- }
- }
- const hostDirectives = facade.hostDirectives?.length
- ? facade.hostDirectives.map((hostDirective) => {
- return typeof hostDirective === 'function'
- ? {
- directive: wrapReference(hostDirective),
- inputs: null,
- outputs: null,
- isForwardReference: false,
- }
- : {
- directive: wrapReference(hostDirective.directive),
- isForwardReference: false,
- inputs: hostDirective.inputs ? parseMappingStringArray$1(hostDirective.inputs) : null,
- outputs: hostDirective.outputs
- ? parseMappingStringArray$1(hostDirective.outputs)
- : null,
- };
- })
- : null;
- return {
- ...facade,
- typeArgumentCount: 0,
- typeSourceSpan: facade.typeSourceSpan,
- type: wrapReference(facade.type),
- deps: null,
- host: {
- ...extractHostBindings$1(facade.propMetadata, facade.typeSourceSpan, facade.host),
- },
- inputs: { ...inputsFromMetadata, ...inputsFromType },
- outputs: { ...outputsFromMetadata, ...outputsFromType },
- queries: facade.queries.map(convertToR3QueryMetadata),
- providers: facade.providers != null ? new WrappedNodeExpr(facade.providers) : null,
- viewQueries: facade.viewQueries.map(convertToR3QueryMetadata),
- fullInheritance: false,
- hostDirectives,
- };
- }
- function convertDeclareDirectiveFacadeToMetadata(declaration, typeSourceSpan) {
- const hostDirectives = declaration.hostDirectives?.length
- ? declaration.hostDirectives.map((dir) => ({
- directive: wrapReference(dir.directive),
- isForwardReference: false,
- inputs: dir.inputs ? getHostDirectiveBindingMapping(dir.inputs) : null,
- outputs: dir.outputs ? getHostDirectiveBindingMapping(dir.outputs) : null,
- }))
- : null;
- return {
- name: declaration.type.name,
- type: wrapReference(declaration.type),
- typeSourceSpan,
- selector: declaration.selector ?? null,
- inputs: declaration.inputs ? inputsPartialMetadataToInputMetadata(declaration.inputs) : {},
- outputs: declaration.outputs ?? {},
- host: convertHostDeclarationToMetadata(declaration.host),
- queries: (declaration.queries ?? []).map(convertQueryDeclarationToMetadata),
- viewQueries: (declaration.viewQueries ?? []).map(convertQueryDeclarationToMetadata),
- providers: declaration.providers !== undefined ? new WrappedNodeExpr(declaration.providers) : null,
- exportAs: declaration.exportAs ?? null,
- usesInheritance: declaration.usesInheritance ?? false,
- lifecycle: { usesOnChanges: declaration.usesOnChanges ?? false },
- deps: null,
- typeArgumentCount: 0,
- fullInheritance: false,
- isStandalone: declaration.isStandalone ?? getJitStandaloneDefaultForVersion(declaration.version),
- isSignal: declaration.isSignal ?? false,
- hostDirectives,
- };
- }
- function convertHostDeclarationToMetadata(host = {}) {
- return {
- attributes: convertOpaqueValuesToExpressions(host.attributes ?? {}),
- listeners: host.listeners ?? {},
- properties: host.properties ?? {},
- specialAttributes: {
- classAttr: host.classAttribute,
- styleAttr: host.styleAttribute,
- },
- };
- }
- /**
- * Parses a host directive mapping where each odd array key is the name of an input/output
- * and each even key is its public name, e.g. `['one', 'oneAlias', 'two', 'two']`.
- */
- function getHostDirectiveBindingMapping(array) {
- let result = null;
- for (let i = 1; i < array.length; i += 2) {
- result = result || {};
- result[array[i - 1]] = array[i];
- }
- return result;
- }
- function convertOpaqueValuesToExpressions(obj) {
- const result = {};
- for (const key of Object.keys(obj)) {
- result[key] = new WrappedNodeExpr(obj[key]);
- }
- return result;
- }
- function convertDeclareComponentFacadeToMetadata(decl, typeSourceSpan, sourceMapUrl) {
- const { template, interpolation, defer } = parseJitTemplate(decl.template, decl.type.name, sourceMapUrl, decl.preserveWhitespaces ?? false, decl.interpolation, decl.deferBlockDependencies);
- const declarations = [];
- if (decl.dependencies) {
- for (const innerDep of decl.dependencies) {
- switch (innerDep.kind) {
- case 'directive':
- case 'component':
- declarations.push(convertDirectiveDeclarationToMetadata(innerDep));
- break;
- case 'pipe':
- declarations.push(convertPipeDeclarationToMetadata(innerDep));
- break;
- }
- }
- }
- else if (decl.components || decl.directives || decl.pipes) {
- // Existing declarations on NPM may not be using the new `dependencies` merged field, and may
- // have separate fields for dependencies instead. Unify them for JIT compilation.
- decl.components &&
- declarations.push(...decl.components.map((dir) => convertDirectiveDeclarationToMetadata(dir, /* isComponent */ true)));
- decl.directives &&
- declarations.push(...decl.directives.map((dir) => convertDirectiveDeclarationToMetadata(dir)));
- decl.pipes && declarations.push(...convertPipeMapToMetadata(decl.pipes));
- }
- return {
- ...convertDeclareDirectiveFacadeToMetadata(decl, typeSourceSpan),
- template,
- styles: decl.styles ?? [],
- declarations,
- viewProviders: decl.viewProviders !== undefined ? new WrappedNodeExpr(decl.viewProviders) : null,
- animations: decl.animations !== undefined ? new WrappedNodeExpr(decl.animations) : null,
- defer,
- changeDetection: decl.changeDetection ?? exports.ChangeDetectionStrategy.Default,
- encapsulation: decl.encapsulation ?? exports.ViewEncapsulation.Emulated,
- interpolation,
- declarationListEmitMode: 2 /* DeclarationListEmitMode.ClosureResolved */,
- relativeContextFilePath: '',
- i18nUseExternalIds: true,
- relativeTemplatePath: null,
- };
- }
- function convertDeclarationFacadeToMetadata(declaration) {
- return {
- ...declaration,
- type: new WrappedNodeExpr(declaration.type),
- };
- }
- function convertDirectiveDeclarationToMetadata(declaration, isComponent = null) {
- return {
- kind: exports.R3TemplateDependencyKind.Directive,
- isComponent: isComponent || declaration.kind === 'component',
- selector: declaration.selector,
- type: new WrappedNodeExpr(declaration.type),
- inputs: declaration.inputs ?? [],
- outputs: declaration.outputs ?? [],
- exportAs: declaration.exportAs ?? null,
- };
- }
- function convertPipeMapToMetadata(pipes) {
- if (!pipes) {
- return [];
- }
- return Object.keys(pipes).map((name) => {
- return {
- kind: exports.R3TemplateDependencyKind.Pipe,
- name,
- type: new WrappedNodeExpr(pipes[name]),
- };
- });
- }
- function convertPipeDeclarationToMetadata(pipe) {
- return {
- kind: exports.R3TemplateDependencyKind.Pipe,
- name: pipe.name,
- type: new WrappedNodeExpr(pipe.type),
- };
- }
- function parseJitTemplate(template, typeName, sourceMapUrl, preserveWhitespaces, interpolation, deferBlockDependencies) {
- const interpolationConfig = interpolation
- ? InterpolationConfig.fromArray(interpolation)
- : DEFAULT_INTERPOLATION_CONFIG;
- // Parse the template and check for errors.
- const parsed = parseTemplate(template, sourceMapUrl, {
- preserveWhitespaces,
- interpolationConfig,
- });
- if (parsed.errors !== null) {
- const errors = parsed.errors.map((err) => err.toString()).join(', ');
- throw new Error(`Errors during JIT compilation of template for ${typeName}: ${errors}`);
- }
- const binder = new R3TargetBinder(new SelectorMatcher());
- const boundTarget = binder.bind({ template: parsed.nodes });
- return {
- template: parsed,
- interpolation: interpolationConfig,
- defer: createR3ComponentDeferMetadata(boundTarget, deferBlockDependencies),
- };
- }
- /**
- * Convert the expression, if present to an `R3ProviderExpression`.
- *
- * In JIT mode we do not want the compiler to wrap the expression in a `forwardRef()` call because,
- * if it is referencing a type that has not yet been defined, it will have already been wrapped in
- * a `forwardRef()` - either by the application developer or during partial-compilation. Thus we can
- * use `ForwardRefHandling.None`.
- */
- function convertToProviderExpression(obj, property) {
- if (obj.hasOwnProperty(property)) {
- return createMayBeForwardRefExpression(new WrappedNodeExpr(obj[property]), 0 /* ForwardRefHandling.None */);
- }
- else {
- return undefined;
- }
- }
- function wrapExpression(obj, property) {
- if (obj.hasOwnProperty(property)) {
- return new WrappedNodeExpr(obj[property]);
- }
- else {
- return undefined;
- }
- }
- function computeProvidedIn(providedIn) {
- const expression = typeof providedIn === 'function'
- ? new WrappedNodeExpr(providedIn)
- : new LiteralExpr(providedIn ?? null);
- // See `convertToProviderExpression()` for why this uses `ForwardRefHandling.None`.
- return createMayBeForwardRefExpression(expression, 0 /* ForwardRefHandling.None */);
- }
- function convertR3DependencyMetadataArray(facades) {
- return facades == null ? null : facades.map(convertR3DependencyMetadata);
- }
- function convertR3DependencyMetadata(facade) {
- const isAttributeDep = facade.attribute != null; // both `null` and `undefined`
- const rawToken = facade.token === null ? null : new WrappedNodeExpr(facade.token);
- // In JIT mode, if the dep is an `@Attribute()` then we use the attribute name given in
- // `attribute` rather than the `token`.
- const token = isAttributeDep ? new WrappedNodeExpr(facade.attribute) : rawToken;
- return createR3DependencyMetadata(token, isAttributeDep, facade.host, facade.optional, facade.self, facade.skipSelf);
- }
- function convertR3DeclareDependencyMetadata(facade) {
- const isAttributeDep = facade.attribute ?? false;
- const token = facade.token === null ? null : new WrappedNodeExpr(facade.token);
- return createR3DependencyMetadata(token, isAttributeDep, facade.host ?? false, facade.optional ?? false, facade.self ?? false, facade.skipSelf ?? false);
- }
- function createR3DependencyMetadata(token, isAttributeDep, host, optional, self, skipSelf) {
- // If the dep is an `@Attribute()` the `attributeNameType` ought to be the `unknown` type.
- // But types are not available at runtime so we just use a literal `"<unknown>"` string as a dummy
- // marker.
- const attributeNameType = isAttributeDep ? literal$1('unknown') : null;
- return { token, attributeNameType, host, optional, self, skipSelf };
- }
- function createR3ComponentDeferMetadata(boundTarget, deferBlockDependencies) {
- const deferredBlocks = boundTarget.getDeferBlocks();
- const blocks = new Map();
- for (let i = 0; i < deferredBlocks.length; i++) {
- const dependencyFn = deferBlockDependencies?.[i];
- blocks.set(deferredBlocks[i], dependencyFn ? new WrappedNodeExpr(dependencyFn) : null);
- }
- return { mode: 0 /* DeferBlockDepsEmitMode.PerBlock */, blocks };
- }
- function extractHostBindings$1(propMetadata, sourceSpan, host) {
- // First parse the declarations from the metadata.
- const bindings = parseHostBindings(host || {});
- // After that check host bindings for errors
- const errors = verifyHostBindings(bindings, sourceSpan);
- if (errors.length) {
- throw new Error(errors.map((error) => error.msg).join('\n'));
- }
- // Next, loop over the properties of the object, looking for @HostBinding and @HostListener.
- for (const field in propMetadata) {
- if (propMetadata.hasOwnProperty(field)) {
- propMetadata[field].forEach((ann) => {
- if (isHostBinding(ann)) {
- // Since this is a decorator, we know that the value is a class member. Always access it
- // through `this` so that further down the line it can't be confused for a literal value
- // (e.g. if there's a property called `true`).
- bindings.properties[ann.hostPropertyName || field] = getSafePropertyAccessString('this', field);
- }
- else if (isHostListener(ann)) {
- bindings.listeners[ann.eventName || field] = `${field}(${(ann.args || []).join(',')})`;
- }
- });
- }
- }
- return bindings;
- }
- function isHostBinding(value) {
- return value.ngMetadataName === 'HostBinding';
- }
- function isHostListener(value) {
- return value.ngMetadataName === 'HostListener';
- }
- function isInput(value) {
- return value.ngMetadataName === 'Input';
- }
- function isOutput(value) {
- return value.ngMetadataName === 'Output';
- }
- function inputsPartialMetadataToInputMetadata(inputs) {
- return Object.keys(inputs).reduce((result, minifiedClassName) => {
- const value = inputs[minifiedClassName];
- // Handle legacy partial input output.
- if (typeof value === 'string' || Array.isArray(value)) {
- result[minifiedClassName] = parseLegacyInputPartialOutput(value);
- }
- else {
- result[minifiedClassName] = {
- bindingPropertyName: value.publicName,
- classPropertyName: minifiedClassName,
- transformFunction: value.transformFunction !== null ? new WrappedNodeExpr(value.transformFunction) : null,
- required: value.isRequired,
- isSignal: value.isSignal,
- };
- }
- return result;
- }, {});
- }
- /**
- * Parses the legacy input partial output. For more details see `partial/directive.ts`.
- * TODO(legacy-partial-output-inputs): Remove in v18.
- */
- function parseLegacyInputPartialOutput(value) {
- if (typeof value === 'string') {
- return {
- bindingPropertyName: value,
- classPropertyName: value,
- transformFunction: null,
- required: false,
- // legacy partial output does not capture signal inputs.
- isSignal: false,
- };
- }
- return {
- bindingPropertyName: value[0],
- classPropertyName: value[1],
- transformFunction: value[2] ? new WrappedNodeExpr(value[2]) : null,
- required: false,
- // legacy partial output does not capture signal inputs.
- isSignal: false,
- };
- }
- function parseInputsArray$1(values) {
- return values.reduce((results, value) => {
- if (typeof value === 'string') {
- const [bindingPropertyName, classPropertyName] = parseMappingString$1(value);
- results[classPropertyName] = {
- bindingPropertyName,
- classPropertyName,
- required: false,
- // Signal inputs not supported for the inputs array.
- isSignal: false,
- transformFunction: null,
- };
- }
- else {
- results[value.name] = {
- bindingPropertyName: value.alias || value.name,
- classPropertyName: value.name,
- required: value.required || false,
- // Signal inputs not supported for the inputs array.
- isSignal: false,
- transformFunction: value.transform != null ? new WrappedNodeExpr(value.transform) : null,
- };
- }
- return results;
- }, {});
- }
- function parseMappingStringArray$1(values) {
- return values.reduce((results, value) => {
- const [alias, fieldName] = parseMappingString$1(value);
- results[fieldName] = alias;
- return results;
- }, {});
- }
- function parseMappingString$1(value) {
- // Either the value is 'field' or 'field: property'. In the first case, `property` will
- // be undefined, in which case the field name should also be used as the property name.
- const [fieldName, bindingPropertyName] = value.split(':', 2).map((str) => str.trim());
- return [bindingPropertyName ?? fieldName, fieldName];
- }
- function convertDeclarePipeFacadeToMetadata(declaration) {
- return {
- name: declaration.type.name,
- type: wrapReference(declaration.type),
- typeArgumentCount: 0,
- pipeName: declaration.name,
- deps: null,
- pure: declaration.pure ?? true,
- isStandalone: declaration.isStandalone ?? getJitStandaloneDefaultForVersion(declaration.version),
- };
- }
- function convertDeclareInjectorFacadeToMetadata(declaration) {
- return {
- name: declaration.type.name,
- type: wrapReference(declaration.type),
- providers: declaration.providers !== undefined && declaration.providers.length > 0
- ? new WrappedNodeExpr(declaration.providers)
- : null,
- imports: declaration.imports !== undefined
- ? declaration.imports.map((i) => new WrappedNodeExpr(i))
- : [],
- };
- }
- function publishFacade(global) {
- const ng = global.ng || (global.ng = {});
- ng.ɵcompilerFacade = new CompilerFacadeImpl();
- }
- /**
- * @module
- * @description
- * Entry point for all public APIs of the compiler package.
- */
- new Version('19.2.13');
- const _I18N_ATTR = 'i18n';
- const _I18N_ATTR_PREFIX = 'i18n-';
- const _I18N_COMMENT_PREFIX_REGEXP = /^i18n:?/;
- const MEANING_SEPARATOR = '|';
- const ID_SEPARATOR = '@@';
- let i18nCommentsWarned = false;
- /**
- * Extract translatable messages from an html AST
- */
- function extractMessages(nodes, interpolationConfig, implicitTags, implicitAttrs, preserveSignificantWhitespace) {
- const visitor = new _Visitor(implicitTags, implicitAttrs, preserveSignificantWhitespace);
- return visitor.extract(nodes, interpolationConfig);
- }
- class ExtractionResult {
- messages;
- errors;
- constructor(messages, errors) {
- this.messages = messages;
- this.errors = errors;
- }
- }
- var _VisitorMode;
- (function (_VisitorMode) {
- _VisitorMode[_VisitorMode["Extract"] = 0] = "Extract";
- _VisitorMode[_VisitorMode["Merge"] = 1] = "Merge";
- })(_VisitorMode || (_VisitorMode = {}));
- /**
- * This Visitor is used:
- * 1. to extract all the translatable strings from an html AST (see `extract()`),
- * 2. to replace the translatable strings with the actual translations (see `merge()`)
- *
- * @internal
- */
- class _Visitor {
- _implicitTags;
- _implicitAttrs;
- _preserveSignificantWhitespace;
- // Using non-null assertions because all variables are (re)set in init()
- _depth;
- // <el i18n>...</el>
- _inI18nNode;
- _inImplicitNode;
- // <!--i18n-->...<!--/i18n-->
- _inI18nBlock;
- _blockMeaningAndDesc;
- _blockChildren;
- _blockStartDepth;
- // {<icu message>}
- _inIcu;
- // set to void 0 when not in a section
- _msgCountAtSectionStart;
- _errors;
- _mode;
- // _VisitorMode.Extract only
- _messages;
- // _VisitorMode.Merge only
- _translations;
- _createI18nMessage;
- constructor(_implicitTags, _implicitAttrs, _preserveSignificantWhitespace = true) {
- this._implicitTags = _implicitTags;
- this._implicitAttrs = _implicitAttrs;
- this._preserveSignificantWhitespace = _preserveSignificantWhitespace;
- }
- /**
- * Extracts the messages from the tree
- */
- extract(nodes, interpolationConfig) {
- this._init(_VisitorMode.Extract, interpolationConfig);
- nodes.forEach((node) => node.visit(this, null));
- if (this._inI18nBlock) {
- this._reportError(nodes[nodes.length - 1], 'Unclosed block');
- }
- return new ExtractionResult(this._messages, this._errors);
- }
- /**
- * Returns a tree where all translatable nodes are translated
- */
- merge(nodes, translations, interpolationConfig) {
- this._init(_VisitorMode.Merge, interpolationConfig);
- this._translations = translations;
- // Construct a single fake root element
- const wrapper = new Element('wrapper', [], nodes, undefined, undefined, undefined);
- const translatedNode = wrapper.visit(this, null);
- if (this._inI18nBlock) {
- this._reportError(nodes[nodes.length - 1], 'Unclosed block');
- }
- return new ParseTreeResult(translatedNode.children, this._errors);
- }
- visitExpansionCase(icuCase, context) {
- // Parse cases for translatable html attributes
- const expression = visitAll(this, icuCase.expression, context);
- if (this._mode === _VisitorMode.Merge) {
- return new ExpansionCase(icuCase.value, expression, icuCase.sourceSpan, icuCase.valueSourceSpan, icuCase.expSourceSpan);
- }
- }
- visitExpansion(icu, context) {
- this._mayBeAddBlockChildren(icu);
- const wasInIcu = this._inIcu;
- if (!this._inIcu) {
- // nested ICU messages should not be extracted but top-level translated as a whole
- if (this._isInTranslatableSection) {
- this._addMessage([icu]);
- }
- this._inIcu = true;
- }
- const cases = visitAll(this, icu.cases, context);
- if (this._mode === _VisitorMode.Merge) {
- icu = new Expansion(icu.switchValue, icu.type, cases, icu.sourceSpan, icu.switchValueSourceSpan);
- }
- this._inIcu = wasInIcu;
- return icu;
- }
- visitComment(comment, context) {
- const isOpening = _isOpeningComment(comment);
- if (isOpening && this._isInTranslatableSection) {
- this._reportError(comment, 'Could not start a block inside a translatable section');
- return;
- }
- const isClosing = _isClosingComment(comment);
- if (isClosing && !this._inI18nBlock) {
- this._reportError(comment, 'Trying to close an unopened block');
- return;
- }
- if (!this._inI18nNode && !this._inIcu) {
- if (!this._inI18nBlock) {
- if (isOpening) {
- // deprecated from v5 you should use <ng-container i18n> instead of i18n comments
- if (!i18nCommentsWarned && console && console.warn) {
- i18nCommentsWarned = true;
- const details = comment.sourceSpan.details ? `, ${comment.sourceSpan.details}` : '';
- // TODO(ocombe): use a log service once there is a public one available
- console.warn(`I18n comments are deprecated, use an <ng-container> element instead (${comment.sourceSpan.start}${details})`);
- }
- this._inI18nBlock = true;
- this._blockStartDepth = this._depth;
- this._blockChildren = [];
- this._blockMeaningAndDesc = comment
- .value.replace(_I18N_COMMENT_PREFIX_REGEXP, '')
- .trim();
- this._openTranslatableSection(comment);
- }
- }
- else {
- if (isClosing) {
- if (this._depth == this._blockStartDepth) {
- this._closeTranslatableSection(comment, this._blockChildren);
- this._inI18nBlock = false;
- const message = this._addMessage(this._blockChildren, this._blockMeaningAndDesc);
- // merge attributes in sections
- const nodes = this._translateMessage(comment, message);
- return visitAll(this, nodes);
- }
- else {
- this._reportError(comment, 'I18N blocks should not cross element boundaries');
- return;
- }
- }
- }
- }
- }
- visitText(text, context) {
- if (this._isInTranslatableSection) {
- this._mayBeAddBlockChildren(text);
- }
- return text;
- }
- visitElement(el, context) {
- this._mayBeAddBlockChildren(el);
- this._depth++;
- const wasInI18nNode = this._inI18nNode;
- const wasInImplicitNode = this._inImplicitNode;
- let childNodes = [];
- let translatedChildNodes = undefined;
- // Extract:
- // - top level nodes with the (implicit) "i18n" attribute if not already in a section
- // - ICU messages
- const i18nAttr = _getI18nAttr(el);
- const i18nMeta = i18nAttr ? i18nAttr.value : '';
- const isImplicit = this._implicitTags.some((tag) => el.name === tag) &&
- !this._inIcu &&
- !this._isInTranslatableSection;
- const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
- this._inImplicitNode = wasInImplicitNode || isImplicit;
- if (!this._isInTranslatableSection && !this._inIcu) {
- if (i18nAttr || isTopLevelImplicit) {
- this._inI18nNode = true;
- const message = this._addMessage(el.children, i18nMeta);
- translatedChildNodes = this._translateMessage(el, message);
- }
- if (this._mode == _VisitorMode.Extract) {
- const isTranslatable = i18nAttr || isTopLevelImplicit;
- if (isTranslatable)
- this._openTranslatableSection(el);
- visitAll(this, el.children);
- if (isTranslatable)
- this._closeTranslatableSection(el, el.children);
- }
- }
- else {
- if (i18nAttr || isTopLevelImplicit) {
- this._reportError(el, 'Could not mark an element as translatable inside a translatable section');
- }
- if (this._mode == _VisitorMode.Extract) {
- // Descend into child nodes for extraction
- visitAll(this, el.children);
- }
- }
- if (this._mode === _VisitorMode.Merge) {
- const visitNodes = translatedChildNodes || el.children;
- visitNodes.forEach((child) => {
- const visited = child.visit(this, context);
- if (visited && !this._isInTranslatableSection) {
- // Do not add the children from translatable sections (= i18n blocks here)
- // They will be added later in this loop when the block closes (i.e. on `<!-- /i18n -->`)
- childNodes = childNodes.concat(visited);
- }
- });
- }
- this._visitAttributesOf(el);
- this._depth--;
- this._inI18nNode = wasInI18nNode;
- this._inImplicitNode = wasInImplicitNode;
- if (this._mode === _VisitorMode.Merge) {
- const translatedAttrs = this._translateAttributes(el);
- return new Element(el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
- }
- return null;
- }
- visitAttribute(attribute, context) {
- throw new Error('unreachable code');
- }
- visitBlock(block, context) {
- visitAll(this, block.children, context);
- }
- visitBlockParameter(parameter, context) { }
- visitLetDeclaration(decl, context) { }
- _init(mode, interpolationConfig) {
- this._mode = mode;
- this._inI18nBlock = false;
- this._inI18nNode = false;
- this._depth = 0;
- this._inIcu = false;
- this._msgCountAtSectionStart = undefined;
- this._errors = [];
- this._messages = [];
- this._inImplicitNode = false;
- this._createI18nMessage = createI18nMessageFactory(interpolationConfig, DEFAULT_CONTAINER_BLOCKS,
- // When dropping significant whitespace we need to retain whitespace tokens or
- // else we won't be able to reuse source spans because empty tokens would be
- // removed and cause a mismatch.
- /* retainEmptyTokens */ !this._preserveSignificantWhitespace,
- /* preserveExpressionWhitespace */ this._preserveSignificantWhitespace);
- }
- // looks for translatable attributes
- _visitAttributesOf(el) {
- const explicitAttrNameToValue = {};
- const implicitAttrNames = this._implicitAttrs[el.name] || [];
- el.attrs
- .filter((attr) => attr.name.startsWith(_I18N_ATTR_PREFIX))
- .forEach((attr) => (explicitAttrNameToValue[attr.name.slice(_I18N_ATTR_PREFIX.length)] = attr.value));
- el.attrs.forEach((attr) => {
- if (attr.name in explicitAttrNameToValue) {
- this._addMessage([attr], explicitAttrNameToValue[attr.name]);
- }
- else if (implicitAttrNames.some((name) => attr.name === name)) {
- this._addMessage([attr]);
- }
- });
- }
- // add a translatable message
- _addMessage(ast, msgMeta) {
- if (ast.length == 0 ||
- this._isEmptyAttributeValue(ast) ||
- this._isPlaceholderOnlyAttributeValue(ast) ||
- this._isPlaceholderOnlyMessage(ast)) {
- // Do not create empty messages
- return null;
- }
- const { meaning, description, id } = _parseMessageMeta(msgMeta);
- const message = this._createI18nMessage(ast, meaning, description, id);
- this._messages.push(message);
- return message;
- }
- // Check for cases like `<div i18n-title title="">`.
- _isEmptyAttributeValue(ast) {
- if (!isAttrNode(ast))
- return false;
- const node = ast[0];
- return node.value.trim() === '';
- }
- // Check for cases like `<div i18n-title title="{{ name }}">`.
- _isPlaceholderOnlyAttributeValue(ast) {
- if (!isAttrNode(ast))
- return false;
- const tokens = ast[0].valueTokens ?? [];
- const interpolations = tokens.filter((token) => token.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */);
- const plainText = tokens
- .filter((token) => token.type === 16 /* TokenType.ATTR_VALUE_TEXT */)
- // `AttributeValueTextToken` always has exactly one part per its type.
- .map((token) => token.parts[0].trim())
- .join('');
- // Check if there is a single interpolation and all text around it is empty.
- return interpolations.length === 1 && plainText === '';
- }
- // Check for cases like `<div i18n>{{ name }}</div>`.
- _isPlaceholderOnlyMessage(ast) {
- if (!isTextNode(ast))
- return false;
- const tokens = ast[0].tokens;
- const interpolations = tokens.filter((token) => token.type === 8 /* TokenType.INTERPOLATION */);
- const plainText = tokens
- .filter((token) => token.type === 5 /* TokenType.TEXT */)
- // `TextToken` always has exactly one part per its type.
- .map((token) => token.parts[0].trim())
- .join('');
- // Check if there is a single interpolation and all text around it is empty.
- return interpolations.length === 1 && plainText === '';
- }
- // Translates the given message given the `TranslationBundle`
- // This is used for translating elements / blocks - see `_translateAttributes` for attributes
- // no-op when called in extraction mode (returns [])
- _translateMessage(el, message) {
- if (message && this._mode === _VisitorMode.Merge) {
- const nodes = this._translations.get(message);
- if (nodes) {
- return nodes;
- }
- this._reportError(el, `Translation unavailable for message id="${this._translations.digest(message)}"`);
- }
- return [];
- }
- // translate the attributes of an element and remove i18n specific attributes
- _translateAttributes(el) {
- const attributes = el.attrs;
- const i18nParsedMessageMeta = {};
- attributes.forEach((attr) => {
- if (attr.name.startsWith(_I18N_ATTR_PREFIX)) {
- i18nParsedMessageMeta[attr.name.slice(_I18N_ATTR_PREFIX.length)] = _parseMessageMeta(attr.value);
- }
- });
- const translatedAttributes = [];
- attributes.forEach((attr) => {
- if (attr.name === _I18N_ATTR || attr.name.startsWith(_I18N_ATTR_PREFIX)) {
- // strip i18n specific attributes
- return;
- }
- if (attr.value && attr.value != '' && i18nParsedMessageMeta.hasOwnProperty(attr.name)) {
- const { meaning, description, id } = i18nParsedMessageMeta[attr.name];
- const message = this._createI18nMessage([attr], meaning, description, id);
- const nodes = this._translations.get(message);
- if (nodes) {
- if (nodes.length == 0) {
- translatedAttributes.push(new Attribute(attr.name, '', attr.sourceSpan, undefined /* keySpan */, undefined /* valueSpan */, undefined /* valueTokens */, undefined /* i18n */));
- }
- else if (nodes[0] instanceof Text) {
- const value = nodes[0].value;
- translatedAttributes.push(new Attribute(attr.name, value, attr.sourceSpan, undefined /* keySpan */, undefined /* valueSpan */, undefined /* valueTokens */, undefined /* i18n */));
- }
- else {
- this._reportError(el, `Unexpected translation for attribute "${attr.name}" (id="${id || this._translations.digest(message)}")`);
- }
- }
- else {
- this._reportError(el, `Translation unavailable for attribute "${attr.name}" (id="${id || this._translations.digest(message)}")`);
- }
- }
- else {
- translatedAttributes.push(attr);
- }
- });
- return translatedAttributes;
- }
- /**
- * Add the node as a child of the block when:
- * - we are in a block,
- * - we are not inside a ICU message (those are handled separately),
- * - the node is a "direct child" of the block
- */
- _mayBeAddBlockChildren(node) {
- if (this._inI18nBlock && !this._inIcu && this._depth == this._blockStartDepth) {
- this._blockChildren.push(node);
- }
- }
- /**
- * Marks the start of a section, see `_closeTranslatableSection`
- */
- _openTranslatableSection(node) {
- if (this._isInTranslatableSection) {
- this._reportError(node, 'Unexpected section start');
- }
- else {
- this._msgCountAtSectionStart = this._messages.length;
- }
- }
- /**
- * A translatable section could be:
- * - the content of translatable element,
- * - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
- */
- get _isInTranslatableSection() {
- return this._msgCountAtSectionStart !== void 0;
- }
- /**
- * Terminates a section.
- *
- * If a section has only one significant children (comments not significant) then we should not
- * keep the message from this children:
- *
- * `<p i18n="meaning|description">{ICU message}</p>` would produce two messages:
- * - one for the <p> content with meaning and description,
- * - another one for the ICU message.
- *
- * In this case the last message is discarded as it contains less information (the AST is
- * otherwise identical).
- *
- * Note that we should still keep messages extracted from attributes inside the section (ie in the
- * ICU message here)
- */
- _closeTranslatableSection(node, directChildren) {
- if (!this._isInTranslatableSection) {
- this._reportError(node, 'Unexpected section end');
- return;
- }
- const startIndex = this._msgCountAtSectionStart;
- const significantChildren = directChildren.reduce((count, node) => count + (node instanceof Comment ? 0 : 1), 0);
- if (significantChildren == 1) {
- for (let i = this._messages.length - 1; i >= startIndex; i--) {
- const ast = this._messages[i].nodes;
- if (!(ast.length == 1 && ast[0] instanceof Text$2)) {
- this._messages.splice(i, 1);
- break;
- }
- }
- }
- this._msgCountAtSectionStart = undefined;
- }
- _reportError(node, msg) {
- this._errors.push(new I18nError(node.sourceSpan, msg));
- }
- }
- function _isOpeningComment(n) {
- return !!(n instanceof Comment && n.value && n.value.startsWith('i18n'));
- }
- function _isClosingComment(n) {
- return !!(n instanceof Comment && n.value && n.value === '/i18n');
- }
- function _getI18nAttr(p) {
- return p.attrs.find((attr) => attr.name === _I18N_ATTR) || null;
- }
- function _parseMessageMeta(i18n) {
- if (!i18n)
- return { meaning: '', description: '', id: '' };
- const idIndex = i18n.indexOf(ID_SEPARATOR);
- const descIndex = i18n.indexOf(MEANING_SEPARATOR);
- const [meaningAndDesc, id] = idIndex > -1 ? [i18n.slice(0, idIndex), i18n.slice(idIndex + 2)] : [i18n, ''];
- const [meaning, description] = descIndex > -1
- ? [meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)]
- : ['', meaningAndDesc];
- return { meaning, description, id: id.trim() };
- }
- function isTextNode(ast) {
- return ast.length === 1 && ast[0] instanceof Text;
- }
- function isAttrNode(ast) {
- return ast.length === 1 && ast[0] instanceof Attribute;
- }
- //////////////////////////////////////
- // THIS FILE HAS GLOBAL SIDE EFFECT //
- // (see bottom of file) //
- //////////////////////////////////////
- /**
- * @module
- * @description
- * Entry point for all APIs of the compiler package.
- *
- * <div class="callout is-critical">
- * <header>Unstable APIs</header>
- * <p>
- * All compiler apis are currently considered experimental and private!
- * </p>
- * <p>
- * We expect the APIs in this package to keep on changing. Do not rely on them.
- * </p>
- * </div>
- */
- // This file only reexports content of the `src` folder. Keep it that way.
- // This function call has a global side effects and publishes the compiler into global namespace for
- // the late binding of the Compiler to the @angular/core for jit compilation.
- publishFacade(_global);
- /**
- * @publicApi
- */
- exports.ErrorCode = void 0;
- (function (ErrorCode) {
- ErrorCode[ErrorCode["DECORATOR_ARG_NOT_LITERAL"] = 1001] = "DECORATOR_ARG_NOT_LITERAL";
- ErrorCode[ErrorCode["DECORATOR_ARITY_WRONG"] = 1002] = "DECORATOR_ARITY_WRONG";
- ErrorCode[ErrorCode["DECORATOR_NOT_CALLED"] = 1003] = "DECORATOR_NOT_CALLED";
- ErrorCode[ErrorCode["DECORATOR_UNEXPECTED"] = 1005] = "DECORATOR_UNEXPECTED";
- /**
- * This error code indicates that there are incompatible decorators on a type or a class field.
- */
- ErrorCode[ErrorCode["DECORATOR_COLLISION"] = 1006] = "DECORATOR_COLLISION";
- ErrorCode[ErrorCode["VALUE_HAS_WRONG_TYPE"] = 1010] = "VALUE_HAS_WRONG_TYPE";
- ErrorCode[ErrorCode["VALUE_NOT_LITERAL"] = 1011] = "VALUE_NOT_LITERAL";
- ErrorCode[ErrorCode["DUPLICATE_DECORATED_PROPERTIES"] = 1012] = "DUPLICATE_DECORATED_PROPERTIES";
- /**
- * Raised when an initializer API is annotated with an unexpected decorator.
- *
- * e.g. `@Input` is also applied on the class member using `input`.
- */
- ErrorCode[ErrorCode["INITIALIZER_API_WITH_DISALLOWED_DECORATOR"] = 1050] = "INITIALIZER_API_WITH_DISALLOWED_DECORATOR";
- /**
- * Raised when an initializer API feature (like signal inputs) are also
- * declared in the class decorator metadata.
- *
- * e.g. a signal input is also declared in the `@Directive` `inputs` array.
- */
- ErrorCode[ErrorCode["INITIALIZER_API_DECORATOR_METADATA_COLLISION"] = 1051] = "INITIALIZER_API_DECORATOR_METADATA_COLLISION";
- /**
- * Raised whenever an initializer API does not support the `.required`
- * function, but is still detected unexpectedly.
- */
- ErrorCode[ErrorCode["INITIALIZER_API_NO_REQUIRED_FUNCTION"] = 1052] = "INITIALIZER_API_NO_REQUIRED_FUNCTION";
- /**
- * Raised whenever an initializer API is used on a class member
- * and the given access modifiers (e.g. `private`) are not allowed.
- */
- ErrorCode[ErrorCode["INITIALIZER_API_DISALLOWED_MEMBER_VISIBILITY"] = 1053] = "INITIALIZER_API_DISALLOWED_MEMBER_VISIBILITY";
- /**
- * An Angular feature, like inputs, outputs or queries is incorrectly
- * declared on a static member.
- */
- ErrorCode[ErrorCode["INCORRECTLY_DECLARED_ON_STATIC_MEMBER"] = 1100] = "INCORRECTLY_DECLARED_ON_STATIC_MEMBER";
- ErrorCode[ErrorCode["COMPONENT_MISSING_TEMPLATE"] = 2001] = "COMPONENT_MISSING_TEMPLATE";
- ErrorCode[ErrorCode["PIPE_MISSING_NAME"] = 2002] = "PIPE_MISSING_NAME";
- ErrorCode[ErrorCode["PARAM_MISSING_TOKEN"] = 2003] = "PARAM_MISSING_TOKEN";
- ErrorCode[ErrorCode["DIRECTIVE_MISSING_SELECTOR"] = 2004] = "DIRECTIVE_MISSING_SELECTOR";
- /** Raised when an undecorated class is passed in as a provider to a module or a directive. */
- ErrorCode[ErrorCode["UNDECORATED_PROVIDER"] = 2005] = "UNDECORATED_PROVIDER";
- /**
- * Raised when a Directive inherits its constructor from a base class without an Angular
- * decorator.
- */
- ErrorCode[ErrorCode["DIRECTIVE_INHERITS_UNDECORATED_CTOR"] = 2006] = "DIRECTIVE_INHERITS_UNDECORATED_CTOR";
- /**
- * Raised when an undecorated class that is using Angular features
- * has been discovered.
- */
- ErrorCode[ErrorCode["UNDECORATED_CLASS_USING_ANGULAR_FEATURES"] = 2007] = "UNDECORATED_CLASS_USING_ANGULAR_FEATURES";
- /**
- * Raised when an component cannot resolve an external resource, such as a template or a style
- * sheet.
- */
- ErrorCode[ErrorCode["COMPONENT_RESOURCE_NOT_FOUND"] = 2008] = "COMPONENT_RESOURCE_NOT_FOUND";
- /**
- * Raised when a component uses `ShadowDom` view encapsulation, but its selector
- * does not match the shadow DOM tag name requirements.
- */
- ErrorCode[ErrorCode["COMPONENT_INVALID_SHADOW_DOM_SELECTOR"] = 2009] = "COMPONENT_INVALID_SHADOW_DOM_SELECTOR";
- /**
- * Raised when a component has `imports` but is not marked as `standalone: true`.
- */
- ErrorCode[ErrorCode["COMPONENT_NOT_STANDALONE"] = 2010] = "COMPONENT_NOT_STANDALONE";
- /**
- * Raised when a type in the `imports` of a component is a directive or pipe, but is not
- * standalone.
- */
- ErrorCode[ErrorCode["COMPONENT_IMPORT_NOT_STANDALONE"] = 2011] = "COMPONENT_IMPORT_NOT_STANDALONE";
- /**
- * Raised when a type in the `imports` of a component is not a directive, pipe, or NgModule.
- */
- ErrorCode[ErrorCode["COMPONENT_UNKNOWN_IMPORT"] = 2012] = "COMPONENT_UNKNOWN_IMPORT";
- /**
- * Raised when the compiler wasn't able to resolve the metadata of a host directive.
- */
- ErrorCode[ErrorCode["HOST_DIRECTIVE_INVALID"] = 2013] = "HOST_DIRECTIVE_INVALID";
- /**
- * Raised when a host directive isn't standalone.
- */
- ErrorCode[ErrorCode["HOST_DIRECTIVE_NOT_STANDALONE"] = 2014] = "HOST_DIRECTIVE_NOT_STANDALONE";
- /**
- * Raised when a host directive is a component.
- */
- ErrorCode[ErrorCode["HOST_DIRECTIVE_COMPONENT"] = 2015] = "HOST_DIRECTIVE_COMPONENT";
- /**
- * Raised when a type with Angular decorator inherits its constructor from a base class
- * which has a constructor that is incompatible with Angular DI.
- */
- ErrorCode[ErrorCode["INJECTABLE_INHERITS_INVALID_CONSTRUCTOR"] = 2016] = "INJECTABLE_INHERITS_INVALID_CONSTRUCTOR";
- /** Raised when a host tries to alias a host directive binding that does not exist. */
- ErrorCode[ErrorCode["HOST_DIRECTIVE_UNDEFINED_BINDING"] = 2017] = "HOST_DIRECTIVE_UNDEFINED_BINDING";
- /**
- * Raised when a host tries to alias a host directive
- * binding to a pre-existing binding's public name.
- */
- ErrorCode[ErrorCode["HOST_DIRECTIVE_CONFLICTING_ALIAS"] = 2018] = "HOST_DIRECTIVE_CONFLICTING_ALIAS";
- /**
- * Raised when a host directive definition doesn't expose a
- * required binding from the host directive.
- */
- ErrorCode[ErrorCode["HOST_DIRECTIVE_MISSING_REQUIRED_BINDING"] = 2019] = "HOST_DIRECTIVE_MISSING_REQUIRED_BINDING";
- /**
- * Raised when a component specifies both a `transform` function on an input
- * and has a corresponding `ngAcceptInputType_` member for the same input.
- */
- ErrorCode[ErrorCode["CONFLICTING_INPUT_TRANSFORM"] = 2020] = "CONFLICTING_INPUT_TRANSFORM";
- /** Raised when a component has both `styleUrls` and `styleUrl`. */
- ErrorCode[ErrorCode["COMPONENT_INVALID_STYLE_URLS"] = 2021] = "COMPONENT_INVALID_STYLE_URLS";
- /**
- * Raised when a type in the `deferredImports` of a component is not a component, directive or
- * pipe.
- */
- ErrorCode[ErrorCode["COMPONENT_UNKNOWN_DEFERRED_IMPORT"] = 2022] = "COMPONENT_UNKNOWN_DEFERRED_IMPORT";
- /**
- * Raised when a `standalone: false` component is declared but `strictStandalone` is set.
- */
- ErrorCode[ErrorCode["NON_STANDALONE_NOT_ALLOWED"] = 2023] = "NON_STANDALONE_NOT_ALLOWED";
- ErrorCode[ErrorCode["SYMBOL_NOT_EXPORTED"] = 3001] = "SYMBOL_NOT_EXPORTED";
- /**
- * Raised when a relationship between directives and/or pipes would cause a cyclic import to be
- * created that cannot be handled, such as in partial compilation mode.
- */
- ErrorCode[ErrorCode["IMPORT_CYCLE_DETECTED"] = 3003] = "IMPORT_CYCLE_DETECTED";
- /**
- * Raised when the compiler is unable to generate an import statement for a reference.
- */
- ErrorCode[ErrorCode["IMPORT_GENERATION_FAILURE"] = 3004] = "IMPORT_GENERATION_FAILURE";
- ErrorCode[ErrorCode["CONFIG_FLAT_MODULE_NO_INDEX"] = 4001] = "CONFIG_FLAT_MODULE_NO_INDEX";
- ErrorCode[ErrorCode["CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK"] = 4002] = "CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK";
- ErrorCode[ErrorCode["CONFIG_EXTENDED_DIAGNOSTICS_IMPLIES_STRICT_TEMPLATES"] = 4003] = "CONFIG_EXTENDED_DIAGNOSTICS_IMPLIES_STRICT_TEMPLATES";
- ErrorCode[ErrorCode["CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CATEGORY_LABEL"] = 4004] = "CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CATEGORY_LABEL";
- ErrorCode[ErrorCode["CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CHECK"] = 4005] = "CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CHECK";
- /**
- * Raised when a host expression has a parse error, such as a host listener or host binding
- * expression containing a pipe.
- */
- ErrorCode[ErrorCode["HOST_BINDING_PARSE_ERROR"] = 5001] = "HOST_BINDING_PARSE_ERROR";
- /**
- * Raised when the compiler cannot parse a component's template.
- */
- ErrorCode[ErrorCode["TEMPLATE_PARSE_ERROR"] = 5002] = "TEMPLATE_PARSE_ERROR";
- /**
- * Raised when an NgModule contains an invalid reference in `declarations`.
- */
- ErrorCode[ErrorCode["NGMODULE_INVALID_DECLARATION"] = 6001] = "NGMODULE_INVALID_DECLARATION";
- /**
- * Raised when an NgModule contains an invalid type in `imports`.
- */
- ErrorCode[ErrorCode["NGMODULE_INVALID_IMPORT"] = 6002] = "NGMODULE_INVALID_IMPORT";
- /**
- * Raised when an NgModule contains an invalid type in `exports`.
- */
- ErrorCode[ErrorCode["NGMODULE_INVALID_EXPORT"] = 6003] = "NGMODULE_INVALID_EXPORT";
- /**
- * Raised when an NgModule contains a type in `exports` which is neither in `declarations` nor
- * otherwise imported.
- */
- ErrorCode[ErrorCode["NGMODULE_INVALID_REEXPORT"] = 6004] = "NGMODULE_INVALID_REEXPORT";
- /**
- * Raised when a `ModuleWithProviders` with a missing
- * generic type argument is passed into an `NgModule`.
- */
- ErrorCode[ErrorCode["NGMODULE_MODULE_WITH_PROVIDERS_MISSING_GENERIC"] = 6005] = "NGMODULE_MODULE_WITH_PROVIDERS_MISSING_GENERIC";
- /**
- * Raised when an NgModule exports multiple directives/pipes of the same name and the compiler
- * attempts to generate private re-exports within the NgModule file.
- */
- ErrorCode[ErrorCode["NGMODULE_REEXPORT_NAME_COLLISION"] = 6006] = "NGMODULE_REEXPORT_NAME_COLLISION";
- /**
- * Raised when a directive/pipe is part of the declarations of two or more NgModules.
- */
- ErrorCode[ErrorCode["NGMODULE_DECLARATION_NOT_UNIQUE"] = 6007] = "NGMODULE_DECLARATION_NOT_UNIQUE";
- /**
- * Raised when a standalone directive/pipe is part of the declarations of an NgModule.
- */
- ErrorCode[ErrorCode["NGMODULE_DECLARATION_IS_STANDALONE"] = 6008] = "NGMODULE_DECLARATION_IS_STANDALONE";
- /**
- * Raised when a standalone component is part of the bootstrap list of an NgModule.
- */
- ErrorCode[ErrorCode["NGMODULE_BOOTSTRAP_IS_STANDALONE"] = 6009] = "NGMODULE_BOOTSTRAP_IS_STANDALONE";
- /**
- * Indicates that an NgModule is declared with `id: module.id`. This is an anti-pattern that is
- * disabled explicitly in the compiler, that was originally based on a misunderstanding of
- * `NgModule.id`.
- */
- ErrorCode[ErrorCode["WARN_NGMODULE_ID_UNNECESSARY"] = 6100] = "WARN_NGMODULE_ID_UNNECESSARY";
- /**
- * 6999 was previously assigned to NGMODULE_VE_DEPENDENCY_ON_IVY_LIB
- * To prevent any confusion, let's not reassign it.
- */
- /**
- * An element name failed validation against the DOM schema.
- */
- ErrorCode[ErrorCode["SCHEMA_INVALID_ELEMENT"] = 8001] = "SCHEMA_INVALID_ELEMENT";
- /**
- * An element's attribute name failed validation against the DOM schema.
- */
- ErrorCode[ErrorCode["SCHEMA_INVALID_ATTRIBUTE"] = 8002] = "SCHEMA_INVALID_ATTRIBUTE";
- /**
- * No matching directive was found for a `#ref="target"` expression.
- */
- ErrorCode[ErrorCode["MISSING_REFERENCE_TARGET"] = 8003] = "MISSING_REFERENCE_TARGET";
- /**
- * No matching pipe was found for a
- */
- ErrorCode[ErrorCode["MISSING_PIPE"] = 8004] = "MISSING_PIPE";
- /**
- * The left-hand side of an assignment expression was a template variable. Effectively, the
- * template looked like:
- *
- * ```html
- * <ng-template let-something>
- * <button (click)="something = ...">...</button>
- * </ng-template>
- * ```
- *
- * Template variables are read-only.
- */
- ErrorCode[ErrorCode["WRITE_TO_READ_ONLY_VARIABLE"] = 8005] = "WRITE_TO_READ_ONLY_VARIABLE";
- /**
- * A template variable was declared twice. For example:
- *
- * ```html
- * <div *ngFor="let i of items; let i = index">
- * </div>
- * ```
- */
- ErrorCode[ErrorCode["DUPLICATE_VARIABLE_DECLARATION"] = 8006] = "DUPLICATE_VARIABLE_DECLARATION";
- /**
- * A template has a two way binding (two bindings created by a single syntactical element)
- * in which the input and output are going to different places.
- */
- ErrorCode[ErrorCode["SPLIT_TWO_WAY_BINDING"] = 8007] = "SPLIT_TWO_WAY_BINDING";
- /**
- * A directive usage isn't binding to one or more required inputs.
- */
- ErrorCode[ErrorCode["MISSING_REQUIRED_INPUTS"] = 8008] = "MISSING_REQUIRED_INPUTS";
- /**
- * The tracking expression of a `for` loop block is accessing a variable that is unavailable,
- * for example:
- *
- * ```angular-html
- * <ng-template let-ref>
- * @for (item of items; track ref) {}
- * </ng-template>
- * ```
- */
- ErrorCode[ErrorCode["ILLEGAL_FOR_LOOP_TRACK_ACCESS"] = 8009] = "ILLEGAL_FOR_LOOP_TRACK_ACCESS";
- /**
- * The trigger of a `defer` block cannot access its trigger element,
- * either because it doesn't exist or it's in a different view.
- *
- * ```angular-html
- * @defer (on interaction(trigger)) {...}
- *
- * <ng-template>
- * <button #trigger></button>
- * </ng-template>
- * ```
- */
- ErrorCode[ErrorCode["INACCESSIBLE_DEFERRED_TRIGGER_ELEMENT"] = 8010] = "INACCESSIBLE_DEFERRED_TRIGGER_ELEMENT";
- /**
- * A control flow node is projected at the root of a component and is preventing its direct
- * descendants from being projected, because it has more than one root node.
- *
- * ```angular-html
- * <comp>
- * @if (expr) {
- * <div projectsIntoSlot></div>
- * Text preventing the div from being projected
- * }
- * </comp>
- * ```
- */
- ErrorCode[ErrorCode["CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION"] = 8011] = "CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION";
- /**
- * A pipe imported via `@Component.deferredImports` is
- * used outside of a `@defer` block in a template.
- */
- ErrorCode[ErrorCode["DEFERRED_PIPE_USED_EAGERLY"] = 8012] = "DEFERRED_PIPE_USED_EAGERLY";
- /**
- * A directive/component imported via `@Component.deferredImports` is
- * used outside of a `@defer` block in a template.
- */
- ErrorCode[ErrorCode["DEFERRED_DIRECTIVE_USED_EAGERLY"] = 8013] = "DEFERRED_DIRECTIVE_USED_EAGERLY";
- /**
- * A directive/component/pipe imported via `@Component.deferredImports` is
- * also included into the `@Component.imports` list.
- */
- ErrorCode[ErrorCode["DEFERRED_DEPENDENCY_IMPORTED_EAGERLY"] = 8014] = "DEFERRED_DEPENDENCY_IMPORTED_EAGERLY";
- /** An expression is trying to write to an `@let` declaration. */
- ErrorCode[ErrorCode["ILLEGAL_LET_WRITE"] = 8015] = "ILLEGAL_LET_WRITE";
- /** An expression is trying to read an `@let` before it has been defined. */
- ErrorCode[ErrorCode["LET_USED_BEFORE_DEFINITION"] = 8016] = "LET_USED_BEFORE_DEFINITION";
- /** A `@let` declaration conflicts with another symbol in the same scope. */
- ErrorCode[ErrorCode["CONFLICTING_LET_DECLARATION"] = 8017] = "CONFLICTING_LET_DECLARATION";
- /**
- * A two way binding in a template has an incorrect syntax,
- * parentheses outside brackets. For example:
- *
- * ```html
- * <div ([foo])="bar" />
- * ```
- */
- ErrorCode[ErrorCode["INVALID_BANANA_IN_BOX"] = 8101] = "INVALID_BANANA_IN_BOX";
- /**
- * The left side of a nullish coalescing operation is not nullable.
- *
- * ```html
- * {{ foo ?? bar }}
- * ```
- * When the type of foo doesn't include `null` or `undefined`.
- */
- ErrorCode[ErrorCode["NULLISH_COALESCING_NOT_NULLABLE"] = 8102] = "NULLISH_COALESCING_NOT_NULLABLE";
- /**
- * A known control flow directive (e.g. `*ngIf`) is used in a template,
- * but the `CommonModule` is not imported.
- */
- ErrorCode[ErrorCode["MISSING_CONTROL_FLOW_DIRECTIVE"] = 8103] = "MISSING_CONTROL_FLOW_DIRECTIVE";
- /**
- * A text attribute is not interpreted as a binding but likely intended to be.
- *
- * For example:
- * ```html
- * <div
- * attr.x="value"
- * class.blue="true"
- * style.margin-right.px="5">
- * </div>
- * ```
- *
- * All of the above attributes will just be static text attributes and will not be interpreted as
- * bindings by the compiler.
- */
- ErrorCode[ErrorCode["TEXT_ATTRIBUTE_NOT_BINDING"] = 8104] = "TEXT_ATTRIBUTE_NOT_BINDING";
- /**
- * NgForOf is used in a template, but the user forgot to include let
- * in their statement.
- *
- * For example:
- * ```html
- * <ul><li *ngFor="item of items">{{item["name"]}};</li></ul>
- * ```
- */
- ErrorCode[ErrorCode["MISSING_NGFOROF_LET"] = 8105] = "MISSING_NGFOROF_LET";
- /**
- * Indicates that the binding suffix is not supported
- *
- * Style bindings support suffixes like `style.width.px`, `.em`, and `.%`.
- * These suffixes are _not_ supported for attribute bindings.
- *
- * For example `[attr.width.px]="5"` becomes `width.px="5"` when bound.
- * This is almost certainly unintentional and this error is meant to
- * surface this mistake to the developer.
- */
- ErrorCode[ErrorCode["SUFFIX_NOT_SUPPORTED"] = 8106] = "SUFFIX_NOT_SUPPORTED";
- /**
- * The left side of an optional chain operation is not nullable.
- *
- * ```html
- * {{ foo?.bar }}
- * {{ foo?.['bar'] }}
- * {{ foo?.() }}
- * ```
- * When the type of foo doesn't include `null` or `undefined`.
- */
- ErrorCode[ErrorCode["OPTIONAL_CHAIN_NOT_NULLABLE"] = 8107] = "OPTIONAL_CHAIN_NOT_NULLABLE";
- /**
- * `ngSkipHydration` should not be a binding (it should be a static attribute).
- *
- * For example:
- * ```html
- * <my-cmp [ngSkipHydration]="someTruthyVar" />
- * ```
- *
- * `ngSkipHydration` cannot be a binding and can not have values other than "true" or an empty
- * value
- */
- ErrorCode[ErrorCode["SKIP_HYDRATION_NOT_STATIC"] = 8108] = "SKIP_HYDRATION_NOT_STATIC";
- /**
- * Signal functions should be invoked when interpolated in templates.
- *
- * For example:
- * ```html
- * {{ mySignal() }}
- * ```
- */
- ErrorCode[ErrorCode["INTERPOLATED_SIGNAL_NOT_INVOKED"] = 8109] = "INTERPOLATED_SIGNAL_NOT_INVOKED";
- /**
- * Initializer-based APIs can only be invoked from inside of an initializer.
- *
- * ```ts
- * // Allowed
- * myInput = input();
- *
- * // Not allowed
- * function myInput() {
- * return input();
- * }
- * ```
- */
- ErrorCode[ErrorCode["UNSUPPORTED_INITIALIZER_API_USAGE"] = 8110] = "UNSUPPORTED_INITIALIZER_API_USAGE";
- /**
- * A function in an event binding is not called.
- *
- * For example:
- * ```html
- * <button (click)="myFunc"></button>
- * ```
- *
- * This will not call `myFunc` when the button is clicked. Instead, it should be
- * `<button (click)="myFunc()"></button>`.
- */
- ErrorCode[ErrorCode["UNINVOKED_FUNCTION_IN_EVENT_BINDING"] = 8111] = "UNINVOKED_FUNCTION_IN_EVENT_BINDING";
- /**
- * A `@let` declaration in a template isn't used.
- *
- * For example:
- * ```angular-html
- * @let used = 1; <!-- Not an error -->
- * @let notUsed = 2; <!-- Error -->
- *
- * {{used}}
- * ```
- */
- ErrorCode[ErrorCode["UNUSED_LET_DECLARATION"] = 8112] = "UNUSED_LET_DECLARATION";
- /**
- * A symbol referenced in `@Component.imports` isn't being used within the template.
- */
- ErrorCode[ErrorCode["UNUSED_STANDALONE_IMPORTS"] = 8113] = "UNUSED_STANDALONE_IMPORTS";
- /**
- * The template type-checking engine would need to generate an inline type check block for a
- * component, but the current type-checking environment doesn't support it.
- */
- ErrorCode[ErrorCode["INLINE_TCB_REQUIRED"] = 8900] = "INLINE_TCB_REQUIRED";
- /**
- * The template type-checking engine would need to generate an inline type constructor for a
- * directive or component, but the current type-checking environment doesn't support it.
- */
- ErrorCode[ErrorCode["INLINE_TYPE_CTOR_REQUIRED"] = 8901] = "INLINE_TYPE_CTOR_REQUIRED";
- /**
- * An injectable already has a `ɵprov` property.
- */
- ErrorCode[ErrorCode["INJECTABLE_DUPLICATE_PROV"] = 9001] = "INJECTABLE_DUPLICATE_PROV";
- // 10XXX error codes are reserved for diagnostics with categories other than
- // `ts.DiagnosticCategory.Error`. These diagnostics are generated by the compiler when configured
- // to do so by a tool such as the Language Service, or by the Language Service itself.
- /**
- * Suggest users to enable `strictTemplates` to make use of full capabilities
- * provided by Angular language service.
- */
- ErrorCode[ErrorCode["SUGGEST_STRICT_TEMPLATES"] = 10001] = "SUGGEST_STRICT_TEMPLATES";
- /**
- * Indicates that a particular structural directive provides advanced type narrowing
- * functionality, but the current template type-checking configuration does not allow its usage in
- * type inference.
- */
- ErrorCode[ErrorCode["SUGGEST_SUBOPTIMAL_TYPE_INFERENCE"] = 10002] = "SUGGEST_SUBOPTIMAL_TYPE_INFERENCE";
- /**
- * In local compilation mode a const is required to be resolved statically but cannot be so since
- * it is imported from a file outside of the compilation unit. This usually happens with const
- * being used as Angular decorators parameters such as `@Component.template`,
- * `@HostListener.eventName`, etc.
- */
- ErrorCode[ErrorCode["LOCAL_COMPILATION_UNRESOLVED_CONST"] = 11001] = "LOCAL_COMPILATION_UNRESOLVED_CONST";
- /**
- * In local compilation mode a certain expression or syntax is not supported. This is usually
- * because the expression/syntax is not very common and so we did not add support for it yet. This
- * can be changed in the future and support for more expressions could be added if need be.
- * Meanwhile, this error is thrown to indicate a current unavailability.
- */
- ErrorCode[ErrorCode["LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION"] = 11003] = "LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION";
- })(exports.ErrorCode || (exports.ErrorCode = {}));
- /**
- * Contains a set of error messages that have detailed guides at angular.io.
- * Full list of available error guides can be found at https://angular.dev/errors
- */
- const COMPILER_ERRORS_WITH_GUIDES = new Set([
- exports.ErrorCode.DECORATOR_ARG_NOT_LITERAL,
- exports.ErrorCode.IMPORT_CYCLE_DETECTED,
- exports.ErrorCode.PARAM_MISSING_TOKEN,
- exports.ErrorCode.SCHEMA_INVALID_ELEMENT,
- exports.ErrorCode.SCHEMA_INVALID_ATTRIBUTE,
- exports.ErrorCode.MISSING_REFERENCE_TARGET,
- exports.ErrorCode.COMPONENT_INVALID_SHADOW_DOM_SELECTOR,
- exports.ErrorCode.WARN_NGMODULE_ID_UNNECESSARY,
- ]);
- function ngErrorCode(code) {
- return parseInt('-99' + code);
- }
- class FatalDiagnosticError extends Error {
- code;
- node;
- diagnosticMessage;
- relatedInformation;
- constructor(code, node, diagnosticMessage, relatedInformation) {
- super(`FatalDiagnosticError: Code: ${code}, Message: ${ts.flattenDiagnosticMessageText(diagnosticMessage, '\n')}`);
- this.code = code;
- this.node = node;
- this.diagnosticMessage = diagnosticMessage;
- this.relatedInformation = relatedInformation;
- // Extending `Error` ends up breaking some internal tests. This appears to be a known issue
- // when extending errors in TS and the workaround is to explicitly set the prototype.
- // https://stackoverflow.com/questions/41102060/typescript-extending-error-class
- Object.setPrototypeOf(this, new.target.prototype);
- }
- /**
- * @internal
- */
- _isFatalDiagnosticError = true;
- toDiagnostic() {
- return makeDiagnostic(this.code, this.node, this.diagnosticMessage, this.relatedInformation);
- }
- }
- function makeDiagnostic(code, node, messageText, relatedInformation, category = ts.DiagnosticCategory.Error) {
- node = ts.getOriginalNode(node);
- return {
- category,
- code: ngErrorCode(code),
- file: ts.getOriginalNode(node).getSourceFile(),
- start: node.getStart(undefined, false),
- length: node.getWidth(),
- messageText,
- relatedInformation,
- };
- }
- function makeDiagnosticChain(messageText, next) {
- return {
- category: ts.DiagnosticCategory.Message,
- code: 0,
- messageText,
- next,
- };
- }
- function makeRelatedInformation(node, messageText) {
- node = ts.getOriginalNode(node);
- return {
- category: ts.DiagnosticCategory.Message,
- code: 0,
- file: node.getSourceFile(),
- start: node.getStart(),
- length: node.getWidth(),
- messageText,
- };
- }
- function addDiagnosticChain(messageText, add) {
- if (typeof messageText === 'string') {
- return makeDiagnosticChain(messageText, add);
- }
- if (messageText.next === undefined) {
- messageText.next = add;
- }
- else {
- messageText.next.push(...add);
- }
- return messageText;
- }
- function isFatalDiagnosticError(err) {
- return err._isFatalDiagnosticError === true;
- }
- /**
- * Enum holding the name of each extended template diagnostic. The name is used as a user-meaningful
- * value for configuring the diagnostic in the project's options.
- *
- * See the corresponding `ErrorCode` for documentation about each specific error.
- * packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts
- *
- * @publicApi
- */
- exports.ExtendedTemplateDiagnosticName = void 0;
- (function (ExtendedTemplateDiagnosticName) {
- ExtendedTemplateDiagnosticName["INVALID_BANANA_IN_BOX"] = "invalidBananaInBox";
- ExtendedTemplateDiagnosticName["NULLISH_COALESCING_NOT_NULLABLE"] = "nullishCoalescingNotNullable";
- ExtendedTemplateDiagnosticName["OPTIONAL_CHAIN_NOT_NULLABLE"] = "optionalChainNotNullable";
- ExtendedTemplateDiagnosticName["MISSING_CONTROL_FLOW_DIRECTIVE"] = "missingControlFlowDirective";
- ExtendedTemplateDiagnosticName["TEXT_ATTRIBUTE_NOT_BINDING"] = "textAttributeNotBinding";
- ExtendedTemplateDiagnosticName["UNINVOKED_FUNCTION_IN_EVENT_BINDING"] = "uninvokedFunctionInEventBinding";
- ExtendedTemplateDiagnosticName["MISSING_NGFOROF_LET"] = "missingNgForOfLet";
- ExtendedTemplateDiagnosticName["SUFFIX_NOT_SUPPORTED"] = "suffixNotSupported";
- ExtendedTemplateDiagnosticName["SKIP_HYDRATION_NOT_STATIC"] = "skipHydrationNotStatic";
- ExtendedTemplateDiagnosticName["INTERPOLATED_SIGNAL_NOT_INVOKED"] = "interpolatedSignalNotInvoked";
- ExtendedTemplateDiagnosticName["CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION"] = "controlFlowPreventingContentProjection";
- ExtendedTemplateDiagnosticName["UNUSED_LET_DECLARATION"] = "unusedLetDeclaration";
- ExtendedTemplateDiagnosticName["UNUSED_STANDALONE_IMPORTS"] = "unusedStandaloneImports";
- })(exports.ExtendedTemplateDiagnosticName || (exports.ExtendedTemplateDiagnosticName = {}));
- /**
- * The default `FileSystem` that will always fail.
- *
- * This is a way of ensuring that the developer consciously chooses and
- * configures the `FileSystem` before using it; particularly important when
- * considering static functions like `absoluteFrom()` which rely on
- * the `FileSystem` under the hood.
- */
- class InvalidFileSystem {
- exists(path) {
- throw makeError();
- }
- readFile(path) {
- throw makeError();
- }
- readFileBuffer(path) {
- throw makeError();
- }
- writeFile(path, data, exclusive) {
- throw makeError();
- }
- removeFile(path) {
- throw makeError();
- }
- symlink(target, path) {
- throw makeError();
- }
- readdir(path) {
- throw makeError();
- }
- lstat(path) {
- throw makeError();
- }
- stat(path) {
- throw makeError();
- }
- pwd() {
- throw makeError();
- }
- chdir(path) {
- throw makeError();
- }
- extname(path) {
- throw makeError();
- }
- copyFile(from, to) {
- throw makeError();
- }
- moveFile(from, to) {
- throw makeError();
- }
- ensureDir(path) {
- throw makeError();
- }
- removeDeep(path) {
- throw makeError();
- }
- isCaseSensitive() {
- throw makeError();
- }
- resolve(...paths) {
- throw makeError();
- }
- dirname(file) {
- throw makeError();
- }
- join(basePath, ...paths) {
- throw makeError();
- }
- isRoot(path) {
- throw makeError();
- }
- isRooted(path) {
- throw makeError();
- }
- relative(from, to) {
- throw makeError();
- }
- basename(filePath, extension) {
- throw makeError();
- }
- realpath(filePath) {
- throw makeError();
- }
- getDefaultLibLocation() {
- throw makeError();
- }
- normalize(path) {
- throw makeError();
- }
- }
- function makeError() {
- return new Error('FileSystem has not been configured. Please call `setFileSystem()` before calling this method.');
- }
- const TS_DTS_JS_EXTENSION = /(?:\.d)?\.ts$|\.js$/;
- /**
- * Remove a .ts, .d.ts, or .js extension from a file name.
- */
- function stripExtension(path) {
- return path.replace(TS_DTS_JS_EXTENSION, '');
- }
- function getSourceFileOrError(program, fileName) {
- const sf = program.getSourceFile(fileName);
- if (sf === undefined) {
- throw new Error(`Program does not contain "${fileName}" - available files are ${program
- .getSourceFiles()
- .map((sf) => sf.fileName)
- .join(', ')}`);
- }
- return sf;
- }
- let fs = new InvalidFileSystem();
- function getFileSystem() {
- return fs;
- }
- function setFileSystem(fileSystem) {
- fs = fileSystem;
- }
- /**
- * Convert the path `path` to an `AbsoluteFsPath`, throwing an error if it's not an absolute path.
- */
- function absoluteFrom(path) {
- if (!fs.isRooted(path)) {
- throw new Error(`Internal Error: absoluteFrom(${path}): path is not absolute`);
- }
- return fs.resolve(path);
- }
- const ABSOLUTE_PATH = Symbol('AbsolutePath');
- /**
- * Extract an `AbsoluteFsPath` from a `ts.SourceFile`-like object.
- */
- function absoluteFromSourceFile(sf) {
- const sfWithPatch = sf;
- if (sfWithPatch[ABSOLUTE_PATH] === undefined) {
- sfWithPatch[ABSOLUTE_PATH] = fs.resolve(sfWithPatch.fileName);
- }
- // Non-null assertion needed since TS doesn't narrow the type of fields that use a symbol as a key
- // apparently.
- return sfWithPatch[ABSOLUTE_PATH];
- }
- /**
- * Static access to `dirname`.
- */
- function dirname(file) {
- return fs.dirname(file);
- }
- /**
- * Static access to `join`.
- */
- function join(basePath, ...paths) {
- return fs.join(basePath, ...paths);
- }
- /**
- * Static access to `resolve`s.
- */
- function resolve(basePath, ...paths) {
- return fs.resolve(basePath, ...paths);
- }
- /**
- * Static access to `isRooted`.
- */
- function isRooted(path) {
- return fs.isRooted(path);
- }
- /**
- * Static access to `relative`.
- */
- function relative(from, to) {
- return fs.relative(from, to);
- }
- /**
- * Returns true if the given path is locally relative.
- *
- * This is used to work out if the given path is relative (i.e. not absolute) but also is not
- * escaping the current directory.
- */
- function isLocalRelativePath(relativePath) {
- return !isRooted(relativePath) && !relativePath.startsWith('..');
- }
- /**
- * Converts a path to a form suitable for use as a relative module import specifier.
- *
- * In other words it adds the `./` to the path if it is locally relative.
- */
- function toRelativeImport(relativePath) {
- return isLocalRelativePath(relativePath) ? `./${relativePath}` : relativePath;
- }
- const LogicalProjectPath = {
- /**
- * Get the relative path between two `LogicalProjectPath`s.
- *
- * This will return a `PathSegment` which would be a valid module specifier to use in `from` when
- * importing from `to`.
- */
- relativePathBetween: function (from, to) {
- const relativePath = relative(dirname(resolve(from)), resolve(to));
- return toRelativeImport(relativePath);
- },
- };
- /**
- * A utility class which can translate absolute paths to source files into logical paths in
- * TypeScript's logical file system, based on the root directories of the project.
- */
- class LogicalFileSystem {
- compilerHost;
- /**
- * The root directories of the project, sorted with the longest path first.
- */
- rootDirs;
- /**
- * The same root directories as `rootDirs` but with each one converted to its
- * canonical form for matching in case-insensitive file-systems.
- */
- canonicalRootDirs;
- /**
- * A cache of file paths to project paths, because computation of these paths is slightly
- * expensive.
- */
- cache = new Map();
- constructor(rootDirs, compilerHost) {
- this.compilerHost = compilerHost;
- // Make a copy and sort it by length in reverse order (longest first). This speeds up lookups,
- // since there's no need to keep going through the array once a match is found.
- this.rootDirs = rootDirs.concat([]).sort((a, b) => b.length - a.length);
- this.canonicalRootDirs = this.rootDirs.map((dir) => this.compilerHost.getCanonicalFileName(dir));
- }
- /**
- * Get the logical path in the project of a `ts.SourceFile`.
- *
- * This method is provided as a convenient alternative to calling
- * `logicalPathOfFile(absoluteFromSourceFile(sf))`.
- */
- logicalPathOfSf(sf) {
- return this.logicalPathOfFile(absoluteFromSourceFile(sf));
- }
- /**
- * Get the logical path in the project of a source file.
- *
- * @returns A `LogicalProjectPath` to the source file, or `null` if the source file is not in any
- * of the TS project's root directories.
- */
- logicalPathOfFile(physicalFile) {
- if (!this.cache.has(physicalFile)) {
- const canonicalFilePath = this.compilerHost.getCanonicalFileName(physicalFile);
- let logicalFile = null;
- for (let i = 0; i < this.rootDirs.length; i++) {
- const rootDir = this.rootDirs[i];
- const canonicalRootDir = this.canonicalRootDirs[i];
- if (isWithinBasePath(canonicalRootDir, canonicalFilePath)) {
- // Note that we match against canonical paths but then create the logical path from
- // original paths.
- logicalFile = this.createLogicalProjectPath(physicalFile, rootDir);
- // The logical project does not include any special "node_modules" nested directories.
- if (logicalFile.indexOf('/node_modules/') !== -1) {
- logicalFile = null;
- }
- else {
- break;
- }
- }
- }
- this.cache.set(physicalFile, logicalFile);
- }
- return this.cache.get(physicalFile);
- }
- createLogicalProjectPath(file, rootDir) {
- const logicalPath = stripExtension(file.slice(rootDir.length));
- return (logicalPath.startsWith('/') ? logicalPath : '/' + logicalPath);
- }
- }
- /**
- * Is the `path` a descendant of the `base`?
- * E.g. `foo/bar/zee` is within `foo/bar` but not within `foo/car`.
- */
- function isWithinBasePath(base, path) {
- return isLocalRelativePath(relative(base, path));
- }
- /// <reference types="node" />
- /**
- * A wrapper around the Node.js file-system that supports path manipulation.
- */
- class NodeJSPathManipulation {
- pwd() {
- return this.normalize(process.cwd());
- }
- chdir(dir) {
- process.chdir(dir);
- }
- resolve(...paths) {
- return this.normalize(p__namespace.resolve(...paths));
- }
- dirname(file) {
- return this.normalize(p__namespace.dirname(file));
- }
- join(basePath, ...paths) {
- return this.normalize(p__namespace.join(basePath, ...paths));
- }
- isRoot(path) {
- return this.dirname(path) === this.normalize(path);
- }
- isRooted(path) {
- return p__namespace.isAbsolute(path);
- }
- relative(from, to) {
- return this.normalize(p__namespace.relative(from, to));
- }
- basename(filePath, extension) {
- return p__namespace.basename(filePath, extension);
- }
- extname(path) {
- return p__namespace.extname(path);
- }
- normalize(path) {
- // Convert backslashes to forward slashes
- return path.replace(/\\/g, '/');
- }
- }
- // G3-ESM-MARKER: G3 uses CommonJS, but externally everything in ESM.
- // CommonJS/ESM interop for determining the current file name and containing dir.
- const isCommonJS = typeof __filename !== 'undefined';
- const currentFileUrl = isCommonJS ? null : (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('checker-5pyJrZ9G.cjs', document.baseURI).href));
- const currentFileName = isCommonJS ? __filename : url.fileURLToPath(currentFileUrl);
- /**
- * A wrapper around the Node.js file-system that supports readonly operations and path manipulation.
- */
- class NodeJSReadonlyFileSystem extends NodeJSPathManipulation {
- _caseSensitive = undefined;
- isCaseSensitive() {
- if (this._caseSensitive === undefined) {
- // Note the use of the real file-system is intentional:
- // `this.exists()` relies upon `isCaseSensitive()` so that would cause an infinite recursion.
- this._caseSensitive = !fs$1.existsSync(this.normalize(toggleCase(currentFileName)));
- }
- return this._caseSensitive;
- }
- exists(path) {
- return fs$1.existsSync(path);
- }
- readFile(path) {
- return fs$1.readFileSync(path, 'utf8');
- }
- readFileBuffer(path) {
- return fs$1.readFileSync(path);
- }
- readdir(path) {
- return fs$1.readdirSync(path);
- }
- lstat(path) {
- return fs$1.lstatSync(path);
- }
- stat(path) {
- return fs$1.statSync(path);
- }
- realpath(path) {
- return this.resolve(fs$1.realpathSync(path));
- }
- getDefaultLibLocation() {
- // G3-ESM-MARKER: G3 uses CommonJS, but externally everything in ESM.
- const requireFn = isCommonJS ? require : module$1.createRequire(currentFileUrl);
- return this.resolve(requireFn.resolve('typescript'), '..');
- }
- }
- /**
- * A wrapper around the Node.js file-system (i.e. the `fs` package).
- */
- class NodeJSFileSystem extends NodeJSReadonlyFileSystem {
- writeFile(path, data, exclusive = false) {
- fs$1.writeFileSync(path, data, exclusive ? { flag: 'wx' } : undefined);
- }
- removeFile(path) {
- fs$1.unlinkSync(path);
- }
- symlink(target, path) {
- fs$1.symlinkSync(target, path);
- }
- copyFile(from, to) {
- fs$1.copyFileSync(from, to);
- }
- moveFile(from, to) {
- fs$1.renameSync(from, to);
- }
- ensureDir(path) {
- fs$1.mkdirSync(path, { recursive: true });
- }
- removeDeep(path) {
- fs$1.rmdirSync(path, { recursive: true });
- }
- }
- /**
- * Toggle the case of each character in a string.
- */
- function toggleCase(str) {
- return str.replace(/\w/g, (ch) => ch.toUpperCase() === ch ? ch.toLowerCase() : ch.toUpperCase());
- }
- const TS = /\.tsx?$/i;
- const D_TS = /\.d\.ts$/i;
- function isSymbolWithValueDeclaration(symbol) {
- // If there is a value declaration set, then the `declarations` property is never undefined. We
- // still check for the property to exist as this matches with the type that `symbol` is narrowed
- // to.
- return (symbol != null && symbol.valueDeclaration !== undefined && symbol.declarations !== undefined);
- }
- function isDtsPath(filePath) {
- return D_TS.test(filePath);
- }
- function isNonDeclarationTsPath(filePath) {
- return TS.test(filePath) && !D_TS.test(filePath);
- }
- function isFromDtsFile(node) {
- let sf = node.getSourceFile();
- if (sf === undefined) {
- sf = ts.getOriginalNode(node).getSourceFile();
- }
- return sf !== undefined && sf.isDeclarationFile;
- }
- function nodeNameForError(node) {
- if (node.name !== undefined && ts.isIdentifier(node.name)) {
- return node.name.text;
- }
- else {
- const kind = ts.SyntaxKind[node.kind];
- const { line, character } = ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart());
- return `${kind}@${line}:${character}`;
- }
- }
- function getSourceFile(node) {
- // In certain transformation contexts, `ts.Node.getSourceFile()` can actually return `undefined`,
- // despite the type signature not allowing it. In that event, get the `ts.SourceFile` via the
- // original node instead (which works).
- const directSf = node.getSourceFile();
- return directSf !== undefined ? directSf : ts.getOriginalNode(node).getSourceFile();
- }
- function getSourceFileOrNull(program, fileName) {
- return program.getSourceFile(fileName) || null;
- }
- function getTokenAtPosition(sf, pos) {
- // getTokenAtPosition is part of TypeScript's private API.
- return ts.getTokenAtPosition(sf, pos);
- }
- function identifierOfNode(decl) {
- if (decl.name !== undefined && ts.isIdentifier(decl.name)) {
- return decl.name;
- }
- else {
- return null;
- }
- }
- function isDeclaration(node) {
- return isValueDeclaration(node) || isTypeDeclaration(node);
- }
- function isValueDeclaration(node) {
- return (ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isVariableDeclaration(node));
- }
- function isTypeDeclaration(node) {
- return (ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node));
- }
- function isNamedDeclaration(node) {
- const namedNode = node;
- return namedNode.name !== undefined && ts.isIdentifier(namedNode.name);
- }
- function getRootDirs(host, options) {
- const rootDirs = [];
- const cwd = host.getCurrentDirectory();
- const fs = getFileSystem();
- if (options.rootDirs !== undefined) {
- rootDirs.push(...options.rootDirs);
- }
- else if (options.rootDir !== undefined) {
- rootDirs.push(options.rootDir);
- }
- else {
- rootDirs.push(cwd);
- }
- // In Windows the above might not always return posix separated paths
- // See:
- // https://github.com/Microsoft/TypeScript/blob/3f7357d37f66c842d70d835bc925ec2a873ecfec/src/compiler/sys.ts#L650
- // Also compiler options might be set via an API which doesn't normalize paths
- return rootDirs.map((rootDir) => fs.resolve(cwd, host.getCanonicalFileName(rootDir)));
- }
- function nodeDebugInfo(node) {
- const sf = getSourceFile(node);
- const { line, character } = ts.getLineAndCharacterOfPosition(sf, node.pos);
- return `[${sf.fileName}: ${ts.SyntaxKind[node.kind]} @ ${line}:${character}]`;
- }
- /**
- * Resolve the specified `moduleName` using the given `compilerOptions` and `compilerHost`.
- *
- * This helper will attempt to use the `CompilerHost.resolveModuleNames()` method if available.
- * Otherwise it will fallback on the `ts.ResolveModuleName()` function.
- */
- function resolveModuleName(moduleName, containingFile, compilerOptions, compilerHost, moduleResolutionCache) {
- if (compilerHost.resolveModuleNames) {
- return compilerHost.resolveModuleNames([moduleName], containingFile, undefined, // reusedNames
- undefined, // redirectedReference
- compilerOptions)[0];
- }
- else {
- return ts.resolveModuleName(moduleName, containingFile, compilerOptions, compilerHost, moduleResolutionCache !== null ? moduleResolutionCache : undefined).resolvedModule;
- }
- }
- /** Returns true if the node is an assignment expression. */
- function isAssignment(node) {
- return ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken;
- }
- /**
- * Obtains the non-redirected source file for `sf`.
- */
- function toUnredirectedSourceFile(sf) {
- const redirectInfo = sf.redirectInfo;
- if (redirectInfo === undefined) {
- return sf;
- }
- return redirectInfo.unredirected;
- }
- /**
- * Find the name, if any, by which a node is exported from a given file.
- */
- function findExportedNameOfNode(target, file, reflector) {
- const exports = reflector.getExportsOfModule(file);
- if (exports === null) {
- return null;
- }
- const declaredName = isNamedDeclaration(target) ? target.name.text : null;
- // Look for the export which declares the node.
- let foundExportName = null;
- for (const [exportName, declaration] of exports) {
- if (declaration.node !== target) {
- continue;
- }
- if (exportName === declaredName) {
- // A non-alias export exists which is always preferred, so use that one.
- return exportName;
- }
- foundExportName = exportName;
- }
- return foundExportName;
- }
- /**
- * Flags which alter the imports generated by the `ReferenceEmitter`.
- */
- exports.ImportFlags = void 0;
- (function (ImportFlags) {
- ImportFlags[ImportFlags["None"] = 0] = "None";
- /**
- * Force the generation of a new import when generating a reference, even if an identifier already
- * exists in the target file which could be used instead.
- *
- * This is sometimes required if there's a risk TypeScript might remove imports during emit.
- */
- ImportFlags[ImportFlags["ForceNewImport"] = 1] = "ForceNewImport";
- /**
- * Don't make use of any aliasing information when emitting a reference.
- *
- * This is sometimes required if emitting into a context where generated references will be fed
- * into TypeScript and type-checked (such as in template type-checking).
- */
- ImportFlags[ImportFlags["NoAliasing"] = 2] = "NoAliasing";
- /**
- * Indicates that an import to a type-only declaration is allowed.
- *
- * For references that occur in type-positions, the referred declaration may be a type-only
- * declaration that is not retained during emit. Including this flag allows to emit references to
- * type-only declarations as used in e.g. template type-checking.
- */
- ImportFlags[ImportFlags["AllowTypeImports"] = 4] = "AllowTypeImports";
- /**
- * Indicates that importing from a declaration file using a relative import path is allowed.
- *
- * The generated imports should normally use module specifiers that are valid for use in
- * production code, where arbitrary relative imports into e.g. node_modules are not allowed. For
- * template type-checking code it is however acceptable to use relative imports, as such files are
- * never emitted to JS code.
- *
- * Non-declaration files have to be contained within a configured `rootDir` so using relative
- * paths may not be possible for those, hence this flag only applies when importing from a
- * declaration file.
- */
- ImportFlags[ImportFlags["AllowRelativeDtsImports"] = 8] = "AllowRelativeDtsImports";
- /**
- * Indicates that references coming from ambient imports are allowed.
- */
- ImportFlags[ImportFlags["AllowAmbientReferences"] = 16] = "AllowAmbientReferences";
- })(exports.ImportFlags || (exports.ImportFlags = {}));
- exports.ReferenceEmitKind = void 0;
- (function (ReferenceEmitKind) {
- ReferenceEmitKind[ReferenceEmitKind["Success"] = 0] = "Success";
- ReferenceEmitKind[ReferenceEmitKind["Failed"] = 1] = "Failed";
- })(exports.ReferenceEmitKind || (exports.ReferenceEmitKind = {}));
- /**
- * Verifies that a reference was emitted successfully, or raises a `FatalDiagnosticError` otherwise.
- * @param result The emit result that should have been successful.
- * @param origin The node that is used to report the failure diagnostic.
- * @param typeKind The kind of the symbol that the reference represents, e.g. 'component' or
- * 'class'.
- */
- function assertSuccessfulReferenceEmit(result, origin, typeKind) {
- if (result.kind === exports.ReferenceEmitKind.Success) {
- return;
- }
- const message = makeDiagnosticChain(`Unable to import ${typeKind} ${nodeNameForError(result.ref.node)}.`, [makeDiagnosticChain(result.reason)]);
- throw new FatalDiagnosticError(exports.ErrorCode.IMPORT_GENERATION_FAILURE, origin, message, [
- makeRelatedInformation(result.ref.node, `The ${typeKind} is declared here.`),
- ]);
- }
- /**
- * Generates `Expression`s which refer to `Reference`s in a given context.
- *
- * A `ReferenceEmitter` uses one or more `ReferenceEmitStrategy` implementations to produce an
- * `Expression` which refers to a `Reference` in the context of a particular file.
- */
- class ReferenceEmitter {
- strategies;
- constructor(strategies) {
- this.strategies = strategies;
- }
- emit(ref, context, importFlags = exports.ImportFlags.None) {
- for (const strategy of this.strategies) {
- const emitted = strategy.emit(ref, context, importFlags);
- if (emitted !== null) {
- return emitted;
- }
- }
- return {
- kind: exports.ReferenceEmitKind.Failed,
- ref,
- context,
- reason: `Unable to write a reference to ${nodeNameForError(ref.node)}.`,
- };
- }
- }
- /**
- * A `ReferenceEmitStrategy` which will refer to declarations by any local `ts.Identifier`s, if
- * such identifiers are available.
- */
- class LocalIdentifierStrategy {
- emit(ref, context, importFlags) {
- const refSf = getSourceFile(ref.node);
- // If the emitter has specified ForceNewImport, then LocalIdentifierStrategy should not use a
- // local identifier at all, *except* in the source file where the node is actually declared.
- if (importFlags & exports.ImportFlags.ForceNewImport && refSf !== context) {
- return null;
- }
- // If referenced node is not an actual TS declaration (e.g. `class Foo` or `function foo() {}`,
- // etc) and it is in the current file then just use it directly.
- // This is important because the reference could be a property access (e.g. `exports.foo`). In
- // such a case, the reference's `identities` property would be `[foo]`, which would result in an
- // invalid emission of a free-standing `foo` identifier, rather than `exports.foo`.
- if (!isDeclaration(ref.node) && refSf === context) {
- return {
- kind: exports.ReferenceEmitKind.Success,
- expression: new WrappedNodeExpr(ref.node),
- importedFile: null,
- };
- }
- // If the reference is to an ambient type, it can be referenced directly.
- if (ref.isAmbient && importFlags & exports.ImportFlags.AllowAmbientReferences) {
- const identifier = identifierOfNode(ref.node);
- if (identifier !== null) {
- return {
- kind: exports.ReferenceEmitKind.Success,
- expression: new WrappedNodeExpr(identifier),
- importedFile: null,
- };
- }
- else {
- return null;
- }
- }
- // A Reference can have multiple identities in different files, so it may already have an
- // Identifier in the requested context file.
- const identifier = ref.getIdentityIn(context);
- if (identifier !== null) {
- return {
- kind: exports.ReferenceEmitKind.Success,
- expression: new WrappedNodeExpr(identifier),
- importedFile: null,
- };
- }
- else {
- return null;
- }
- }
- }
- /**
- * A `ReferenceEmitStrategy` which will refer to declarations that come from `node_modules` using
- * an absolute import.
- *
- * Part of this strategy involves looking at the target entry point and identifying the exported
- * name of the targeted declaration, as it might be different from the declared name (e.g. a
- * directive might be declared as FooDirImpl, but exported as FooDir). If no export can be found
- * which maps back to the original directive, an error is thrown.
- */
- class AbsoluteModuleStrategy {
- program;
- checker;
- moduleResolver;
- reflectionHost;
- /**
- * A cache of the exports of specific modules, because resolving a module to its exports is a
- * costly operation.
- */
- moduleExportsCache = new Map();
- constructor(program, checker, moduleResolver, reflectionHost) {
- this.program = program;
- this.checker = checker;
- this.moduleResolver = moduleResolver;
- this.reflectionHost = reflectionHost;
- }
- emit(ref, context, importFlags) {
- if (ref.bestGuessOwningModule === null) {
- // There is no module name available for this Reference, meaning it was arrived at via a
- // relative path.
- return null;
- }
- else if (!isDeclaration(ref.node)) {
- // It's not possible to import something which isn't a declaration.
- throw new Error(`Debug assert: unable to import a Reference to non-declaration of type ${ts.SyntaxKind[ref.node.kind]}.`);
- }
- else if ((importFlags & exports.ImportFlags.AllowTypeImports) === 0 && isTypeDeclaration(ref.node)) {
- throw new Error(`Importing a type-only declaration of type ${ts.SyntaxKind[ref.node.kind]} in a value position is not allowed.`);
- }
- // Try to find the exported name of the declaration, if one is available.
- const { specifier, resolutionContext } = ref.bestGuessOwningModule;
- const exports$1 = this.getExportsOfModule(specifier, resolutionContext);
- if (exports$1.module === null) {
- return {
- kind: exports.ReferenceEmitKind.Failed,
- ref,
- context,
- reason: `The module '${specifier}' could not be found.`,
- };
- }
- else if (exports$1.exportMap === null || !exports$1.exportMap.has(ref.node)) {
- return {
- kind: exports.ReferenceEmitKind.Failed,
- ref,
- context,
- reason: `The symbol is not exported from ${exports$1.module.fileName} (module '${specifier}').`,
- };
- }
- const symbolName = exports$1.exportMap.get(ref.node);
- return {
- kind: exports.ReferenceEmitKind.Success,
- expression: new ExternalExpr(new ExternalReference(specifier, symbolName)),
- importedFile: exports$1.module,
- };
- }
- getExportsOfModule(moduleName, fromFile) {
- if (!this.moduleExportsCache.has(moduleName)) {
- this.moduleExportsCache.set(moduleName, this.enumerateExportsOfModule(moduleName, fromFile));
- }
- return this.moduleExportsCache.get(moduleName);
- }
- enumerateExportsOfModule(specifier, fromFile) {
- // First, resolve the module specifier to its entry point, and get the ts.Symbol for it.
- const entryPointFile = this.moduleResolver.resolveModule(specifier, fromFile);
- if (entryPointFile === null) {
- return { module: null, exportMap: null };
- }
- const exports = this.reflectionHost.getExportsOfModule(entryPointFile);
- if (exports === null) {
- return { module: entryPointFile, exportMap: null };
- }
- const exportMap = new Map();
- for (const [name, declaration] of exports) {
- if (exportMap.has(declaration.node)) {
- // An export for this declaration has already been registered. We prefer an export that
- // has the same name as the declared name, i.e. is not an aliased export. This is relevant
- // for partial compilations where emitted references should import symbols using a stable
- // name. This is particularly relevant for declarations inside VE-generated libraries, as
- // such libraries contain private, unstable reexports of symbols.
- const existingExport = exportMap.get(declaration.node);
- if (isNamedDeclaration(declaration.node) && declaration.node.name.text === existingExport) {
- continue;
- }
- }
- exportMap.set(declaration.node, name);
- }
- return { module: entryPointFile, exportMap };
- }
- }
- /**
- * A `ReferenceEmitStrategy` which will refer to declarations via relative paths, provided they're
- * both in the logical project "space" of paths.
- *
- * This is trickier than it sounds, as the two files may be in different root directories in the
- * project. Simply calculating a file system relative path between the two is not sufficient.
- * Instead, `LogicalProjectPath`s are used.
- */
- class LogicalProjectStrategy {
- reflector;
- logicalFs;
- relativePathStrategy;
- constructor(reflector, logicalFs) {
- this.reflector = reflector;
- this.logicalFs = logicalFs;
- this.relativePathStrategy = new RelativePathStrategy(this.reflector);
- }
- emit(ref, context, importFlags) {
- const destSf = getSourceFile(ref.node);
- // Compute the relative path from the importing file to the file being imported. This is done
- // as a logical path computation, because the two files might be in different rootDirs.
- const destPath = this.logicalFs.logicalPathOfSf(destSf);
- if (destPath === null) {
- // The imported file is not within the logical project filesystem. An import into a
- // declaration file is exempt from `TS6059: File is not under 'rootDir'` so we choose to allow
- // using a filesystem relative path as fallback, if allowed per the provided import flags.
- if (destSf.isDeclarationFile && importFlags & exports.ImportFlags.AllowRelativeDtsImports) {
- return this.relativePathStrategy.emit(ref, context);
- }
- // Note: this error is analogous to `TS6059: File is not under 'rootDir'` that TypeScript
- // reports.
- return {
- kind: exports.ReferenceEmitKind.Failed,
- ref,
- context,
- reason: `The file ${destSf.fileName} is outside of the configured 'rootDir'.`,
- };
- }
- const originPath = this.logicalFs.logicalPathOfSf(context);
- if (originPath === null) {
- throw new Error(`Debug assert: attempt to import from ${context.fileName} but it's outside the program?`);
- }
- // There's no way to emit a relative reference from a file to itself.
- if (destPath === originPath) {
- return null;
- }
- const name = findExportedNameOfNode(ref.node, destSf, this.reflector);
- if (name === null) {
- // The target declaration isn't exported from the file it's declared in. This is an issue!
- return {
- kind: exports.ReferenceEmitKind.Failed,
- ref,
- context,
- reason: `The symbol is not exported from ${destSf.fileName}.`,
- };
- }
- // With both files expressed as LogicalProjectPaths, getting the module specifier as a relative
- // path is now straightforward.
- const moduleName = LogicalProjectPath.relativePathBetween(originPath, destPath);
- return {
- kind: exports.ReferenceEmitKind.Success,
- expression: new ExternalExpr({ moduleName, name }),
- importedFile: destSf,
- };
- }
- }
- /**
- * A `ReferenceEmitStrategy` which constructs relatives paths between `ts.SourceFile`s.
- *
- * This strategy can be used if there is no `rootDir`/`rootDirs` structure for the project which
- * necessitates the stronger logic of `LogicalProjectStrategy`.
- */
- class RelativePathStrategy {
- reflector;
- constructor(reflector) {
- this.reflector = reflector;
- }
- emit(ref, context) {
- const destSf = getSourceFile(ref.node);
- const relativePath = relative(dirname(absoluteFromSourceFile(context)), absoluteFromSourceFile(destSf));
- const moduleName = toRelativeImport(stripExtension(relativePath));
- const name = findExportedNameOfNode(ref.node, destSf, this.reflector);
- if (name === null) {
- return {
- kind: exports.ReferenceEmitKind.Failed,
- ref,
- context,
- reason: `The symbol is not exported from ${destSf.fileName}.`,
- };
- }
- return {
- kind: exports.ReferenceEmitKind.Success,
- expression: new ExternalExpr({ moduleName, name }),
- importedFile: destSf,
- };
- }
- }
- /**
- * A `ReferenceEmitStrategy` which uses a `UnifiedModulesHost` to generate absolute import
- * references.
- */
- class UnifiedModulesStrategy {
- reflector;
- unifiedModulesHost;
- constructor(reflector, unifiedModulesHost) {
- this.reflector = reflector;
- this.unifiedModulesHost = unifiedModulesHost;
- }
- emit(ref, context) {
- const destSf = getSourceFile(ref.node);
- const name = findExportedNameOfNode(ref.node, destSf, this.reflector);
- if (name === null) {
- return null;
- }
- const moduleName = this.unifiedModulesHost.fileNameToModuleName(destSf.fileName, context.fileName);
- return {
- kind: exports.ReferenceEmitKind.Success,
- expression: new ExternalExpr({ moduleName, name }),
- importedFile: destSf,
- };
- }
- }
- const patchedReferencedAliasesSymbol = Symbol('patchedReferencedAliases');
- /**
- * Patches the alias declaration reference resolution for a given transformation context
- * so that TypeScript knows about the specified alias declarations being referenced.
- *
- * This exists because TypeScript performs analysis of import usage before transformers
- * run and doesn't refresh its state after transformations. This means that imports
- * for symbols used as constructor types are elided due to their original type-only usage.
- *
- * In reality though, since we downlevel decorators and constructor parameters, we want
- * these symbols to be retained in the JavaScript output as they will be used as values
- * at runtime. We can instruct TypeScript to preserve imports for such identifiers by
- * creating a mutable clone of a given import specifier/clause or namespace, but that
- * has the downside of preserving the full import in the JS output. See:
- * https://github.com/microsoft/TypeScript/blob/3eaa7c65f6f076a08a5f7f1946fd0df7c7430259/src/compiler/transformers/ts.ts#L242-L250.
- *
- * This is a trick the CLI used in the past for constructor parameter downleveling in JIT:
- * https://github.com/angular/angular-cli/blob/b3f84cc5184337666ce61c07b7b9df418030106f/packages/ngtools/webpack/src/transformers/ctor-parameters.ts#L323-L325
- * The trick is not ideal though as it preserves the full import (as outlined before), and it
- * results in a slow-down due to the type checker being involved multiple times. The CLI worked
- * around this import preserving issue by having another complex post-process step that detects and
- * elides unused imports. Note that these unused imports could cause unused chunks being generated
- * by webpack if the application or library is not marked as side-effect free.
- *
- * This is not ideal though, as we basically re-implement the complex import usage resolution
- * from TypeScript. We can do better by letting TypeScript do the import eliding, but providing
- * information about the alias declarations (e.g. import specifiers) that should not be elided
- * because they are actually referenced (as they will now appear in static properties).
- *
- * More information about these limitations with transformers can be found in:
- * 1. https://github.com/Microsoft/TypeScript/issues/17552.
- * 2. https://github.com/microsoft/TypeScript/issues/17516.
- * 3. https://github.com/angular/tsickle/issues/635.
- *
- * The patch we apply to tell TypeScript about actual referenced aliases (i.e. imported symbols),
- * matches conceptually with the logic that runs internally in TypeScript when the
- * `emitDecoratorMetadata` flag is enabled. TypeScript basically surfaces the same problem and
- * solves it conceptually the same way, but obviously doesn't need to access an internal API.
- *
- * The set that is returned by this function is meant to be filled with import declaration nodes
- * that have been referenced in a value-position by the transform, such the installed patch can
- * ensure that those import declarations are not elided.
- *
- * If `null` is returned then the transform operates in an isolated context, i.e. using the
- * `ts.transform` API. In such scenario there is no information whether an alias declaration
- * is referenced, so all alias declarations are naturally preserved and explicitly registering
- * an alias declaration as used isn't necessary.
- *
- * See below. Note that this uses sourcegraph as the TypeScript checker file doesn't display on
- * Github.
- * https://sourcegraph.com/github.com/microsoft/TypeScript@3eaa7c65f6f076a08a5f7f1946fd0df7c7430259/-/blob/src/compiler/checker.ts#L31219-31257
- */
- function loadIsReferencedAliasDeclarationPatch(context) {
- // If the `getEmitResolver` method is not available, TS most likely changed the
- // internal structure of the transformation context. We will abort gracefully.
- if (!isTransformationContextWithEmitResolver(context)) {
- throwIncompatibleTransformationContextError();
- }
- const emitResolver = context.getEmitResolver();
- if (emitResolver === undefined) {
- // In isolated `ts.transform` operations no emit resolver is present, return null as `isReferencedAliasDeclaration`
- // will never be invoked.
- return null;
- }
- // The emit resolver may have been patched already, in which case we return the set of referenced
- // aliases that was created when the patch was first applied.
- // See https://github.com/angular/angular/issues/40276.
- const existingReferencedAliases = emitResolver[patchedReferencedAliasesSymbol];
- if (existingReferencedAliases !== undefined) {
- return existingReferencedAliases;
- }
- const originalIsReferencedAliasDeclaration = emitResolver.isReferencedAliasDeclaration;
- // If the emit resolver does not have a function called `isReferencedAliasDeclaration`, then
- // we abort gracefully as most likely TS changed the internal structure of the emit resolver.
- if (originalIsReferencedAliasDeclaration === undefined) {
- throwIncompatibleTransformationContextError();
- }
- const referencedAliases = new Set();
- emitResolver.isReferencedAliasDeclaration = function (node, ...args) {
- if (isAliasImportDeclaration(node) && referencedAliases.has(node)) {
- return true;
- }
- return originalIsReferencedAliasDeclaration.call(emitResolver, node, ...args);
- };
- return (emitResolver[patchedReferencedAliasesSymbol] = referencedAliases);
- }
- /**
- * Gets whether a given node corresponds to an import alias declaration. Alias
- * declarations can be import specifiers, namespace imports or import clauses
- * as these do not declare an actual symbol but just point to a target declaration.
- */
- function isAliasImportDeclaration(node) {
- return ts.isImportSpecifier(node) || ts.isNamespaceImport(node) || ts.isImportClause(node);
- }
- /** Whether the transformation context exposes its emit resolver. */
- function isTransformationContextWithEmitResolver(context) {
- return context.getEmitResolver !== undefined;
- }
- /**
- * Throws an error about an incompatible TypeScript version for which the alias
- * declaration reference resolution could not be monkey-patched. The error will
- * also propose potential solutions that can be applied by developers.
- */
- function throwIncompatibleTransformationContextError() {
- throw Error('Angular compiler is incompatible with this version of the TypeScript compiler.\n\n' +
- 'If you recently updated TypeScript and this issue surfaces now, consider downgrading.\n\n' +
- 'Please report an issue on the Angular repositories when this issue ' +
- 'surfaces and you are using a supposedly compatible TypeScript version.');
- }
- const DefaultImportDeclaration = Symbol('DefaultImportDeclaration');
- /**
- * Attaches a default import declaration to `expr` to indicate the dependency of `expr` on the
- * default import.
- */
- function attachDefaultImportDeclaration(expr, importDecl) {
- expr[DefaultImportDeclaration] = importDecl;
- }
- /**
- * Obtains the default import declaration that `expr` depends on, or `null` if there is no such
- * dependency.
- */
- function getDefaultImportDeclaration(expr) {
- return expr[DefaultImportDeclaration] ?? null;
- }
- /**
- * TypeScript has trouble with generating default imports inside of transformers for some module
- * formats. The issue is that for the statement:
- *
- * import X from 'some/module';
- * console.log(X);
- *
- * TypeScript will not use the "X" name in generated code. For normal user code, this is fine
- * because references to X will also be renamed. However, if both the import and any references are
- * added in a transformer, TypeScript does not associate the two, and will leave the "X" references
- * dangling while renaming the import variable. The generated code looks something like:
- *
- * const module_1 = require('some/module');
- * console.log(X); // now X is a dangling reference.
- *
- * Therefore, we cannot synthetically add default imports, and must reuse the imports that users
- * include. Doing this poses a challenge for imports that are only consumed in the type position in
- * the user's code. If Angular reuses the imported symbol in a value position (for example, we
- * see a constructor parameter of type Foo and try to write "inject(Foo)") we will also end up with
- * a dangling reference, as TS will elide the import because it was only used in the type position
- * originally.
- *
- * To avoid this, the compiler must patch the emit resolver, and should only do this for imports
- * which are actually consumed. The `DefaultImportTracker` keeps track of these imports as they're
- * encountered and emitted, and implements a transform which can correctly flag the imports as
- * required.
- *
- * This problem does not exist for non-default imports as the compiler can easily insert
- * "import * as X" style imports for those, and the "X" identifier survives transformation.
- */
- class DefaultImportTracker {
- /**
- * A `Map` which tracks the `Set` of `ts.ImportClause`s for default imports that were used in
- * a given file name.
- */
- sourceFileToUsedImports = new Map();
- recordUsedImport(importDecl) {
- if (importDecl.importClause) {
- const sf = getSourceFile(importDecl);
- // Add the default import declaration to the set of used import declarations for the file.
- if (!this.sourceFileToUsedImports.has(sf.fileName)) {
- this.sourceFileToUsedImports.set(sf.fileName, new Set());
- }
- this.sourceFileToUsedImports.get(sf.fileName).add(importDecl.importClause);
- }
- }
- /**
- * Get a `ts.TransformerFactory` which will preserve default imports that were previously marked
- * as used.
- *
- * This transformer must run after any other transformers which call `recordUsedImport`.
- */
- importPreservingTransformer() {
- return (context) => {
- let clausesToPreserve = null;
- return (sourceFile) => {
- const clausesForFile = this.sourceFileToUsedImports.get(sourceFile.fileName);
- if (clausesForFile !== undefined) {
- for (const clause of clausesForFile) {
- // Initialize the patch lazily so that apps that
- // don't use default imports aren't patched.
- if (clausesToPreserve === null) {
- clausesToPreserve = loadIsReferencedAliasDeclarationPatch(context);
- }
- clausesToPreserve?.add(clause);
- }
- }
- return sourceFile;
- };
- };
- }
- }
- function isDecoratorIdentifier(exp) {
- return (ts.isIdentifier(exp) ||
- (ts.isPropertyAccessExpression(exp) &&
- ts.isIdentifier(exp.expression) &&
- ts.isIdentifier(exp.name)));
- }
- /**
- * An enumeration of possible kinds of class members.
- */
- exports.ClassMemberKind = void 0;
- (function (ClassMemberKind) {
- ClassMemberKind[ClassMemberKind["Constructor"] = 0] = "Constructor";
- ClassMemberKind[ClassMemberKind["Getter"] = 1] = "Getter";
- ClassMemberKind[ClassMemberKind["Setter"] = 2] = "Setter";
- ClassMemberKind[ClassMemberKind["Property"] = 3] = "Property";
- ClassMemberKind[ClassMemberKind["Method"] = 4] = "Method";
- })(exports.ClassMemberKind || (exports.ClassMemberKind = {}));
- /** Possible access levels of a class member. */
- exports.ClassMemberAccessLevel = void 0;
- (function (ClassMemberAccessLevel) {
- ClassMemberAccessLevel[ClassMemberAccessLevel["PublicWritable"] = 0] = "PublicWritable";
- ClassMemberAccessLevel[ClassMemberAccessLevel["PublicReadonly"] = 1] = "PublicReadonly";
- ClassMemberAccessLevel[ClassMemberAccessLevel["Protected"] = 2] = "Protected";
- ClassMemberAccessLevel[ClassMemberAccessLevel["Private"] = 3] = "Private";
- ClassMemberAccessLevel[ClassMemberAccessLevel["EcmaScriptPrivate"] = 4] = "EcmaScriptPrivate";
- })(exports.ClassMemberAccessLevel || (exports.ClassMemberAccessLevel = {}));
- /** Indicates that a declaration is referenced through an ambient type. */
- const AmbientImport = {};
- /**
- * Potentially convert a `ts.TypeNode` to a `TypeValueReference`, which indicates how to use the
- * type given in the `ts.TypeNode` in a value position.
- *
- * This can return `null` if the `typeNode` is `null`, if it does not refer to a symbol with a value
- * declaration, or if it is not possible to statically understand.
- */
- function typeToValue(typeNode, checker, isLocalCompilation) {
- // It's not possible to get a value expression if the parameter doesn't even have a type.
- if (typeNode === null) {
- return missingType();
- }
- if (!ts.isTypeReferenceNode(typeNode)) {
- return unsupportedType(typeNode);
- }
- const symbols = resolveTypeSymbols(typeNode, checker);
- if (symbols === null) {
- return unknownReference(typeNode);
- }
- const { local, decl } = symbols;
- // It's only valid to convert a type reference to a value reference if the type actually
- // has a value declaration associated with it. Note that const enums are an exception,
- // because while they do have a value declaration, they don't exist at runtime.
- if (decl.valueDeclaration === undefined || decl.flags & ts.SymbolFlags.ConstEnum) {
- let typeOnlyDecl = null;
- if (decl.declarations !== undefined && decl.declarations.length > 0) {
- typeOnlyDecl = decl.declarations[0];
- }
- // In local compilation mode a declaration is considered invalid only if it is a type related
- // declaration.
- if (!isLocalCompilation ||
- (typeOnlyDecl &&
- [
- ts.SyntaxKind.TypeParameter,
- ts.SyntaxKind.TypeAliasDeclaration,
- ts.SyntaxKind.InterfaceDeclaration,
- ].includes(typeOnlyDecl.kind))) {
- return noValueDeclaration(typeNode, typeOnlyDecl);
- }
- }
- // The type points to a valid value declaration. Rewrite the TypeReference into an
- // Expression which references the value pointed to by the TypeReference, if possible.
- // Look at the local `ts.Symbol`'s declarations and see if it comes from an import
- // statement. If so, extract the module specifier and the name of the imported type.
- const firstDecl = local.declarations && local.declarations[0];
- if (firstDecl !== undefined) {
- if (ts.isImportClause(firstDecl) && firstDecl.name !== undefined) {
- // This is a default import.
- // import Foo from 'foo';
- if (firstDecl.isTypeOnly) {
- // Type-only imports cannot be represented as value.
- return typeOnlyImport(typeNode, firstDecl);
- }
- if (!ts.isImportDeclaration(firstDecl.parent)) {
- return unsupportedType(typeNode);
- }
- return {
- kind: 0 /* TypeValueReferenceKind.LOCAL */,
- expression: firstDecl.name,
- defaultImportStatement: firstDecl.parent,
- };
- }
- else if (ts.isImportSpecifier(firstDecl)) {
- // The symbol was imported by name
- // import {Foo} from 'foo';
- // or
- // import {Foo as Bar} from 'foo';
- if (firstDecl.isTypeOnly) {
- // The import specifier can't be type-only (e.g. `import {type Foo} from '...')`.
- return typeOnlyImport(typeNode, firstDecl);
- }
- if (firstDecl.parent.parent.isTypeOnly) {
- // The import specifier can't be inside a type-only import clause
- // (e.g. `import type {Foo} from '...')`.
- return typeOnlyImport(typeNode, firstDecl.parent.parent);
- }
- // Determine the name to import (`Foo`) from the import specifier, as the symbol names of
- // the imported type could refer to a local alias (like `Bar` in the example above).
- const importedName = (firstDecl.propertyName || firstDecl.name).text;
- // The first symbol name refers to the local name, which is replaced by `importedName` above.
- // Any remaining symbol names make up the complete path to the value.
- const [_localName, ...nestedPath] = symbols.symbolNames;
- const importDeclaration = firstDecl.parent.parent.parent;
- if (!ts.isImportDeclaration(importDeclaration)) {
- return unsupportedType(typeNode);
- }
- const moduleName = extractModuleName(importDeclaration);
- return {
- kind: 1 /* TypeValueReferenceKind.IMPORTED */,
- valueDeclaration: decl.valueDeclaration ?? null,
- moduleName,
- importedName,
- nestedPath,
- };
- }
- else if (ts.isNamespaceImport(firstDecl)) {
- // The import is a namespace import
- // import * as Foo from 'foo';
- if (firstDecl.parent.isTypeOnly) {
- // Type-only imports cannot be represented as value.
- return typeOnlyImport(typeNode, firstDecl.parent);
- }
- if (symbols.symbolNames.length === 1) {
- // The type refers to the namespace itself, which cannot be represented as a value.
- return namespaceImport(typeNode, firstDecl.parent);
- }
- // The first symbol name refers to the local name of the namespace, which is is discarded
- // as a new namespace import will be generated. This is followed by the symbol name that needs
- // to be imported and any remaining names that constitute the complete path to the value.
- const [_ns, importedName, ...nestedPath] = symbols.symbolNames;
- const importDeclaration = firstDecl.parent.parent;
- if (!ts.isImportDeclaration(importDeclaration)) {
- return unsupportedType(typeNode);
- }
- const moduleName = extractModuleName(importDeclaration);
- return {
- kind: 1 /* TypeValueReferenceKind.IMPORTED */,
- valueDeclaration: decl.valueDeclaration ?? null,
- moduleName,
- importedName,
- nestedPath,
- };
- }
- }
- // If the type is not imported, the type reference can be converted into an expression as is.
- const expression = typeNodeToValueExpr(typeNode);
- if (expression !== null) {
- return {
- kind: 0 /* TypeValueReferenceKind.LOCAL */,
- expression,
- defaultImportStatement: null,
- };
- }
- else {
- return unsupportedType(typeNode);
- }
- }
- function unsupportedType(typeNode) {
- return {
- kind: 2 /* TypeValueReferenceKind.UNAVAILABLE */,
- reason: { kind: 5 /* ValueUnavailableKind.UNSUPPORTED */, typeNode },
- };
- }
- function noValueDeclaration(typeNode, decl) {
- return {
- kind: 2 /* TypeValueReferenceKind.UNAVAILABLE */,
- reason: { kind: 1 /* ValueUnavailableKind.NO_VALUE_DECLARATION */, typeNode, decl },
- };
- }
- function typeOnlyImport(typeNode, node) {
- return {
- kind: 2 /* TypeValueReferenceKind.UNAVAILABLE */,
- reason: { kind: 2 /* ValueUnavailableKind.TYPE_ONLY_IMPORT */, typeNode, node },
- };
- }
- function unknownReference(typeNode) {
- return {
- kind: 2 /* TypeValueReferenceKind.UNAVAILABLE */,
- reason: { kind: 3 /* ValueUnavailableKind.UNKNOWN_REFERENCE */, typeNode },
- };
- }
- function namespaceImport(typeNode, importClause) {
- return {
- kind: 2 /* TypeValueReferenceKind.UNAVAILABLE */,
- reason: { kind: 4 /* ValueUnavailableKind.NAMESPACE */, typeNode, importClause },
- };
- }
- function missingType() {
- return {
- kind: 2 /* TypeValueReferenceKind.UNAVAILABLE */,
- reason: { kind: 0 /* ValueUnavailableKind.MISSING_TYPE */ },
- };
- }
- /**
- * Attempt to extract a `ts.Expression` that's equivalent to a `ts.TypeNode`, as the two have
- * different AST shapes but can reference the same symbols.
- *
- * This will return `null` if an equivalent expression cannot be constructed.
- */
- function typeNodeToValueExpr(node) {
- if (ts.isTypeReferenceNode(node)) {
- return entityNameToValue(node.typeName);
- }
- else {
- return null;
- }
- }
- /**
- * Resolve a `TypeReference` node to the `ts.Symbol`s for both its declaration and its local source.
- *
- * In the event that the `TypeReference` refers to a locally declared symbol, these will be the
- * same. If the `TypeReference` refers to an imported symbol, then `decl` will be the fully resolved
- * `ts.Symbol` of the referenced symbol. `local` will be the `ts.Symbol` of the `ts.Identifier`
- * which points to the import statement by which the symbol was imported.
- *
- * All symbol names that make up the type reference are returned left-to-right into the
- * `symbolNames` array, which is guaranteed to include at least one entry.
- */
- function resolveTypeSymbols(typeRef, checker) {
- const typeName = typeRef.typeName;
- // typeRefSymbol is the ts.Symbol of the entire type reference.
- const typeRefSymbol = checker.getSymbolAtLocation(typeName);
- if (typeRefSymbol === undefined) {
- return null;
- }
- // `local` is the `ts.Symbol` for the local `ts.Identifier` for the type.
- // If the type is actually locally declared or is imported by name, for example:
- // import {Foo} from './foo';
- // then it'll be the same as `typeRefSymbol`.
- //
- // If the type is imported via a namespace import, for example:
- // import * as foo from './foo';
- // and then referenced as:
- // constructor(f: foo.Foo)
- // then `local` will be the `ts.Symbol` of `foo`, whereas `typeRefSymbol` will be the `ts.Symbol`
- // of `foo.Foo`. This allows tracking of the import behind whatever type reference exists.
- let local = typeRefSymbol;
- // Destructure a name like `foo.X.Y.Z` as follows:
- // - in `leftMost`, the `ts.Identifier` of the left-most name (`foo`) in the qualified name.
- // This identifier is used to resolve the `ts.Symbol` for `local`.
- // - in `symbolNames`, all names involved in the qualified path, or a single symbol name if the
- // type is not qualified.
- let leftMost = typeName;
- const symbolNames = [];
- while (ts.isQualifiedName(leftMost)) {
- symbolNames.unshift(leftMost.right.text);
- leftMost = leftMost.left;
- }
- symbolNames.unshift(leftMost.text);
- if (leftMost !== typeName) {
- const localTmp = checker.getSymbolAtLocation(leftMost);
- if (localTmp !== undefined) {
- local = localTmp;
- }
- }
- // De-alias the top-level type reference symbol to get the symbol of the actual declaration.
- let decl = typeRefSymbol;
- if (typeRefSymbol.flags & ts.SymbolFlags.Alias) {
- decl = checker.getAliasedSymbol(typeRefSymbol);
- }
- return { local, decl, symbolNames };
- }
- function entityNameToValue(node) {
- if (ts.isQualifiedName(node)) {
- const left = entityNameToValue(node.left);
- return left !== null ? ts.factory.createPropertyAccessExpression(left, node.right) : null;
- }
- else if (ts.isIdentifier(node)) {
- const clone = ts.setOriginalNode(ts.factory.createIdentifier(node.text), node);
- clone.parent = node.parent;
- return clone;
- }
- else {
- return null;
- }
- }
- function extractModuleName(node) {
- if (!ts.isStringLiteral(node.moduleSpecifier)) {
- throw new Error('not a module specifier');
- }
- return node.moduleSpecifier.text;
- }
- function isNamedClassDeclaration(node) {
- return ts.isClassDeclaration(node) && isIdentifier(node.name);
- }
- function isIdentifier(node) {
- return node !== undefined && ts.isIdentifier(node);
- }
- /**
- * Converts the given class member access level to a string.
- * Useful fo error messages.
- */
- function classMemberAccessLevelToString(level) {
- switch (level) {
- case exports.ClassMemberAccessLevel.EcmaScriptPrivate:
- return 'ES private';
- case exports.ClassMemberAccessLevel.Private:
- return 'private';
- case exports.ClassMemberAccessLevel.Protected:
- return 'protected';
- case exports.ClassMemberAccessLevel.PublicReadonly:
- return 'public readonly';
- case exports.ClassMemberAccessLevel.PublicWritable:
- default:
- return 'public';
- }
- }
- /**
- * reflector.ts implements static reflection of declarations using the TypeScript `ts.TypeChecker`.
- */
- class TypeScriptReflectionHost {
- checker;
- isLocalCompilation;
- skipPrivateValueDeclarationTypes;
- /**
- * @param skipPrivateValueDeclarationTypes Avoids using a value declaration that is considered private (using a ɵ-prefix),
- * instead using the first available declaration. This is needed for the {@link FormControl} API of
- * which the type declaration documents the type and the value declaration corresponds with an implementation detail.
- */
- constructor(checker, isLocalCompilation = false, skipPrivateValueDeclarationTypes = false) {
- this.checker = checker;
- this.isLocalCompilation = isLocalCompilation;
- this.skipPrivateValueDeclarationTypes = skipPrivateValueDeclarationTypes;
- }
- getDecoratorsOfDeclaration(declaration) {
- const decorators = ts.canHaveDecorators(declaration)
- ? ts.getDecorators(declaration)
- : undefined;
- return decorators !== undefined && decorators.length
- ? decorators
- .map((decorator) => this._reflectDecorator(decorator))
- .filter((dec) => dec !== null)
- : null;
- }
- getMembersOfClass(clazz) {
- const tsClazz = castDeclarationToClassOrDie(clazz);
- return tsClazz.members
- .map((member) => {
- const result = reflectClassMember(member);
- if (result === null) {
- return null;
- }
- return {
- ...result,
- decorators: this.getDecoratorsOfDeclaration(member),
- };
- })
- .filter((member) => member !== null);
- }
- getConstructorParameters(clazz) {
- const tsClazz = castDeclarationToClassOrDie(clazz);
- const isDeclaration = tsClazz.getSourceFile().isDeclarationFile;
- // For non-declaration files, we want to find the constructor with a `body`. The constructors
- // without a `body` are overloads whereas we want the implementation since it's the one that'll
- // be executed and which can have decorators. For declaration files, we take the first one that
- // we get.
- const ctor = tsClazz.members.find((member) => ts.isConstructorDeclaration(member) && (isDeclaration || member.body !== undefined));
- if (ctor === undefined) {
- return null;
- }
- return ctor.parameters.map((node) => {
- // The name of the parameter is easy.
- const name = parameterName(node.name);
- const decorators = this.getDecoratorsOfDeclaration(node);
- // It may or may not be possible to write an expression that refers to the value side of the
- // type named for the parameter.
- let originalTypeNode = node.type || null;
- let typeNode = originalTypeNode;
- // Check if we are dealing with a simple nullable union type e.g. `foo: Foo|null`
- // and extract the type. More complex union types e.g. `foo: Foo|Bar` are not supported.
- // We also don't need to support `foo: Foo|undefined` because Angular's DI injects `null` for
- // optional tokes that don't have providers.
- if (typeNode && ts.isUnionTypeNode(typeNode)) {
- let childTypeNodes = typeNode.types.filter((childTypeNode) => !(ts.isLiteralTypeNode(childTypeNode) &&
- childTypeNode.literal.kind === ts.SyntaxKind.NullKeyword));
- if (childTypeNodes.length === 1) {
- typeNode = childTypeNodes[0];
- }
- }
- const typeValueReference = typeToValue(typeNode, this.checker, this.isLocalCompilation);
- return {
- name,
- nameNode: node.name,
- typeValueReference,
- typeNode: originalTypeNode,
- decorators,
- };
- });
- }
- getImportOfIdentifier(id) {
- const directImport = this.getDirectImportOfIdentifier(id);
- if (directImport !== null) {
- return directImport;
- }
- else if (ts.isQualifiedName(id.parent) && id.parent.right === id) {
- return this.getImportOfNamespacedIdentifier(id, getQualifiedNameRoot(id.parent));
- }
- else if (ts.isPropertyAccessExpression(id.parent) && id.parent.name === id) {
- return this.getImportOfNamespacedIdentifier(id, getFarLeftIdentifier(id.parent));
- }
- else {
- return null;
- }
- }
- getExportsOfModule(node) {
- // In TypeScript code, modules are only ts.SourceFiles. Throw if the node isn't a module.
- if (!ts.isSourceFile(node)) {
- throw new Error(`getExportsOfModule() called on non-SourceFile in TS code`);
- }
- // Reflect the module to a Symbol, and use getExportsOfModule() to get a list of exported
- // Symbols.
- const symbol = this.checker.getSymbolAtLocation(node);
- if (symbol === undefined) {
- return null;
- }
- const map = new Map();
- this.checker.getExportsOfModule(symbol).forEach((exportSymbol) => {
- // Map each exported Symbol to a Declaration and add it to the map.
- const decl = this.getDeclarationOfSymbol(exportSymbol, null);
- if (decl !== null) {
- map.set(exportSymbol.name, decl);
- }
- });
- return map;
- }
- isClass(node) {
- // For our purposes, classes are "named" ts.ClassDeclarations;
- // (`node.name` can be undefined in unnamed default exports: `default export class { ... }`).
- return isNamedClassDeclaration(node);
- }
- hasBaseClass(clazz) {
- return this.getBaseClassExpression(clazz) !== null;
- }
- getBaseClassExpression(clazz) {
- if (!(ts.isClassDeclaration(clazz) || ts.isClassExpression(clazz)) ||
- clazz.heritageClauses === undefined) {
- return null;
- }
- const extendsClause = clazz.heritageClauses.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword);
- if (extendsClause === undefined) {
- return null;
- }
- const extendsType = extendsClause.types[0];
- if (extendsType === undefined) {
- return null;
- }
- return extendsType.expression;
- }
- getDeclarationOfIdentifier(id) {
- // Resolve the identifier to a Symbol, and return the declaration of that.
- let symbol = this.checker.getSymbolAtLocation(id);
- if (symbol === undefined) {
- return null;
- }
- return this.getDeclarationOfSymbol(symbol, id);
- }
- getDefinitionOfFunction(node) {
- if (!ts.isFunctionDeclaration(node) &&
- !ts.isMethodDeclaration(node) &&
- !ts.isFunctionExpression(node) &&
- !ts.isArrowFunction(node)) {
- return null;
- }
- let body = null;
- if (node.body !== undefined) {
- // The body might be an expression if the node is an arrow function.
- body = ts.isBlock(node.body)
- ? Array.from(node.body.statements)
- : [ts.factory.createReturnStatement(node.body)];
- }
- const type = this.checker.getTypeAtLocation(node);
- const signatures = this.checker.getSignaturesOfType(type, ts.SignatureKind.Call);
- return {
- node,
- body,
- signatureCount: signatures.length,
- typeParameters: node.typeParameters === undefined ? null : Array.from(node.typeParameters),
- parameters: node.parameters.map((param) => {
- const name = parameterName(param.name);
- const initializer = param.initializer || null;
- return { name, node: param, initializer, type: param.type || null };
- }),
- };
- }
- getGenericArityOfClass(clazz) {
- if (!ts.isClassDeclaration(clazz)) {
- return null;
- }
- return clazz.typeParameters !== undefined ? clazz.typeParameters.length : 0;
- }
- getVariableValue(declaration) {
- return declaration.initializer || null;
- }
- isStaticallyExported(decl) {
- // First check if there's an `export` modifier directly on the declaration.
- let topLevel = decl;
- if (ts.isVariableDeclaration(decl) && ts.isVariableDeclarationList(decl.parent)) {
- topLevel = decl.parent.parent;
- }
- const modifiers = ts.canHaveModifiers(topLevel) ? ts.getModifiers(topLevel) : undefined;
- if (modifiers !== undefined &&
- modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
- // The node is part of a declaration that's directly exported.
- return true;
- }
- // If `topLevel` is not directly exported via a modifier, then it might be indirectly exported,
- // e.g.:
- //
- // class Foo {}
- // export {Foo};
- //
- // The only way to check this is to look at the module level for exports of the class. As a
- // performance optimization, this check is only performed if the class is actually declared at
- // the top level of the file and thus eligible for exporting in the first place.
- if (topLevel.parent === undefined || !ts.isSourceFile(topLevel.parent)) {
- return false;
- }
- const localExports = this.getLocalExportedDeclarationsOfSourceFile(decl.getSourceFile());
- return localExports.has(decl);
- }
- getDirectImportOfIdentifier(id) {
- const symbol = this.checker.getSymbolAtLocation(id);
- if (symbol === undefined ||
- symbol.declarations === undefined ||
- symbol.declarations.length !== 1) {
- return null;
- }
- const decl = symbol.declarations[0];
- const importDecl = getContainingImportDeclaration(decl);
- // Ignore declarations that are defined locally (not imported).
- if (importDecl === null) {
- return null;
- }
- // The module specifier is guaranteed to be a string literal, so this should always pass.
- if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
- // Not allowed to happen in TypeScript ASTs.
- return null;
- }
- return {
- from: importDecl.moduleSpecifier.text,
- name: getExportedName(decl, id),
- node: importDecl,
- };
- }
- /**
- * Try to get the import info for this identifier as though it is a namespaced import.
- *
- * For example, if the identifier is the `Directive` part of a qualified type chain like:
- *
- * ```ts
- * core.Directive
- * ```
- *
- * then it might be that `core` is a namespace import such as:
- *
- * ```ts
- * import * as core from 'tslib';
- * ```
- *
- * @param id the TypeScript identifier to find the import info for.
- * @returns The import info if this is a namespaced import or `null`.
- */
- getImportOfNamespacedIdentifier(id, namespaceIdentifier) {
- if (namespaceIdentifier === null) {
- return null;
- }
- const namespaceSymbol = this.checker.getSymbolAtLocation(namespaceIdentifier);
- if (!namespaceSymbol || namespaceSymbol.declarations === undefined) {
- return null;
- }
- const declaration = namespaceSymbol.declarations.length === 1 ? namespaceSymbol.declarations[0] : null;
- if (!declaration) {
- return null;
- }
- const namespaceDeclaration = ts.isNamespaceImport(declaration) ? declaration : null;
- if (!namespaceDeclaration) {
- return null;
- }
- const importDeclaration = namespaceDeclaration.parent.parent;
- if (!ts.isImportDeclaration(importDeclaration) ||
- !ts.isStringLiteral(importDeclaration.moduleSpecifier)) {
- // Should not happen as this would be invalid TypesScript
- return null;
- }
- return {
- from: importDeclaration.moduleSpecifier.text,
- name: id.text,
- node: importDeclaration,
- };
- }
- /**
- * Resolve a `ts.Symbol` to its declaration, keeping track of the `viaModule` along the way.
- */
- getDeclarationOfSymbol(symbol, originalId) {
- // If the symbol points to a ShorthandPropertyAssignment, resolve it.
- let valueDeclaration = undefined;
- if (symbol.valueDeclaration !== undefined) {
- valueDeclaration = symbol.valueDeclaration;
- }
- else if (symbol.declarations !== undefined && symbol.declarations.length > 0) {
- valueDeclaration = symbol.declarations[0];
- }
- if (valueDeclaration !== undefined && ts.isShorthandPropertyAssignment(valueDeclaration)) {
- const shorthandSymbol = this.checker.getShorthandAssignmentValueSymbol(valueDeclaration);
- if (shorthandSymbol === undefined) {
- return null;
- }
- return this.getDeclarationOfSymbol(shorthandSymbol, originalId);
- }
- else if (valueDeclaration !== undefined && ts.isExportSpecifier(valueDeclaration)) {
- const targetSymbol = this.checker.getExportSpecifierLocalTargetSymbol(valueDeclaration);
- if (targetSymbol === undefined) {
- return null;
- }
- return this.getDeclarationOfSymbol(targetSymbol, originalId);
- }
- const importInfo = originalId && this.getImportOfIdentifier(originalId);
- // Now, resolve the Symbol to its declaration by following any and all aliases.
- while (symbol.flags & ts.SymbolFlags.Alias) {
- symbol = this.checker.getAliasedSymbol(symbol);
- }
- // Look at the resolved Symbol's declarations and pick one of them to return.
- // Value declarations are given precedence over type declarations if not specified otherwise
- if (symbol.valueDeclaration !== undefined &&
- (!this.skipPrivateValueDeclarationTypes || !isPrivateSymbol(this.checker, symbol))) {
- return {
- node: symbol.valueDeclaration,
- viaModule: this._viaModule(symbol.valueDeclaration, originalId, importInfo),
- };
- }
- else if (symbol.declarations !== undefined && symbol.declarations.length > 0) {
- return {
- node: symbol.declarations[0],
- viaModule: this._viaModule(symbol.declarations[0], originalId, importInfo),
- };
- }
- else {
- return null;
- }
- }
- _reflectDecorator(node) {
- // Attempt to resolve the decorator expression into a reference to a concrete Identifier. The
- // expression may contain a call to a function which returns the decorator function, in which
- // case we want to return the arguments.
- let decoratorExpr = node.expression;
- let args = null;
- // Check for call expressions.
- if (ts.isCallExpression(decoratorExpr)) {
- args = Array.from(decoratorExpr.arguments);
- decoratorExpr = decoratorExpr.expression;
- }
- // The final resolved decorator should be a `ts.Identifier` - if it's not, then something is
- // wrong and the decorator can't be resolved statically.
- if (!isDecoratorIdentifier(decoratorExpr)) {
- return null;
- }
- const decoratorIdentifier = ts.isIdentifier(decoratorExpr) ? decoratorExpr : decoratorExpr.name;
- const importDecl = this.getImportOfIdentifier(decoratorIdentifier);
- return {
- name: decoratorIdentifier.text,
- identifier: decoratorExpr,
- import: importDecl,
- node,
- args,
- };
- }
- /**
- * Get the set of declarations declared in `file` which are exported.
- */
- getLocalExportedDeclarationsOfSourceFile(file) {
- const cacheSf = file;
- if (cacheSf[LocalExportedDeclarations] !== undefined) {
- // TS does not currently narrow symbol-keyed fields, hence the non-null assert is needed.
- return cacheSf[LocalExportedDeclarations];
- }
- const exportSet = new Set();
- cacheSf[LocalExportedDeclarations] = exportSet;
- const sfSymbol = this.checker.getSymbolAtLocation(cacheSf);
- if (sfSymbol === undefined || sfSymbol.exports === undefined) {
- return exportSet;
- }
- // Scan the exported symbol of the `ts.SourceFile` for the original `symbol` of the class
- // declaration.
- //
- // Note: when checking multiple classes declared in the same file, this repeats some operations.
- // In theory, this could be expensive if run in the context of a massive input file. If
- // performance does become an issue here, it should be possible to create a `Set<>`
- // Unfortunately, `ts.Iterator` doesn't implement the iterator protocol, so iteration here is
- // done manually.
- const iter = sfSymbol.exports.values();
- let item = iter.next();
- while (item.done !== true) {
- let exportedSymbol = item.value;
- // If this exported symbol comes from an `export {Foo}` statement, then the symbol is actually
- // for the export declaration, not the original declaration. Such a symbol will be an alias,
- // so unwrap aliasing if necessary.
- if (exportedSymbol.flags & ts.SymbolFlags.Alias) {
- exportedSymbol = this.checker.getAliasedSymbol(exportedSymbol);
- }
- if (exportedSymbol.valueDeclaration !== undefined &&
- exportedSymbol.valueDeclaration.getSourceFile() === file) {
- exportSet.add(exportedSymbol.valueDeclaration);
- }
- item = iter.next();
- }
- return exportSet;
- }
- _viaModule(declaration, originalId, importInfo) {
- if (importInfo === null &&
- originalId !== null &&
- declaration.getSourceFile() !== originalId.getSourceFile()) {
- return AmbientImport;
- }
- return importInfo !== null && importInfo.from !== null && !importInfo.from.startsWith('.')
- ? importInfo.from
- : null;
- }
- }
- class TypeEntityToDeclarationError extends Error {
- constructor(message) {
- super(message);
- // Extending `Error` ends up breaking some internal tests. This appears to be a known issue
- // when extending errors in TS and the workaround is to explicitly set the prototype.
- // https://stackoverflow.com/questions/41102060/typescript-extending-error-class
- Object.setPrototypeOf(this, new.target.prototype);
- }
- }
- /**
- * @throws {TypeEntityToDeclarationError} if the type cannot be converted
- * to a declaration.
- */
- function reflectTypeEntityToDeclaration(type, checker) {
- let realSymbol = checker.getSymbolAtLocation(type);
- if (realSymbol === undefined) {
- throw new TypeEntityToDeclarationError(`Cannot resolve type entity ${type.getText()} to symbol`);
- }
- while (realSymbol.flags & ts.SymbolFlags.Alias) {
- realSymbol = checker.getAliasedSymbol(realSymbol);
- }
- let node = null;
- if (realSymbol.valueDeclaration !== undefined) {
- node = realSymbol.valueDeclaration;
- }
- else if (realSymbol.declarations !== undefined && realSymbol.declarations.length === 1) {
- node = realSymbol.declarations[0];
- }
- else {
- throw new TypeEntityToDeclarationError(`Cannot resolve type entity symbol to declaration`);
- }
- if (ts.isQualifiedName(type)) {
- if (!ts.isIdentifier(type.left)) {
- throw new TypeEntityToDeclarationError(`Cannot handle qualified name with non-identifier lhs`);
- }
- const symbol = checker.getSymbolAtLocation(type.left);
- if (symbol === undefined ||
- symbol.declarations === undefined ||
- symbol.declarations.length !== 1) {
- throw new TypeEntityToDeclarationError(`Cannot resolve qualified type entity lhs to symbol`);
- }
- const decl = symbol.declarations[0];
- if (ts.isNamespaceImport(decl)) {
- const clause = decl.parent;
- const importDecl = clause.parent;
- if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
- throw new TypeEntityToDeclarationError(`Module specifier is not a string`);
- }
- return { node, from: importDecl.moduleSpecifier.text };
- }
- else if (ts.isModuleDeclaration(decl)) {
- return { node, from: null };
- }
- else {
- throw new TypeEntityToDeclarationError(`Unknown import type?`);
- }
- }
- else {
- return { node, from: null };
- }
- }
- function filterToMembersWithDecorator(members, name, module) {
- return members
- .filter((member) => !member.isStatic)
- .map((member) => {
- if (member.decorators === null) {
- return null;
- }
- const decorators = member.decorators.filter((dec) => {
- if (dec.import !== null) {
- return dec.import.name === name && (module === undefined || dec.import.from === module);
- }
- else {
- return dec.name === name && module === undefined;
- }
- });
- if (decorators.length === 0) {
- return null;
- }
- return { member, decorators };
- })
- .filter((value) => value !== null);
- }
- function extractModifiersOfMember(node) {
- const modifiers = ts.getModifiers(node);
- let isStatic = false;
- let isReadonly = false;
- let accessLevel = exports.ClassMemberAccessLevel.PublicWritable;
- if (modifiers !== undefined) {
- for (const modifier of modifiers) {
- switch (modifier.kind) {
- case ts.SyntaxKind.StaticKeyword:
- isStatic = true;
- break;
- case ts.SyntaxKind.PrivateKeyword:
- accessLevel = exports.ClassMemberAccessLevel.Private;
- break;
- case ts.SyntaxKind.ProtectedKeyword:
- accessLevel = exports.ClassMemberAccessLevel.Protected;
- break;
- case ts.SyntaxKind.ReadonlyKeyword:
- isReadonly = true;
- break;
- }
- }
- }
- if (isReadonly && accessLevel === exports.ClassMemberAccessLevel.PublicWritable) {
- accessLevel = exports.ClassMemberAccessLevel.PublicReadonly;
- }
- if (node.name !== undefined && ts.isPrivateIdentifier(node.name)) {
- accessLevel = exports.ClassMemberAccessLevel.EcmaScriptPrivate;
- }
- return { accessLevel, isStatic };
- }
- /**
- * Reflects a class element and returns static information about the
- * class member.
- *
- * Note: Decorator information is not included in this helper as it relies
- * on type checking to resolve originating import.
- */
- function reflectClassMember(node) {
- let kind = null;
- let value = null;
- let name = null;
- let nameNode = null;
- if (ts.isPropertyDeclaration(node)) {
- kind = exports.ClassMemberKind.Property;
- value = node.initializer || null;
- }
- else if (ts.isGetAccessorDeclaration(node)) {
- kind = exports.ClassMemberKind.Getter;
- }
- else if (ts.isSetAccessorDeclaration(node)) {
- kind = exports.ClassMemberKind.Setter;
- }
- else if (ts.isMethodDeclaration(node)) {
- kind = exports.ClassMemberKind.Method;
- }
- else if (ts.isConstructorDeclaration(node)) {
- kind = exports.ClassMemberKind.Constructor;
- }
- else {
- return null;
- }
- if (ts.isConstructorDeclaration(node)) {
- name = 'constructor';
- }
- else if (ts.isIdentifier(node.name)) {
- name = node.name.text;
- nameNode = node.name;
- }
- else if (ts.isStringLiteral(node.name)) {
- name = node.name.text;
- nameNode = node.name;
- }
- else if (ts.isPrivateIdentifier(node.name)) {
- name = node.name.text;
- nameNode = node.name;
- }
- else {
- return null;
- }
- const { accessLevel, isStatic } = extractModifiersOfMember(node);
- return {
- node,
- implementation: node,
- kind,
- type: node.type || null,
- accessLevel,
- name,
- nameNode,
- value,
- isStatic,
- };
- }
- function reflectObjectLiteral(node) {
- const map = new Map();
- node.properties.forEach((prop) => {
- if (ts.isPropertyAssignment(prop)) {
- const name = propertyNameToString(prop.name);
- if (name === null) {
- return;
- }
- map.set(name, prop.initializer);
- }
- else if (ts.isShorthandPropertyAssignment(prop)) {
- map.set(prop.name.text, prop.name);
- }
- else {
- return;
- }
- });
- return map;
- }
- function castDeclarationToClassOrDie(declaration) {
- if (!ts.isClassDeclaration(declaration)) {
- throw new Error(`Reflecting on a ${ts.SyntaxKind[declaration.kind]} instead of a ClassDeclaration.`);
- }
- return declaration;
- }
- function parameterName(name) {
- if (ts.isIdentifier(name)) {
- return name.text;
- }
- else {
- return null;
- }
- }
- function propertyNameToString(node) {
- if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
- return node.text;
- }
- else {
- return null;
- }
- }
- /** Determines whether a given symbol represents a private API (symbols with names that start with `ɵ`) */
- function isPrivateSymbol(typeChecker, symbol) {
- if (symbol.valueDeclaration !== undefined) {
- const symbolType = typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration);
- return symbolType?.symbol?.name.startsWith('ɵ') === true;
- }
- return false;
- }
- /**
- * Compute the left most identifier in a qualified type chain. E.g. the `a` of `a.b.c.SomeType`.
- * @param qualifiedName The starting property access expression from which we want to compute
- * the left most identifier.
- * @returns the left most identifier in the chain or `null` if it is not an identifier.
- */
- function getQualifiedNameRoot(qualifiedName) {
- while (ts.isQualifiedName(qualifiedName.left)) {
- qualifiedName = qualifiedName.left;
- }
- return ts.isIdentifier(qualifiedName.left) ? qualifiedName.left : null;
- }
- /**
- * Compute the left most identifier in a property access chain. E.g. the `a` of `a.b.c.d`.
- * @param propertyAccess The starting property access expression from which we want to compute
- * the left most identifier.
- * @returns the left most identifier in the chain or `null` if it is not an identifier.
- */
- function getFarLeftIdentifier(propertyAccess) {
- while (ts.isPropertyAccessExpression(propertyAccess.expression)) {
- propertyAccess = propertyAccess.expression;
- }
- return ts.isIdentifier(propertyAccess.expression) ? propertyAccess.expression : null;
- }
- /**
- * Gets the closest ancestor `ImportDeclaration` to a node.
- */
- function getContainingImportDeclaration(node) {
- let parent = node.parent;
- while (parent && !ts.isSourceFile(parent)) {
- if (ts.isImportDeclaration(parent)) {
- return parent;
- }
- parent = parent.parent;
- }
- return null;
- }
- /**
- * Compute the name by which the `decl` was exported, not imported.
- * If no such declaration can be found (e.g. it is a namespace import)
- * then fallback to the `originalId`.
- */
- function getExportedName(decl, originalId) {
- return ts.isImportSpecifier(decl)
- ? (decl.propertyName !== undefined ? decl.propertyName : decl.name).text
- : originalId.text;
- }
- const LocalExportedDeclarations = Symbol('LocalExportedDeclarations');
- /**
- * A `ts.Node` plus the context in which it was discovered.
- *
- * A `Reference` is a pointer to a `ts.Node` that was extracted from the program somehow. It
- * contains not only the node itself, but the information regarding how the node was located. In
- * particular, it might track different identifiers by which the node is exposed, as well as
- * potentially a module specifier which might expose the node.
- *
- * The Angular compiler uses `Reference`s instead of `ts.Node`s when tracking classes or generating
- * imports.
- */
- class Reference {
- node;
- /**
- * The compiler's best guess at an absolute module specifier which owns this `Reference`.
- *
- * This is usually determined by tracking the import statements which led the compiler to a given
- * node. If any of these imports are absolute, it's an indication that the node being imported
- * might come from that module.
- *
- * It is not _guaranteed_ that the node in question is exported from its `bestGuessOwningModule` -
- * that is mostly a convention that applies in certain package formats.
- *
- * If `bestGuessOwningModule` is `null`, then it's likely the node came from the current program.
- */
- bestGuessOwningModule;
- identifiers = [];
- /**
- * Indicates that the Reference was created synthetically, not as a result of natural value
- * resolution.
- *
- * This is used to avoid misinterpreting the Reference in certain contexts.
- */
- synthetic = false;
- _alias = null;
- isAmbient;
- constructor(node, bestGuessOwningModule = null) {
- this.node = node;
- if (bestGuessOwningModule === AmbientImport) {
- this.isAmbient = true;
- this.bestGuessOwningModule = null;
- }
- else {
- this.isAmbient = false;
- this.bestGuessOwningModule = bestGuessOwningModule;
- }
- const id = identifierOfNode(node);
- if (id !== null) {
- this.identifiers.push(id);
- }
- }
- /**
- * The best guess at which module specifier owns this particular reference, or `null` if there
- * isn't one.
- */
- get ownedByModuleGuess() {
- if (this.bestGuessOwningModule !== null) {
- return this.bestGuessOwningModule.specifier;
- }
- else {
- return null;
- }
- }
- /**
- * Whether this reference has a potential owning module or not.
- *
- * See `bestGuessOwningModule`.
- */
- get hasOwningModuleGuess() {
- return this.bestGuessOwningModule !== null;
- }
- /**
- * A name for the node, if one is available.
- *
- * This is only suited for debugging. Any actual references to this node should be made with
- * `ts.Identifier`s (see `getIdentityIn`).
- */
- get debugName() {
- const id = identifierOfNode(this.node);
- return id !== null ? id.text : null;
- }
- get alias() {
- return this._alias;
- }
- /**
- * Record a `ts.Identifier` by which it's valid to refer to this node, within the context of this
- * `Reference`.
- */
- addIdentifier(identifier) {
- this.identifiers.push(identifier);
- }
- /**
- * Get a `ts.Identifier` within this `Reference` that can be used to refer within the context of a
- * given `ts.SourceFile`, if any.
- */
- getIdentityIn(context) {
- return this.identifiers.find((id) => id.getSourceFile() === context) || null;
- }
- /**
- * Get a `ts.Identifier` for this `Reference` that exists within the given expression.
- *
- * This is very useful for producing `ts.Diagnostic`s that reference `Reference`s that were
- * extracted from some larger expression, as it can be used to pinpoint the `ts.Identifier` within
- * the expression from which the `Reference` originated.
- */
- getIdentityInExpression(expr) {
- const sf = expr.getSourceFile();
- return (this.identifiers.find((id) => {
- if (id.getSourceFile() !== sf) {
- return false;
- }
- // This identifier is a match if its position lies within the given expression.
- return id.pos >= expr.pos && id.end <= expr.end;
- }) || null);
- }
- /**
- * Given the 'container' expression from which this `Reference` was extracted, produce a
- * `ts.Expression` to use in a diagnostic which best indicates the position within the container
- * expression that generated the `Reference`.
- *
- * For example, given a `Reference` to the class 'Bar' and the containing expression:
- * `[Foo, Bar, Baz]`, this function would attempt to return the `ts.Identifier` for `Bar` within
- * the array. This could be used to produce a nice diagnostic context:
- *
- * ```text
- * [Foo, Bar, Baz]
- * ~~~
- * ```
- *
- * If no specific node can be found, then the `fallback` expression is used, which defaults to the
- * entire containing expression.
- */
- getOriginForDiagnostics(container, fallback = container) {
- const id = this.getIdentityInExpression(container);
- return id !== null ? id : fallback;
- }
- cloneWithAlias(alias) {
- const ref = new Reference(this.node, this.isAmbient ? AmbientImport : this.bestGuessOwningModule);
- ref.identifiers = [...this.identifiers];
- ref._alias = alias;
- return ref;
- }
- cloneWithNoIdentifiers() {
- const ref = new Reference(this.node, this.isAmbient ? AmbientImport : this.bestGuessOwningModule);
- ref._alias = this._alias;
- ref.identifiers = [];
- return ref;
- }
- }
- /** Module name of the framework core. */
- const CORE_MODULE = '@angular/core';
- function valueReferenceToExpression(valueRef) {
- if (valueRef.kind === 2 /* TypeValueReferenceKind.UNAVAILABLE */) {
- return null;
- }
- else if (valueRef.kind === 0 /* TypeValueReferenceKind.LOCAL */) {
- const expr = new WrappedNodeExpr(valueRef.expression);
- if (valueRef.defaultImportStatement !== null) {
- attachDefaultImportDeclaration(expr, valueRef.defaultImportStatement);
- }
- return expr;
- }
- else {
- let importExpr = new ExternalExpr({
- moduleName: valueRef.moduleName,
- name: valueRef.importedName,
- });
- if (valueRef.nestedPath !== null) {
- for (const property of valueRef.nestedPath) {
- importExpr = new ReadPropExpr(importExpr, property);
- }
- }
- return importExpr;
- }
- }
- function toR3Reference(origin, ref, context, refEmitter) {
- const emittedValueRef = refEmitter.emit(ref, context);
- assertSuccessfulReferenceEmit(emittedValueRef, origin, 'class');
- const emittedTypeRef = refEmitter.emit(ref, context, exports.ImportFlags.ForceNewImport | exports.ImportFlags.AllowTypeImports);
- assertSuccessfulReferenceEmit(emittedTypeRef, origin, 'class');
- return {
- value: emittedValueRef.expression,
- type: emittedTypeRef.expression,
- };
- }
- function isAngularCore(decorator) {
- return decorator.import !== null && decorator.import.from === CORE_MODULE;
- }
- /**
- * This function is used for verifying that a given reference is declared
- * inside `@angular/core` and corresponds to the given symbol name.
- *
- * In some cases, due to the compiler face duplicating many symbols as
- * an independent bridge between core and the compiler, the dts bundler may
- * decide to alias declarations in the `.d.ts`, to avoid conflicts.
- *
- * e.g.
- *
- * ```
- * declare enum ViewEncapsulation {} // from the facade
- * declare enum ViewEncapsulation$1 {} // the real one exported to users.
- * ```
- *
- * This function accounts for such potential re-namings.
- */
- function isAngularCoreReferenceWithPotentialAliasing(reference, symbolName, isCore) {
- return ((reference.ownedByModuleGuess === CORE_MODULE || isCore) &&
- reference.debugName?.replace(/\$\d+$/, '') === symbolName);
- }
- function findAngularDecorator(decorators, name, isCore) {
- return decorators.find((decorator) => isAngularDecorator(decorator, name, isCore));
- }
- function isAngularDecorator(decorator, name, isCore) {
- if (isCore) {
- return decorator.name === name;
- }
- else if (isAngularCore(decorator)) {
- return decorator.import.name === name;
- }
- return false;
- }
- function getAngularDecorators(decorators, names, isCore) {
- return decorators.filter((decorator) => {
- const name = isCore ? decorator.name : decorator.import?.name;
- if (name === undefined || !names.includes(name)) {
- return false;
- }
- return isCore || isAngularCore(decorator);
- });
- }
- /**
- * Unwrap a `ts.Expression`, removing outer type-casts or parentheses until the expression is in its
- * lowest level form.
- *
- * For example, the expression "(foo as Type)" unwraps to "foo".
- */
- function unwrapExpression(node) {
- while (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) {
- node = node.expression;
- }
- return node;
- }
- function expandForwardRef(arg) {
- arg = unwrapExpression(arg);
- if (!ts.isArrowFunction(arg) && !ts.isFunctionExpression(arg)) {
- return null;
- }
- const body = arg.body;
- // Either the body is a ts.Expression directly, or a block with a single return statement.
- if (ts.isBlock(body)) {
- // Block body - look for a single return statement.
- if (body.statements.length !== 1) {
- return null;
- }
- const stmt = body.statements[0];
- if (!ts.isReturnStatement(stmt) || stmt.expression === undefined) {
- return null;
- }
- return stmt.expression;
- }
- else {
- // Shorthand body - return as an expression.
- return body;
- }
- }
- /**
- * If the given `node` is a forwardRef() expression then resolve its inner value, otherwise return
- * `null`.
- *
- * @param node the forwardRef() expression to resolve
- * @param reflector a ReflectionHost
- * @returns the resolved expression, if the original expression was a forwardRef(), or `null`
- * otherwise.
- */
- function tryUnwrapForwardRef(node, reflector) {
- node = unwrapExpression(node);
- if (!ts.isCallExpression(node) || node.arguments.length !== 1) {
- return null;
- }
- const fn = ts.isPropertyAccessExpression(node.expression)
- ? node.expression.name
- : node.expression;
- if (!ts.isIdentifier(fn)) {
- return null;
- }
- const expr = expandForwardRef(node.arguments[0]);
- if (expr === null) {
- return null;
- }
- const imp = reflector.getImportOfIdentifier(fn);
- if (imp === null || imp.from !== '@angular/core' || imp.name !== 'forwardRef') {
- return null;
- }
- return expr;
- }
- /**
- * A foreign function resolver for `staticallyResolve` which unwraps forwardRef() expressions.
- *
- * @param ref a Reference to the declaration of the function being called (which might be
- * forwardRef)
- * @param args the arguments to the invocation of the forwardRef expression
- * @returns an unwrapped argument if `ref` pointed to forwardRef, or null otherwise
- */
- function createForwardRefResolver(isCore) {
- return (fn, callExpr, resolve, unresolvable) => {
- if (!isAngularCoreReferenceWithPotentialAliasing(fn, 'forwardRef', isCore) ||
- callExpr.arguments.length !== 1) {
- return unresolvable;
- }
- const expanded = expandForwardRef(callExpr.arguments[0]);
- if (expanded !== null) {
- return resolve(expanded);
- }
- else {
- return unresolvable;
- }
- };
- }
- /**
- * Combines an array of resolver functions into a one.
- * @param resolvers Resolvers to be combined.
- */
- function combineResolvers(resolvers) {
- return (fn, callExpr, resolve, unresolvable) => {
- for (const resolver of resolvers) {
- const resolved = resolver(fn, callExpr, resolve, unresolvable);
- if (resolved !== unresolvable) {
- return resolved;
- }
- }
- return unresolvable;
- };
- }
- function isExpressionForwardReference(expr, context, contextSource) {
- if (isWrappedTsNodeExpr(expr)) {
- const node = ts.getOriginalNode(expr.node);
- return node.getSourceFile() === contextSource && context.pos < node.pos;
- }
- else {
- return false;
- }
- }
- function isWrappedTsNodeExpr(expr) {
- return expr instanceof WrappedNodeExpr;
- }
- function readBaseClass(node, reflector, evaluator) {
- const baseExpression = reflector.getBaseClassExpression(node);
- if (baseExpression !== null) {
- const baseClass = evaluator.evaluate(baseExpression);
- if (baseClass instanceof Reference && reflector.isClass(baseClass.node)) {
- return baseClass;
- }
- else {
- return 'dynamic';
- }
- }
- return null;
- }
- const parensWrapperTransformerFactory = (context) => {
- const visitor = (node) => {
- const visited = ts.visitEachChild(node, visitor, context);
- if (ts.isArrowFunction(visited) || ts.isFunctionExpression(visited)) {
- return ts.factory.createParenthesizedExpression(visited);
- }
- return visited;
- };
- return (node) => ts.visitEachChild(node, visitor, context);
- };
- /**
- * Wraps all functions in a given expression in parentheses. This is needed to avoid problems
- * where Tsickle annotations added between analyse and transform phases in Angular may trigger
- * automatic semicolon insertion, e.g. if a function is the expression in a `return` statement.
- * More
- * info can be found in Tsickle source code here:
- * https://github.com/angular/tsickle/blob/d7974262571c8a17d684e5ba07680e1b1993afdd/src/jsdoc_transformer.ts#L1021
- *
- * @param expression Expression where functions should be wrapped in parentheses
- */
- function wrapFunctionExpressionsInParens(expression) {
- return ts.transform(expression, [parensWrapperTransformerFactory]).transformed[0];
- }
- /**
- * Resolves the given `rawProviders` into `ClassDeclarations` and returns
- * a set containing those that are known to require a factory definition.
- * @param rawProviders Expression that declared the providers array in the source.
- */
- function resolveProvidersRequiringFactory(rawProviders, reflector, evaluator) {
- const providers = new Set();
- const resolvedProviders = evaluator.evaluate(rawProviders);
- if (!Array.isArray(resolvedProviders)) {
- return providers;
- }
- resolvedProviders.forEach(function processProviders(provider) {
- let tokenClass = null;
- if (Array.isArray(provider)) {
- // If we ran into an array, recurse into it until we've resolve all the classes.
- provider.forEach(processProviders);
- }
- else if (provider instanceof Reference) {
- tokenClass = provider;
- }
- else if (provider instanceof Map && provider.has('useClass') && !provider.has('deps')) {
- const useExisting = provider.get('useClass');
- if (useExisting instanceof Reference) {
- tokenClass = useExisting;
- }
- }
- // TODO(alxhub): there was a bug where `getConstructorParameters` would return `null` for a
- // class in a .d.ts file, always, even if the class had a constructor. This was fixed for
- // `getConstructorParameters`, but that fix causes more classes to be recognized here as needing
- // provider checks, which is a breaking change in g3. Avoid this breakage for now by skipping
- // classes from .d.ts files here directly, until g3 can be cleaned up.
- if (tokenClass !== null &&
- !tokenClass.node.getSourceFile().isDeclarationFile &&
- reflector.isClass(tokenClass.node)) {
- const constructorParameters = reflector.getConstructorParameters(tokenClass.node);
- // Note that we only want to capture providers with a non-trivial constructor,
- // because they're the ones that might be using DI and need to be decorated.
- if (constructorParameters !== null && constructorParameters.length > 0) {
- providers.add(tokenClass);
- }
- }
- });
- return providers;
- }
- /**
- * Create an R3Reference for a class.
- *
- * The `value` is the exported declaration of the class from its source file.
- * The `type` is an expression that would be used in the typings (.d.ts) files.
- */
- function wrapTypeReference(reflector, clazz) {
- const value = new WrappedNodeExpr(clazz.name);
- const type = value;
- return { value, type };
- }
- /** Creates a ParseSourceSpan for a TypeScript node. */
- function createSourceSpan(node) {
- const sf = node.getSourceFile();
- const [startOffset, endOffset] = [node.getStart(), node.getEnd()];
- const { line: startLine, character: startCol } = sf.getLineAndCharacterOfPosition(startOffset);
- const { line: endLine, character: endCol } = sf.getLineAndCharacterOfPosition(endOffset);
- const parseSf = new ParseSourceFile(sf.getFullText(), sf.fileName);
- // +1 because values are zero-indexed.
- return new ParseSourceSpan(new ParseLocation(parseSf, startOffset, startLine + 1, startCol + 1), new ParseLocation(parseSf, endOffset, endLine + 1, endCol + 1));
- }
- /**
- * Collate the factory and definition compiled results into an array of CompileResult objects.
- */
- function compileResults(fac, def, metadataStmt, propName, additionalFields, deferrableImports, debugInfo = null, hmrInitializer = null) {
- const statements = def.statements;
- if (metadataStmt !== null) {
- statements.push(metadataStmt);
- }
- if (debugInfo !== null) {
- statements.push(debugInfo);
- }
- if (hmrInitializer !== null) {
- statements.push(hmrInitializer);
- }
- const results = [
- fac,
- {
- name: propName,
- initializer: def.expression,
- statements: def.statements,
- type: def.type,
- deferrableImports,
- },
- ];
- if (additionalFields !== null) {
- results.push(...additionalFields);
- }
- return results;
- }
- function toFactoryMetadata(meta, target) {
- return {
- name: meta.name,
- type: meta.type,
- typeArgumentCount: meta.typeArgumentCount,
- deps: meta.deps,
- target,
- };
- }
- function resolveImportedFile(moduleResolver, importedFile, expr, origin) {
- // If `importedFile` is not 'unknown' then it accurately reflects the source file that is
- // being imported.
- if (importedFile !== 'unknown') {
- return importedFile;
- }
- // Otherwise `expr` has to be inspected to determine the file that is being imported. If `expr`
- // is not an `ExternalExpr` then it does not correspond with an import, so return null in that
- // case.
- if (!(expr instanceof ExternalExpr)) {
- return null;
- }
- // Figure out what file is being imported.
- return moduleResolver.resolveModule(expr.value.moduleName, origin.fileName);
- }
- /**
- * Determines the most appropriate expression for diagnostic reporting purposes. If `expr` is
- * contained within `container` then `expr` is used as origin node, otherwise `container` itself is
- * used.
- */
- function getOriginNodeForDiagnostics(expr, container) {
- const nodeSf = expr.getSourceFile();
- const exprSf = container.getSourceFile();
- if (nodeSf === exprSf && expr.pos >= container.pos && expr.end <= container.end) {
- // `expr` occurs within the same source file as `container` and is contained within it, so
- // `expr` is appropriate to use as origin node for diagnostics.
- return expr;
- }
- else {
- return container;
- }
- }
- function isAbstractClassDeclaration(clazz) {
- return ts.canHaveModifiers(clazz) && clazz.modifiers !== undefined
- ? clazz.modifiers.some((mod) => mod.kind === ts.SyntaxKind.AbstractKeyword)
- : false;
- }
- function getConstructorDependencies(clazz, reflector, isCore) {
- const deps = [];
- const errors = [];
- let ctorParams = reflector.getConstructorParameters(clazz);
- if (ctorParams === null) {
- if (reflector.hasBaseClass(clazz)) {
- return null;
- }
- else {
- ctorParams = [];
- }
- }
- ctorParams.forEach((param, idx) => {
- let token = valueReferenceToExpression(param.typeValueReference);
- let attributeNameType = null;
- let optional = false, self = false, skipSelf = false, host = false;
- (param.decorators || [])
- .filter((dec) => isCore || isAngularCore(dec))
- .forEach((dec) => {
- const name = isCore || dec.import === null ? dec.name : dec.import.name;
- if (name === 'Inject') {
- if (dec.args === null || dec.args.length !== 1) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARITY_WRONG, dec.node, `Unexpected number of arguments to @Inject().`);
- }
- token = new WrappedNodeExpr(dec.args[0]);
- }
- else if (name === 'Optional') {
- optional = true;
- }
- else if (name === 'SkipSelf') {
- skipSelf = true;
- }
- else if (name === 'Self') {
- self = true;
- }
- else if (name === 'Host') {
- host = true;
- }
- else if (name === 'Attribute') {
- if (dec.args === null || dec.args.length !== 1) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARITY_WRONG, dec.node, `Unexpected number of arguments to @Attribute().`);
- }
- const attributeName = dec.args[0];
- token = new WrappedNodeExpr(attributeName);
- if (ts.isStringLiteralLike(attributeName)) {
- attributeNameType = new LiteralExpr(attributeName.text);
- }
- else {
- attributeNameType = new WrappedNodeExpr(ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword));
- }
- }
- else {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_UNEXPECTED, dec.node, `Unexpected decorator ${name} on parameter.`);
- }
- });
- if (token === null) {
- if (param.typeValueReference.kind !== 2 /* TypeValueReferenceKind.UNAVAILABLE */) {
- throw new Error('Illegal state: expected value reference to be unavailable if no token is present');
- }
- errors.push({
- index: idx,
- param,
- reason: param.typeValueReference.reason,
- });
- }
- else {
- deps.push({ token, attributeNameType, optional, self, skipSelf, host });
- }
- });
- if (errors.length === 0) {
- return { deps };
- }
- else {
- return { deps: null, errors };
- }
- }
- /**
- * Convert `ConstructorDeps` into the `R3DependencyMetadata` array for those deps if they're valid,
- * or into an `'invalid'` signal if they're not.
- *
- * This is a companion function to `validateConstructorDependencies` which accepts invalid deps.
- */
- function unwrapConstructorDependencies(deps) {
- if (deps === null) {
- return null;
- }
- else if (deps.deps !== null) {
- // These constructor dependencies are valid.
- return deps.deps;
- }
- else {
- // These deps are invalid.
- return 'invalid';
- }
- }
- function getValidConstructorDependencies(clazz, reflector, isCore) {
- return validateConstructorDependencies(clazz, getConstructorDependencies(clazz, reflector, isCore));
- }
- /**
- * Validate that `ConstructorDeps` does not have any invalid dependencies and convert them into the
- * `R3DependencyMetadata` array if so, or raise a diagnostic if some deps are invalid.
- *
- * This is a companion function to `unwrapConstructorDependencies` which does not accept invalid
- * deps.
- */
- function validateConstructorDependencies(clazz, deps) {
- if (deps === null) {
- return null;
- }
- else if (deps.deps !== null) {
- return deps.deps;
- }
- else {
- // There is at least one error.
- const error = deps.errors[0];
- throw createUnsuitableInjectionTokenError(clazz, error);
- }
- }
- /**
- * Creates a fatal error with diagnostic for an invalid injection token.
- * @param clazz The class for which the injection token was unavailable.
- * @param error The reason why no valid injection token is available.
- */
- function createUnsuitableInjectionTokenError(clazz, error) {
- const { param, index, reason } = error;
- let chainMessage = undefined;
- let hints = undefined;
- switch (reason.kind) {
- case 5 /* ValueUnavailableKind.UNSUPPORTED */:
- chainMessage = 'Consider using the @Inject decorator to specify an injection token.';
- hints = [
- makeRelatedInformation(reason.typeNode, 'This type is not supported as injection token.'),
- ];
- break;
- case 1 /* ValueUnavailableKind.NO_VALUE_DECLARATION */:
- chainMessage = 'Consider using the @Inject decorator to specify an injection token.';
- hints = [
- makeRelatedInformation(reason.typeNode, 'This type does not have a value, so it cannot be used as injection token.'),
- ];
- if (reason.decl !== null) {
- hints.push(makeRelatedInformation(reason.decl, 'The type is declared here.'));
- }
- break;
- case 2 /* ValueUnavailableKind.TYPE_ONLY_IMPORT */:
- chainMessage =
- 'Consider changing the type-only import to a regular import, or use the @Inject decorator to specify an injection token.';
- hints = [
- makeRelatedInformation(reason.typeNode, 'This type is imported using a type-only import, which prevents it from being usable as an injection token.'),
- makeRelatedInformation(reason.node, 'The type-only import occurs here.'),
- ];
- break;
- case 4 /* ValueUnavailableKind.NAMESPACE */:
- chainMessage = 'Consider using the @Inject decorator to specify an injection token.';
- hints = [
- makeRelatedInformation(reason.typeNode, 'This type corresponds with a namespace, which cannot be used as injection token.'),
- makeRelatedInformation(reason.importClause, 'The namespace import occurs here.'),
- ];
- break;
- case 3 /* ValueUnavailableKind.UNKNOWN_REFERENCE */:
- chainMessage = 'The type should reference a known declaration.';
- hints = [makeRelatedInformation(reason.typeNode, 'This type could not be resolved.')];
- break;
- case 0 /* ValueUnavailableKind.MISSING_TYPE */:
- chainMessage =
- 'Consider adding a type to the parameter or use the @Inject decorator to specify an injection token.';
- break;
- }
- const chain = {
- messageText: `No suitable injection token for parameter '${param.name || index}' of class '${clazz.name.text}'.`,
- category: ts.DiagnosticCategory.Error,
- code: 0,
- next: [
- {
- messageText: chainMessage,
- category: ts.DiagnosticCategory.Message,
- code: 0,
- },
- ],
- };
- return new FatalDiagnosticError(exports.ErrorCode.PARAM_MISSING_TOKEN, param.nameNode, chain, hints);
- }
- /**
- * Disambiguates different kinds of compiler metadata objects.
- */
- exports.MetaKind = void 0;
- (function (MetaKind) {
- MetaKind[MetaKind["Directive"] = 0] = "Directive";
- MetaKind[MetaKind["Pipe"] = 1] = "Pipe";
- MetaKind[MetaKind["NgModule"] = 2] = "NgModule";
- })(exports.MetaKind || (exports.MetaKind = {}));
- /**
- * Possible ways that a directive can be matched.
- */
- exports.MatchSource = void 0;
- (function (MatchSource) {
- /** The directive was matched by its selector. */
- MatchSource[MatchSource["Selector"] = 0] = "Selector";
- /** The directive was applied as a host directive. */
- MatchSource[MatchSource["HostDirective"] = 1] = "HostDirective";
- })(exports.MatchSource || (exports.MatchSource = {}));
- /**
- * A mapping of component property and template binding property names, for example containing the
- * inputs of a particular directive or component.
- *
- * A single component property has exactly one input/output annotation (and therefore one binding
- * property name) associated with it, but the same binding property name may be shared across many
- * component property names.
- *
- * Allows bidirectional querying of the mapping - looking up all inputs/outputs with a given
- * property name, or mapping from a specific class property to its binding property name.
- */
- class ClassPropertyMapping {
- /**
- * Mapping from class property names to the single `InputOrOutput` for that class property.
- */
- forwardMap;
- /**
- * Mapping from property names to one or more `InputOrOutput`s which share that name.
- */
- reverseMap;
- constructor(forwardMap) {
- this.forwardMap = forwardMap;
- this.reverseMap = reverseMapFromForwardMap(forwardMap);
- }
- /**
- * Construct a `ClassPropertyMapping` with no entries.
- */
- static empty() {
- return new ClassPropertyMapping(new Map());
- }
- /**
- * Construct a `ClassPropertyMapping` from a primitive JS object which maps class property names
- * to either binding property names or an array that contains both names, which is used in on-disk
- * metadata formats (e.g. in .d.ts files).
- */
- static fromMappedObject(obj) {
- const forwardMap = new Map();
- for (const classPropertyName of Object.keys(obj)) {
- const value = obj[classPropertyName];
- let inputOrOutput;
- if (typeof value === 'string') {
- inputOrOutput = {
- classPropertyName,
- bindingPropertyName: value,
- // Inputs/outputs not captured via an explicit `InputOrOutput` mapping
- // value are always considered non-signal. This is the string shorthand.
- isSignal: false,
- };
- }
- else {
- inputOrOutput = value;
- }
- forwardMap.set(classPropertyName, inputOrOutput);
- }
- return new ClassPropertyMapping(forwardMap);
- }
- /**
- * Merge two mappings into one, with class properties from `b` taking precedence over class
- * properties from `a`.
- */
- static merge(a, b) {
- const forwardMap = new Map(a.forwardMap.entries());
- for (const [classPropertyName, inputOrOutput] of b.forwardMap) {
- forwardMap.set(classPropertyName, inputOrOutput);
- }
- return new ClassPropertyMapping(forwardMap);
- }
- /**
- * All class property names mapped in this mapping.
- */
- get classPropertyNames() {
- return Array.from(this.forwardMap.keys());
- }
- /**
- * All binding property names mapped in this mapping.
- */
- get propertyNames() {
- return Array.from(this.reverseMap.keys());
- }
- /**
- * Check whether a mapping for the given property name exists.
- */
- hasBindingPropertyName(propertyName) {
- return this.reverseMap.has(propertyName);
- }
- /**
- * Lookup all `InputOrOutput`s that use this `propertyName`.
- */
- getByBindingPropertyName(propertyName) {
- return this.reverseMap.has(propertyName) ? this.reverseMap.get(propertyName) : null;
- }
- /**
- * Lookup the `InputOrOutput` associated with a `classPropertyName`.
- */
- getByClassPropertyName(classPropertyName) {
- return this.forwardMap.has(classPropertyName) ? this.forwardMap.get(classPropertyName) : null;
- }
- /**
- * Convert this mapping to a primitive JS object which maps each class property directly to the
- * binding property name associated with it.
- */
- toDirectMappedObject() {
- const obj = {};
- for (const [classPropertyName, inputOrOutput] of this.forwardMap) {
- obj[classPropertyName] = inputOrOutput.bindingPropertyName;
- }
- return obj;
- }
- /**
- * Convert this mapping to a primitive JS object which maps each class property either to itself
- * (for cases where the binding property name is the same) or to an array which contains both
- * names if they differ.
- *
- * This object format is used when mappings are serialized (for example into .d.ts files).
- * @param transform Function used to transform the values of the generated map.
- */
- toJointMappedObject(transform) {
- const obj = {};
- for (const [classPropertyName, inputOrOutput] of this.forwardMap) {
- obj[classPropertyName] = transform(inputOrOutput);
- }
- return obj;
- }
- /**
- * Implement the iterator protocol and return entry objects which contain the class and binding
- * property names (and are useful for destructuring).
- */
- *[Symbol.iterator]() {
- for (const inputOrOutput of this.forwardMap.values()) {
- yield inputOrOutput;
- }
- }
- }
- function reverseMapFromForwardMap(forwardMap) {
- const reverseMap = new Map();
- for (const [_, inputOrOutput] of forwardMap) {
- if (!reverseMap.has(inputOrOutput.bindingPropertyName)) {
- reverseMap.set(inputOrOutput.bindingPropertyName, []);
- }
- reverseMap.get(inputOrOutput.bindingPropertyName).push(inputOrOutput);
- }
- return reverseMap;
- }
- function extractReferencesFromType(checker, def, bestGuessOwningModule) {
- if (!ts.isTupleTypeNode(def)) {
- return { result: [], isIncomplete: false };
- }
- const result = [];
- let isIncomplete = false;
- for (const element of def.elements) {
- if (!ts.isTypeQueryNode(element)) {
- throw new Error(`Expected TypeQueryNode: ${nodeDebugInfo(element)}`);
- }
- const ref = extraReferenceFromTypeQuery(checker, element, def, bestGuessOwningModule);
- // Note: Sometimes a reference inside the type tuple/array
- // may not be resolvable/existent. We proceed with incomplete data.
- if (ref === null) {
- isIncomplete = true;
- }
- else {
- result.push(ref);
- }
- }
- return { result, isIncomplete };
- }
- function extraReferenceFromTypeQuery(checker, typeNode, origin, bestGuessOwningModule) {
- const type = typeNode.exprName;
- let node;
- let from;
- // Gracefully handle when the type entity could not be converted or
- // resolved to its declaration node.
- try {
- const result = reflectTypeEntityToDeclaration(type, checker);
- node = result.node;
- from = result.from;
- }
- catch (e) {
- if (e instanceof TypeEntityToDeclarationError) {
- return null;
- }
- throw e;
- }
- if (!isNamedClassDeclaration(node)) {
- throw new Error(`Expected named ClassDeclaration: ${nodeDebugInfo(node)}`);
- }
- if (from !== null && !from.startsWith('.')) {
- // The symbol was imported using an absolute module specifier so return a reference that
- // uses that absolute module specifier as its best guess owning module.
- return new Reference(node, {
- specifier: from,
- resolutionContext: origin.getSourceFile().fileName,
- });
- }
- // For local symbols or symbols that were imported using a relative module import it is
- // assumed that the symbol is exported from the provided best guess owning module.
- return new Reference(node, bestGuessOwningModule);
- }
- function readBooleanType(type) {
- if (!ts.isLiteralTypeNode(type)) {
- return null;
- }
- switch (type.literal.kind) {
- case ts.SyntaxKind.TrueKeyword:
- return true;
- case ts.SyntaxKind.FalseKeyword:
- return false;
- default:
- return null;
- }
- }
- function readStringType(type) {
- if (!ts.isLiteralTypeNode(type) || !ts.isStringLiteral(type.literal)) {
- return null;
- }
- return type.literal.text;
- }
- function readMapType(type, valueTransform) {
- if (!ts.isTypeLiteralNode(type)) {
- return {};
- }
- const obj = {};
- type.members.forEach((member) => {
- if (!ts.isPropertySignature(member) ||
- member.type === undefined ||
- member.name === undefined ||
- (!ts.isStringLiteral(member.name) && !ts.isIdentifier(member.name))) {
- return;
- }
- const value = valueTransform(member.type);
- if (value !== null) {
- obj[member.name.text] = value;
- }
- });
- return obj;
- }
- function readStringArrayType(type) {
- if (!ts.isTupleTypeNode(type)) {
- return [];
- }
- const res = [];
- type.elements.forEach((el) => {
- if (!ts.isLiteralTypeNode(el) || !ts.isStringLiteral(el.literal)) {
- return;
- }
- res.push(el.literal.text);
- });
- return res;
- }
- /**
- * Inspects the class' members and extracts the metadata that is used when type-checking templates
- * that use the directive. This metadata does not contain information from a base class, if any,
- * making this metadata invariant to changes of inherited classes.
- */
- function extractDirectiveTypeCheckMeta(node, inputs, reflector) {
- const members = reflector.getMembersOfClass(node);
- const staticMembers = members.filter((member) => member.isStatic);
- const ngTemplateGuards = staticMembers
- .map(extractTemplateGuard)
- .filter((guard) => guard !== null);
- const hasNgTemplateContextGuard = staticMembers.some((member) => member.kind === exports.ClassMemberKind.Method && member.name === 'ngTemplateContextGuard');
- const coercedInputFields = new Set(staticMembers.map(extractCoercedInput).filter((inputName) => {
- // If the input refers to a signal input, we will not respect coercion members.
- // A transform function should be used instead.
- if (inputName === null || inputs.getByClassPropertyName(inputName)?.isSignal) {
- return false;
- }
- return true;
- }));
- const restrictedInputFields = new Set();
- const stringLiteralInputFields = new Set();
- const undeclaredInputFields = new Set();
- for (const { classPropertyName, transform } of inputs) {
- const field = members.find((member) => member.name === classPropertyName);
- if (field === undefined || field.node === null) {
- undeclaredInputFields.add(classPropertyName);
- continue;
- }
- if (isRestricted(field.node)) {
- restrictedInputFields.add(classPropertyName);
- }
- if (field.nameNode !== null && ts.isStringLiteral(field.nameNode)) {
- stringLiteralInputFields.add(classPropertyName);
- }
- if (transform !== null) {
- coercedInputFields.add(classPropertyName);
- }
- }
- const arity = reflector.getGenericArityOfClass(node);
- return {
- hasNgTemplateContextGuard,
- ngTemplateGuards,
- coercedInputFields,
- restrictedInputFields,
- stringLiteralInputFields,
- undeclaredInputFields,
- isGeneric: arity !== null && arity > 0,
- };
- }
- function isRestricted(node) {
- const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
- return (modifiers !== undefined &&
- modifiers.some(({ kind }) => {
- return (kind === ts.SyntaxKind.PrivateKeyword ||
- kind === ts.SyntaxKind.ProtectedKeyword ||
- kind === ts.SyntaxKind.ReadonlyKeyword);
- }));
- }
- function extractTemplateGuard(member) {
- if (!member.name.startsWith('ngTemplateGuard_')) {
- return null;
- }
- const inputName = afterUnderscore(member.name);
- if (member.kind === exports.ClassMemberKind.Property) {
- let type = null;
- if (member.type !== null &&
- ts.isLiteralTypeNode(member.type) &&
- ts.isStringLiteral(member.type.literal)) {
- type = member.type.literal.text;
- }
- // Only property members with string literal type 'binding' are considered as template guard.
- if (type !== 'binding') {
- return null;
- }
- return { inputName, type };
- }
- else if (member.kind === exports.ClassMemberKind.Method) {
- return { inputName, type: 'invocation' };
- }
- else {
- return null;
- }
- }
- function extractCoercedInput(member) {
- if (member.kind !== exports.ClassMemberKind.Property || !member.name.startsWith('ngAcceptInputType_')) {
- return null;
- }
- return afterUnderscore(member.name);
- }
- /**
- * A `MetadataReader` that reads from an ordered set of child readers until it obtains the requested
- * metadata.
- *
- * This is used to combine `MetadataReader`s that read from different sources (e.g. from a registry
- * and from .d.ts files).
- */
- class CompoundMetadataReader {
- readers;
- constructor(readers) {
- this.readers = readers;
- }
- getDirectiveMetadata(node) {
- for (const reader of this.readers) {
- const meta = reader.getDirectiveMetadata(node);
- if (meta !== null) {
- return meta;
- }
- }
- return null;
- }
- getNgModuleMetadata(node) {
- for (const reader of this.readers) {
- const meta = reader.getNgModuleMetadata(node);
- if (meta !== null) {
- return meta;
- }
- }
- return null;
- }
- getPipeMetadata(node) {
- for (const reader of this.readers) {
- const meta = reader.getPipeMetadata(node);
- if (meta !== null) {
- return meta;
- }
- }
- return null;
- }
- }
- function afterUnderscore(str) {
- const pos = str.indexOf('_');
- if (pos === -1) {
- throw new Error(`Expected '${str}' to contain '_'`);
- }
- return str.slice(pos + 1);
- }
- /** Returns whether a class declaration has the necessary class fields to make it injectable. */
- function hasInjectableFields(clazz, host) {
- const members = host.getMembersOfClass(clazz);
- return members.some(({ isStatic, name }) => isStatic && (name === 'ɵprov' || name === 'ɵfac'));
- }
- function isHostDirectiveMetaForGlobalMode(hostDirectiveMeta) {
- return hostDirectiveMeta.directive instanceof Reference;
- }
- /**
- * Given a reference to a directive, return a flattened version of its `DirectiveMeta` metadata
- * which includes metadata from its entire inheritance chain.
- *
- * The returned `DirectiveMeta` will either have `baseClass: null` if the inheritance chain could be
- * fully resolved, or `baseClass: 'dynamic'` if the inheritance chain could not be completely
- * followed.
- */
- function flattenInheritedDirectiveMetadata(reader, dir) {
- const topMeta = reader.getDirectiveMetadata(dir);
- if (topMeta === null) {
- return null;
- }
- if (topMeta.baseClass === null) {
- return topMeta;
- }
- const coercedInputFields = new Set();
- const undeclaredInputFields = new Set();
- const restrictedInputFields = new Set();
- const stringLiteralInputFields = new Set();
- let hostDirectives = null;
- let isDynamic = false;
- let inputs = ClassPropertyMapping.empty();
- let outputs = ClassPropertyMapping.empty();
- let isStructural = false;
- const addMetadata = (meta) => {
- if (meta.baseClass === 'dynamic') {
- isDynamic = true;
- }
- else if (meta.baseClass !== null) {
- const baseMeta = reader.getDirectiveMetadata(meta.baseClass);
- if (baseMeta !== null) {
- addMetadata(baseMeta);
- }
- else {
- // Missing metadata for the base class means it's effectively dynamic.
- isDynamic = true;
- }
- }
- isStructural = isStructural || meta.isStructural;
- inputs = ClassPropertyMapping.merge(inputs, meta.inputs);
- outputs = ClassPropertyMapping.merge(outputs, meta.outputs);
- for (const coercedInputField of meta.coercedInputFields) {
- coercedInputFields.add(coercedInputField);
- }
- for (const undeclaredInputField of meta.undeclaredInputFields) {
- undeclaredInputFields.add(undeclaredInputField);
- }
- for (const restrictedInputField of meta.restrictedInputFields) {
- restrictedInputFields.add(restrictedInputField);
- }
- for (const field of meta.stringLiteralInputFields) {
- stringLiteralInputFields.add(field);
- }
- if (meta.hostDirectives !== null && meta.hostDirectives.length > 0) {
- hostDirectives ??= [];
- hostDirectives.push(...meta.hostDirectives);
- }
- };
- addMetadata(topMeta);
- return {
- ...topMeta,
- inputs,
- outputs,
- coercedInputFields,
- undeclaredInputFields,
- restrictedInputFields,
- stringLiteralInputFields,
- baseClass: isDynamic ? 'dynamic' : null,
- isStructural,
- hostDirectives,
- };
- }
- /**
- * Represents a value which cannot be determined statically.
- */
- class DynamicValue {
- node;
- reason;
- code;
- constructor(node, reason, code) {
- this.node = node;
- this.reason = reason;
- this.code = code;
- }
- static fromDynamicInput(node, input) {
- return new DynamicValue(node, input, 0 /* DynamicValueReason.DYNAMIC_INPUT */);
- }
- static fromDynamicString(node) {
- return new DynamicValue(node, undefined, 1 /* DynamicValueReason.DYNAMIC_STRING */);
- }
- static fromExternalReference(node, ref) {
- return new DynamicValue(node, ref, 2 /* DynamicValueReason.EXTERNAL_REFERENCE */);
- }
- static fromUnsupportedSyntax(node) {
- return new DynamicValue(node, undefined, 3 /* DynamicValueReason.UNSUPPORTED_SYNTAX */);
- }
- static fromUnknownIdentifier(node) {
- return new DynamicValue(node, undefined, 4 /* DynamicValueReason.UNKNOWN_IDENTIFIER */);
- }
- static fromInvalidExpressionType(node, value) {
- return new DynamicValue(node, value, 5 /* DynamicValueReason.INVALID_EXPRESSION_TYPE */);
- }
- static fromComplexFunctionCall(node, fn) {
- return new DynamicValue(node, fn, 6 /* DynamicValueReason.COMPLEX_FUNCTION_CALL */);
- }
- static fromDynamicType(node) {
- return new DynamicValue(node, undefined, 7 /* DynamicValueReason.DYNAMIC_TYPE */);
- }
- static fromSyntheticInput(node, value) {
- return new DynamicValue(node, value, 8 /* DynamicValueReason.SYNTHETIC_INPUT */);
- }
- static fromUnknown(node) {
- return new DynamicValue(node, undefined, 9 /* DynamicValueReason.UNKNOWN */);
- }
- isFromDynamicInput() {
- return this.code === 0 /* DynamicValueReason.DYNAMIC_INPUT */;
- }
- isFromDynamicString() {
- return this.code === 1 /* DynamicValueReason.DYNAMIC_STRING */;
- }
- isFromExternalReference() {
- return this.code === 2 /* DynamicValueReason.EXTERNAL_REFERENCE */;
- }
- isFromUnsupportedSyntax() {
- return this.code === 3 /* DynamicValueReason.UNSUPPORTED_SYNTAX */;
- }
- isFromUnknownIdentifier() {
- return this.code === 4 /* DynamicValueReason.UNKNOWN_IDENTIFIER */;
- }
- isFromInvalidExpressionType() {
- return this.code === 5 /* DynamicValueReason.INVALID_EXPRESSION_TYPE */;
- }
- isFromComplexFunctionCall() {
- return this.code === 6 /* DynamicValueReason.COMPLEX_FUNCTION_CALL */;
- }
- isFromDynamicType() {
- return this.code === 7 /* DynamicValueReason.DYNAMIC_TYPE */;
- }
- isFromUnknown() {
- return this.code === 9 /* DynamicValueReason.UNKNOWN */;
- }
- accept(visitor) {
- switch (this.code) {
- case 0 /* DynamicValueReason.DYNAMIC_INPUT */:
- return visitor.visitDynamicInput(this);
- case 1 /* DynamicValueReason.DYNAMIC_STRING */:
- return visitor.visitDynamicString(this);
- case 2 /* DynamicValueReason.EXTERNAL_REFERENCE */:
- return visitor.visitExternalReference(this);
- case 3 /* DynamicValueReason.UNSUPPORTED_SYNTAX */:
- return visitor.visitUnsupportedSyntax(this);
- case 4 /* DynamicValueReason.UNKNOWN_IDENTIFIER */:
- return visitor.visitUnknownIdentifier(this);
- case 5 /* DynamicValueReason.INVALID_EXPRESSION_TYPE */:
- return visitor.visitInvalidExpressionType(this);
- case 6 /* DynamicValueReason.COMPLEX_FUNCTION_CALL */:
- return visitor.visitComplexFunctionCall(this);
- case 7 /* DynamicValueReason.DYNAMIC_TYPE */:
- return visitor.visitDynamicType(this);
- case 8 /* DynamicValueReason.SYNTHETIC_INPUT */:
- return visitor.visitSyntheticInput(this);
- case 9 /* DynamicValueReason.UNKNOWN */:
- return visitor.visitUnknown(this);
- }
- }
- }
- /**
- * A collection of publicly exported declarations from a module. Each declaration is evaluated
- * lazily upon request.
- */
- class ResolvedModule {
- exports;
- evaluate;
- constructor(exports, evaluate) {
- this.exports = exports;
- this.evaluate = evaluate;
- }
- getExport(name) {
- if (!this.exports.has(name)) {
- return undefined;
- }
- return this.evaluate(this.exports.get(name));
- }
- getExports() {
- const map = new Map();
- this.exports.forEach((decl, name) => {
- map.set(name, this.evaluate(decl));
- });
- return map;
- }
- }
- /**
- * A value member of an enumeration.
- *
- * Contains a `Reference` to the enumeration itself, and the name of the referenced member.
- */
- class EnumValue {
- enumRef;
- name;
- resolved;
- constructor(enumRef, name, resolved) {
- this.enumRef = enumRef;
- this.name = name;
- this.resolved = resolved;
- }
- }
- /**
- * An implementation of a known function that can be statically evaluated.
- * It could be a built-in function or method (such as `Array.prototype.slice`) or a TypeScript
- * helper (such as `__spread`).
- */
- class KnownFn {
- }
- /**
- * Derives a type representation from a resolved value to be reported in a diagnostic.
- *
- * @param value The resolved value for which a type representation should be derived.
- * @param maxDepth The maximum nesting depth of objects and arrays, defaults to 1 level.
- */
- function describeResolvedType(value, maxDepth = 1) {
- if (value === null) {
- return 'null';
- }
- else if (value === undefined) {
- return 'undefined';
- }
- else if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'string') {
- return typeof value;
- }
- else if (value instanceof Map) {
- if (maxDepth === 0) {
- return 'object';
- }
- const entries = Array.from(value.entries()).map(([key, v]) => {
- return `${quoteKey(key)}: ${describeResolvedType(v, maxDepth - 1)}`;
- });
- return entries.length > 0 ? `{ ${entries.join('; ')} }` : '{}';
- }
- else if (value instanceof ResolvedModule) {
- return '(module)';
- }
- else if (value instanceof EnumValue) {
- return value.enumRef.debugName ?? '(anonymous)';
- }
- else if (value instanceof Reference) {
- return value.debugName ?? '(anonymous)';
- }
- else if (Array.isArray(value)) {
- if (maxDepth === 0) {
- return 'Array';
- }
- return `[${value.map((v) => describeResolvedType(v, maxDepth - 1)).join(', ')}]`;
- }
- else if (value instanceof DynamicValue) {
- return '(not statically analyzable)';
- }
- else if (value instanceof KnownFn) {
- return 'Function';
- }
- else {
- return 'unknown';
- }
- }
- function quoteKey(key) {
- if (/^[a-z0-9_]+$/i.test(key)) {
- return key;
- }
- else {
- return `'${key.replace(/'/g, "\\'")}'`;
- }
- }
- /**
- * Creates an array of related information diagnostics for a `DynamicValue` that describe the trace
- * of why an expression was evaluated as dynamic.
- *
- * @param node The node for which a `ts.Diagnostic` is to be created with the trace.
- * @param value The dynamic value for which a trace should be created.
- */
- function traceDynamicValue(node, value) {
- return value.accept(new TraceDynamicValueVisitor(node));
- }
- class TraceDynamicValueVisitor {
- node;
- currentContainerNode = null;
- constructor(node) {
- this.node = node;
- }
- visitDynamicInput(value) {
- const trace = value.reason.accept(this);
- if (this.shouldTrace(value.node)) {
- const info = makeRelatedInformation(value.node, 'Unable to evaluate this expression statically.');
- trace.unshift(info);
- }
- return trace;
- }
- visitSyntheticInput(value) {
- return [makeRelatedInformation(value.node, 'Unable to evaluate this expression further.')];
- }
- visitDynamicString(value) {
- return [
- makeRelatedInformation(value.node, 'A string value could not be determined statically.'),
- ];
- }
- visitExternalReference(value) {
- const name = value.reason.debugName;
- const description = name !== null ? `'${name}'` : 'an anonymous declaration';
- return [
- makeRelatedInformation(value.node, `A value for ${description} cannot be determined statically, as it is an external declaration.`),
- ];
- }
- visitComplexFunctionCall(value) {
- return [
- makeRelatedInformation(value.node, 'Unable to evaluate function call of complex function. A function must have exactly one return statement.'),
- makeRelatedInformation(value.reason.node, 'Function is declared here.'),
- ];
- }
- visitInvalidExpressionType(value) {
- return [makeRelatedInformation(value.node, 'Unable to evaluate an invalid expression.')];
- }
- visitUnknown(value) {
- return [makeRelatedInformation(value.node, 'Unable to evaluate statically.')];
- }
- visitUnknownIdentifier(value) {
- return [makeRelatedInformation(value.node, 'Unknown reference.')];
- }
- visitDynamicType(value) {
- return [makeRelatedInformation(value.node, 'Dynamic type.')];
- }
- visitUnsupportedSyntax(value) {
- return [makeRelatedInformation(value.node, 'This syntax is not supported.')];
- }
- /**
- * Determines whether the dynamic value reported for the node should be traced, i.e. if it is not
- * part of the container for which the most recent trace was created.
- */
- shouldTrace(node) {
- if (node === this.node) {
- // Do not include a dynamic value for the origin node, as the main diagnostic is already
- // reported on that node.
- return false;
- }
- const container = getContainerNode(node);
- if (container === this.currentContainerNode) {
- // The node is part of the same container as the previous trace entry, so this dynamic value
- // should not become part of the trace.
- return false;
- }
- this.currentContainerNode = container;
- return true;
- }
- }
- /**
- * Determines the closest parent node that is to be considered as container, which is used to reduce
- * the granularity of tracing the dynamic values to a single entry per container. Currently, full
- * statements and destructuring patterns are considered as container.
- */
- function getContainerNode(node) {
- let currentNode = node;
- while (currentNode !== undefined) {
- switch (currentNode.kind) {
- case ts.SyntaxKind.ExpressionStatement:
- case ts.SyntaxKind.VariableStatement:
- case ts.SyntaxKind.ReturnStatement:
- case ts.SyntaxKind.IfStatement:
- case ts.SyntaxKind.SwitchStatement:
- case ts.SyntaxKind.DoStatement:
- case ts.SyntaxKind.WhileStatement:
- case ts.SyntaxKind.ForStatement:
- case ts.SyntaxKind.ForInStatement:
- case ts.SyntaxKind.ForOfStatement:
- case ts.SyntaxKind.ContinueStatement:
- case ts.SyntaxKind.BreakStatement:
- case ts.SyntaxKind.ThrowStatement:
- case ts.SyntaxKind.ObjectBindingPattern:
- case ts.SyntaxKind.ArrayBindingPattern:
- return currentNode;
- }
- currentNode = currentNode.parent;
- }
- return node.getSourceFile();
- }
- class ArraySliceBuiltinFn extends KnownFn {
- lhs;
- constructor(lhs) {
- super();
- this.lhs = lhs;
- }
- evaluate(node, args) {
- if (args.length === 0) {
- return this.lhs;
- }
- else {
- return DynamicValue.fromUnknown(node);
- }
- }
- }
- class ArrayConcatBuiltinFn extends KnownFn {
- lhs;
- constructor(lhs) {
- super();
- this.lhs = lhs;
- }
- evaluate(node, args) {
- const result = [...this.lhs];
- for (const arg of args) {
- if (arg instanceof DynamicValue) {
- result.push(DynamicValue.fromDynamicInput(node, arg));
- }
- else if (Array.isArray(arg)) {
- result.push(...arg);
- }
- else {
- result.push(arg);
- }
- }
- return result;
- }
- }
- class StringConcatBuiltinFn extends KnownFn {
- lhs;
- constructor(lhs) {
- super();
- this.lhs = lhs;
- }
- evaluate(node, args) {
- let result = this.lhs;
- for (const arg of args) {
- const resolved = arg instanceof EnumValue ? arg.resolved : arg;
- if (typeof resolved === 'string' ||
- typeof resolved === 'number' ||
- typeof resolved === 'boolean' ||
- resolved == null) {
- // Cast to `any`, because `concat` will convert
- // anything to a string, but TS only allows strings.
- result = result.concat(resolved);
- }
- else {
- return DynamicValue.fromUnknown(node);
- }
- }
- return result;
- }
- }
- /**
- * A value produced which originated in a `ForeignFunctionResolver` and doesn't come from the
- * template itself.
- *
- * Synthetic values cannot be further evaluated, and attempts to do so produce `DynamicValue`s
- * instead.
- */
- class SyntheticValue {
- value;
- constructor(value) {
- this.value = value;
- }
- }
- function literalBinaryOp(op) {
- return { op, literal: true };
- }
- function referenceBinaryOp(op) {
- return { op, literal: false };
- }
- const BINARY_OPERATORS$2 = new Map([
- [ts.SyntaxKind.PlusToken, literalBinaryOp((a, b) => a + b)],
- [ts.SyntaxKind.MinusToken, literalBinaryOp((a, b) => a - b)],
- [ts.SyntaxKind.AsteriskToken, literalBinaryOp((a, b) => a * b)],
- [ts.SyntaxKind.SlashToken, literalBinaryOp((a, b) => a / b)],
- [ts.SyntaxKind.PercentToken, literalBinaryOp((a, b) => a % b)],
- [ts.SyntaxKind.AmpersandToken, literalBinaryOp((a, b) => a & b)],
- [ts.SyntaxKind.BarToken, literalBinaryOp((a, b) => a | b)],
- [ts.SyntaxKind.CaretToken, literalBinaryOp((a, b) => a ^ b)],
- [ts.SyntaxKind.LessThanToken, literalBinaryOp((a, b) => a < b)],
- [ts.SyntaxKind.LessThanEqualsToken, literalBinaryOp((a, b) => a <= b)],
- [ts.SyntaxKind.GreaterThanToken, literalBinaryOp((a, b) => a > b)],
- [ts.SyntaxKind.GreaterThanEqualsToken, literalBinaryOp((a, b) => a >= b)],
- [ts.SyntaxKind.EqualsEqualsToken, literalBinaryOp((a, b) => a == b)],
- [ts.SyntaxKind.EqualsEqualsEqualsToken, literalBinaryOp((a, b) => a === b)],
- [ts.SyntaxKind.ExclamationEqualsToken, literalBinaryOp((a, b) => a != b)],
- [ts.SyntaxKind.ExclamationEqualsEqualsToken, literalBinaryOp((a, b) => a !== b)],
- [ts.SyntaxKind.LessThanLessThanToken, literalBinaryOp((a, b) => a << b)],
- [ts.SyntaxKind.GreaterThanGreaterThanToken, literalBinaryOp((a, b) => a >> b)],
- [ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken, literalBinaryOp((a, b) => a >>> b)],
- [ts.SyntaxKind.AsteriskAsteriskToken, literalBinaryOp((a, b) => Math.pow(a, b))],
- [ts.SyntaxKind.AmpersandAmpersandToken, referenceBinaryOp((a, b) => a && b)],
- [ts.SyntaxKind.BarBarToken, referenceBinaryOp((a, b) => a || b)],
- ]);
- const UNARY_OPERATORS$2 = new Map([
- [ts.SyntaxKind.TildeToken, (a) => ~a],
- [ts.SyntaxKind.MinusToken, (a) => -a],
- [ts.SyntaxKind.PlusToken, (a) => +a],
- [ts.SyntaxKind.ExclamationToken, (a) => !a],
- ]);
- class StaticInterpreter {
- host;
- checker;
- dependencyTracker;
- constructor(host, checker, dependencyTracker) {
- this.host = host;
- this.checker = checker;
- this.dependencyTracker = dependencyTracker;
- }
- visit(node, context) {
- return this.visitExpression(node, context);
- }
- visitExpression(node, context) {
- let result;
- if (node.kind === ts.SyntaxKind.TrueKeyword) {
- return true;
- }
- else if (node.kind === ts.SyntaxKind.FalseKeyword) {
- return false;
- }
- else if (node.kind === ts.SyntaxKind.NullKeyword) {
- return null;
- }
- else if (ts.isStringLiteral(node)) {
- return node.text;
- }
- else if (ts.isNoSubstitutionTemplateLiteral(node)) {
- return node.text;
- }
- else if (ts.isTemplateExpression(node)) {
- result = this.visitTemplateExpression(node, context);
- }
- else if (ts.isNumericLiteral(node)) {
- return parseFloat(node.text);
- }
- else if (ts.isObjectLiteralExpression(node)) {
- result = this.visitObjectLiteralExpression(node, context);
- }
- else if (ts.isIdentifier(node)) {
- result = this.visitIdentifier(node, context);
- }
- else if (ts.isPropertyAccessExpression(node)) {
- result = this.visitPropertyAccessExpression(node, context);
- }
- else if (ts.isCallExpression(node)) {
- result = this.visitCallExpression(node, context);
- }
- else if (ts.isConditionalExpression(node)) {
- result = this.visitConditionalExpression(node, context);
- }
- else if (ts.isPrefixUnaryExpression(node)) {
- result = this.visitPrefixUnaryExpression(node, context);
- }
- else if (ts.isBinaryExpression(node)) {
- result = this.visitBinaryExpression(node, context);
- }
- else if (ts.isArrayLiteralExpression(node)) {
- result = this.visitArrayLiteralExpression(node, context);
- }
- else if (ts.isParenthesizedExpression(node)) {
- result = this.visitParenthesizedExpression(node, context);
- }
- else if (ts.isElementAccessExpression(node)) {
- result = this.visitElementAccessExpression(node, context);
- }
- else if (ts.isAsExpression(node)) {
- result = this.visitExpression(node.expression, context);
- }
- else if (ts.isNonNullExpression(node)) {
- result = this.visitExpression(node.expression, context);
- }
- else if (this.host.isClass(node)) {
- result = this.visitDeclaration(node, context);
- }
- else {
- return DynamicValue.fromUnsupportedSyntax(node);
- }
- if (result instanceof DynamicValue && result.node !== node) {
- return DynamicValue.fromDynamicInput(node, result);
- }
- return result;
- }
- visitArrayLiteralExpression(node, context) {
- const array = [];
- for (let i = 0; i < node.elements.length; i++) {
- const element = node.elements[i];
- if (ts.isSpreadElement(element)) {
- array.push(...this.visitSpreadElement(element, context));
- }
- else {
- array.push(this.visitExpression(element, context));
- }
- }
- return array;
- }
- visitObjectLiteralExpression(node, context) {
- const map = new Map();
- for (let i = 0; i < node.properties.length; i++) {
- const property = node.properties[i];
- if (ts.isPropertyAssignment(property)) {
- const name = this.stringNameFromPropertyName(property.name, context);
- // Check whether the name can be determined statically.
- if (name === undefined) {
- return DynamicValue.fromDynamicInput(node, DynamicValue.fromDynamicString(property.name));
- }
- map.set(name, this.visitExpression(property.initializer, context));
- }
- else if (ts.isShorthandPropertyAssignment(property)) {
- const symbol = this.checker.getShorthandAssignmentValueSymbol(property);
- if (symbol === undefined || symbol.valueDeclaration === undefined) {
- map.set(property.name.text, DynamicValue.fromUnknown(property));
- }
- else {
- map.set(property.name.text, this.visitDeclaration(symbol.valueDeclaration, context));
- }
- }
- else if (ts.isSpreadAssignment(property)) {
- const spread = this.visitExpression(property.expression, context);
- if (spread instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, spread);
- }
- else if (spread instanceof Map) {
- spread.forEach((value, key) => map.set(key, value));
- }
- else if (spread instanceof ResolvedModule) {
- spread.getExports().forEach((value, key) => map.set(key, value));
- }
- else {
- return DynamicValue.fromDynamicInput(node, DynamicValue.fromInvalidExpressionType(property, spread));
- }
- }
- else {
- return DynamicValue.fromUnknown(node);
- }
- }
- return map;
- }
- visitTemplateExpression(node, context) {
- const pieces = [node.head.text];
- for (let i = 0; i < node.templateSpans.length; i++) {
- const span = node.templateSpans[i];
- const value = literal(this.visit(span.expression, context), () => DynamicValue.fromDynamicString(span.expression));
- if (value instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, value);
- }
- pieces.push(`${value}`, span.literal.text);
- }
- return pieces.join('');
- }
- visitIdentifier(node, context) {
- const decl = this.host.getDeclarationOfIdentifier(node);
- if (decl === null) {
- if (ts.identifierToKeywordKind(node) === ts.SyntaxKind.UndefinedKeyword) {
- return undefined;
- }
- else {
- // Check if the symbol here is imported.
- if (this.dependencyTracker !== null && this.host.getImportOfIdentifier(node) !== null) {
- // It was, but no declaration for the node could be found. This means that the dependency
- // graph for the current file cannot be properly updated to account for this (broken)
- // import. Instead, the originating file is reported as failing dependency analysis,
- // ensuring that future compilations will always attempt to re-resolve the previously
- // broken identifier.
- this.dependencyTracker.recordDependencyAnalysisFailure(context.originatingFile);
- }
- return DynamicValue.fromUnknownIdentifier(node);
- }
- }
- const declContext = { ...context, ...joinModuleContext(context, node, decl) };
- const result = this.visitDeclaration(decl.node, declContext);
- if (result instanceof Reference) {
- // Only record identifiers to non-synthetic references. Synthetic references may not have the
- // same value at runtime as they do at compile time, so it's not legal to refer to them by the
- // identifier here.
- if (!result.synthetic) {
- result.addIdentifier(node);
- }
- }
- else if (result instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, result);
- }
- return result;
- }
- visitDeclaration(node, context) {
- if (this.dependencyTracker !== null) {
- this.dependencyTracker.addDependency(context.originatingFile, node.getSourceFile());
- }
- if (this.host.isClass(node)) {
- return this.getReference(node, context);
- }
- else if (ts.isVariableDeclaration(node)) {
- return this.visitVariableDeclaration(node, context);
- }
- else if (ts.isParameter(node) && context.scope.has(node)) {
- return context.scope.get(node);
- }
- else if (ts.isExportAssignment(node)) {
- return this.visitExpression(node.expression, context);
- }
- else if (ts.isEnumDeclaration(node)) {
- return this.visitEnumDeclaration(node, context);
- }
- else if (ts.isSourceFile(node)) {
- return this.visitSourceFile(node, context);
- }
- else if (ts.isBindingElement(node)) {
- return this.visitBindingElement(node, context);
- }
- else {
- return this.getReference(node, context);
- }
- }
- visitVariableDeclaration(node, context) {
- const value = this.host.getVariableValue(node);
- if (value !== null) {
- return this.visitExpression(value, context);
- }
- else if (isVariableDeclarationDeclared(node)) {
- // If the declaration has a literal type that can be statically reduced to a value, resolve to
- // that value. If not, the historical behavior for variable declarations is to return a
- // `Reference` to the variable, as the consumer could use it in a context where knowing its
- // static value is not necessary.
- //
- // Arguably, since the value cannot be statically determined, we should return a
- // `DynamicValue`. This returns a `Reference` because it's the same behavior as before
- // `visitType` was introduced.
- //
- // TODO(zarend): investigate switching to a `DynamicValue` and verify this won't break any
- // use cases, especially in ngcc
- if (node.type !== undefined) {
- const evaluatedType = this.visitType(node.type, context);
- if (!(evaluatedType instanceof DynamicValue)) {
- return evaluatedType;
- }
- }
- return this.getReference(node, context);
- }
- else {
- return undefined;
- }
- }
- visitEnumDeclaration(node, context) {
- const enumRef = this.getReference(node, context);
- const map = new Map();
- node.members.forEach((member, index) => {
- const name = this.stringNameFromPropertyName(member.name, context);
- if (name !== undefined) {
- const resolved = member.initializer ? this.visit(member.initializer, context) : index;
- map.set(name, new EnumValue(enumRef, name, resolved));
- }
- });
- return map;
- }
- visitElementAccessExpression(node, context) {
- const lhs = this.visitExpression(node.expression, context);
- if (lhs instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, lhs);
- }
- const rhs = this.visitExpression(node.argumentExpression, context);
- if (rhs instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, rhs);
- }
- if (typeof rhs !== 'string' && typeof rhs !== 'number') {
- return DynamicValue.fromInvalidExpressionType(node, rhs);
- }
- return this.accessHelper(node, lhs, rhs, context);
- }
- visitPropertyAccessExpression(node, context) {
- const lhs = this.visitExpression(node.expression, context);
- const rhs = node.name.text;
- // TODO: handle reference to class declaration.
- if (lhs instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, lhs);
- }
- return this.accessHelper(node, lhs, rhs, context);
- }
- visitSourceFile(node, context) {
- const declarations = this.host.getExportsOfModule(node);
- if (declarations === null) {
- return DynamicValue.fromUnknown(node);
- }
- return new ResolvedModule(declarations, (decl) => {
- const declContext = {
- ...context,
- ...joinModuleContext(context, node, decl),
- };
- // Visit both concrete and inline declarations.
- return this.visitDeclaration(decl.node, declContext);
- });
- }
- accessHelper(node, lhs, rhs, context) {
- const strIndex = `${rhs}`;
- if (lhs instanceof Map) {
- if (lhs.has(strIndex)) {
- return lhs.get(strIndex);
- }
- else {
- return undefined;
- }
- }
- else if (lhs instanceof ResolvedModule) {
- return lhs.getExport(strIndex);
- }
- else if (Array.isArray(lhs)) {
- if (rhs === 'length') {
- return lhs.length;
- }
- else if (rhs === 'slice') {
- return new ArraySliceBuiltinFn(lhs);
- }
- else if (rhs === 'concat') {
- return new ArrayConcatBuiltinFn(lhs);
- }
- if (typeof rhs !== 'number' || !Number.isInteger(rhs)) {
- return DynamicValue.fromInvalidExpressionType(node, rhs);
- }
- return lhs[rhs];
- }
- else if (typeof lhs === 'string' && rhs === 'concat') {
- return new StringConcatBuiltinFn(lhs);
- }
- else if (lhs instanceof Reference) {
- const ref = lhs.node;
- if (this.host.isClass(ref)) {
- const module = owningModule(context, lhs.bestGuessOwningModule);
- let value = undefined;
- const member = this.host
- .getMembersOfClass(ref)
- .find((member) => member.isStatic && member.name === strIndex);
- if (member !== undefined) {
- if (member.value !== null) {
- value = this.visitExpression(member.value, context);
- }
- else if (member.implementation !== null) {
- value = new Reference(member.implementation, module);
- }
- else if (member.node) {
- value = new Reference(member.node, module);
- }
- }
- return value;
- }
- else if (isDeclaration(ref)) {
- return DynamicValue.fromDynamicInput(node, DynamicValue.fromExternalReference(ref, lhs));
- }
- }
- else if (lhs instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, lhs);
- }
- else if (lhs instanceof SyntheticValue) {
- return DynamicValue.fromSyntheticInput(node, lhs);
- }
- return DynamicValue.fromUnknown(node);
- }
- visitCallExpression(node, context) {
- const lhs = this.visitExpression(node.expression, context);
- if (lhs instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, lhs);
- }
- // If the call refers to a builtin function, attempt to evaluate the function.
- if (lhs instanceof KnownFn) {
- return lhs.evaluate(node, this.evaluateFunctionArguments(node, context));
- }
- if (!(lhs instanceof Reference)) {
- return DynamicValue.fromInvalidExpressionType(node.expression, lhs);
- }
- const fn = this.host.getDefinitionOfFunction(lhs.node);
- if (fn === null) {
- return DynamicValue.fromInvalidExpressionType(node.expression, lhs);
- }
- if (!isFunctionOrMethodReference(lhs)) {
- return DynamicValue.fromInvalidExpressionType(node.expression, lhs);
- }
- const resolveFfrExpr = (expr) => {
- let contextExtension = {};
- // TODO(alxhub): the condition `fn.body === null` here is vestigial - we probably _do_ want to
- // change the context like this even for non-null function bodies. But, this is being
- // redesigned as a refactoring with no behavior changes so that should be done as a follow-up.
- if (fn.body === null &&
- expr.getSourceFile() !== node.expression.getSourceFile() &&
- lhs.bestGuessOwningModule !== null) {
- contextExtension = {
- absoluteModuleName: lhs.bestGuessOwningModule.specifier,
- resolutionContext: lhs.bestGuessOwningModule.resolutionContext,
- };
- }
- return this.visitFfrExpression(expr, { ...context, ...contextExtension });
- };
- // If the function is foreign (declared through a d.ts file), attempt to resolve it with the
- // foreignFunctionResolver, if one is specified.
- if (fn.body === null && context.foreignFunctionResolver !== undefined) {
- const unresolvable = DynamicValue.fromDynamicInput(node, DynamicValue.fromExternalReference(node.expression, lhs));
- return context.foreignFunctionResolver(lhs, node, resolveFfrExpr, unresolvable);
- }
- const res = this.visitFunctionBody(node, fn, context);
- // If the result of attempting to resolve the function body was a DynamicValue, attempt to use
- // the foreignFunctionResolver if one is present. This could still potentially yield a usable
- // value.
- if (res instanceof DynamicValue && context.foreignFunctionResolver !== undefined) {
- const unresolvable = DynamicValue.fromComplexFunctionCall(node, fn);
- return context.foreignFunctionResolver(lhs, node, resolveFfrExpr, unresolvable);
- }
- return res;
- }
- /**
- * Visit an expression which was extracted from a foreign-function resolver.
- *
- * This will process the result and ensure it's correct for FFR-resolved values, including marking
- * `Reference`s as synthetic.
- */
- visitFfrExpression(expr, context) {
- const res = this.visitExpression(expr, context);
- if (res instanceof Reference) {
- // This Reference was created synthetically, via a foreign function resolver. The real
- // runtime value of the function expression may be different than the foreign function
- // resolved value, so mark the Reference as synthetic to avoid it being misinterpreted.
- res.synthetic = true;
- }
- return res;
- }
- visitFunctionBody(node, fn, context) {
- if (fn.body === null) {
- return DynamicValue.fromUnknown(node);
- }
- else if (fn.body.length !== 1 || !ts.isReturnStatement(fn.body[0])) {
- return DynamicValue.fromComplexFunctionCall(node, fn);
- }
- const ret = fn.body[0];
- const args = this.evaluateFunctionArguments(node, context);
- const newScope = new Map();
- const calleeContext = { ...context, scope: newScope };
- fn.parameters.forEach((param, index) => {
- let arg = args[index];
- if (param.node.dotDotDotToken !== undefined) {
- arg = args.slice(index);
- }
- if (arg === undefined && param.initializer !== null) {
- arg = this.visitExpression(param.initializer, calleeContext);
- }
- newScope.set(param.node, arg);
- });
- return ret.expression !== undefined
- ? this.visitExpression(ret.expression, calleeContext)
- : undefined;
- }
- visitConditionalExpression(node, context) {
- const condition = this.visitExpression(node.condition, context);
- if (condition instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, condition);
- }
- if (condition) {
- return this.visitExpression(node.whenTrue, context);
- }
- else {
- return this.visitExpression(node.whenFalse, context);
- }
- }
- visitPrefixUnaryExpression(node, context) {
- const operatorKind = node.operator;
- if (!UNARY_OPERATORS$2.has(operatorKind)) {
- return DynamicValue.fromUnsupportedSyntax(node);
- }
- const op = UNARY_OPERATORS$2.get(operatorKind);
- const value = this.visitExpression(node.operand, context);
- if (value instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, value);
- }
- else {
- return op(value);
- }
- }
- visitBinaryExpression(node, context) {
- const tokenKind = node.operatorToken.kind;
- if (!BINARY_OPERATORS$2.has(tokenKind)) {
- return DynamicValue.fromUnsupportedSyntax(node);
- }
- const opRecord = BINARY_OPERATORS$2.get(tokenKind);
- let lhs, rhs;
- if (opRecord.literal) {
- lhs = literal(this.visitExpression(node.left, context), (value) => DynamicValue.fromInvalidExpressionType(node.left, value));
- rhs = literal(this.visitExpression(node.right, context), (value) => DynamicValue.fromInvalidExpressionType(node.right, value));
- }
- else {
- lhs = this.visitExpression(node.left, context);
- rhs = this.visitExpression(node.right, context);
- }
- if (lhs instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, lhs);
- }
- else if (rhs instanceof DynamicValue) {
- return DynamicValue.fromDynamicInput(node, rhs);
- }
- else {
- return opRecord.op(lhs, rhs);
- }
- }
- visitParenthesizedExpression(node, context) {
- return this.visitExpression(node.expression, context);
- }
- evaluateFunctionArguments(node, context) {
- const args = [];
- for (const arg of node.arguments) {
- if (ts.isSpreadElement(arg)) {
- args.push(...this.visitSpreadElement(arg, context));
- }
- else {
- args.push(this.visitExpression(arg, context));
- }
- }
- return args;
- }
- visitSpreadElement(node, context) {
- const spread = this.visitExpression(node.expression, context);
- if (spread instanceof DynamicValue) {
- return [DynamicValue.fromDynamicInput(node, spread)];
- }
- else if (!Array.isArray(spread)) {
- return [DynamicValue.fromInvalidExpressionType(node, spread)];
- }
- else {
- return spread;
- }
- }
- visitBindingElement(node, context) {
- const path = [];
- let closestDeclaration = node;
- while (ts.isBindingElement(closestDeclaration) ||
- ts.isArrayBindingPattern(closestDeclaration) ||
- ts.isObjectBindingPattern(closestDeclaration)) {
- if (ts.isBindingElement(closestDeclaration)) {
- path.unshift(closestDeclaration);
- }
- closestDeclaration = closestDeclaration.parent;
- }
- if (!ts.isVariableDeclaration(closestDeclaration) ||
- closestDeclaration.initializer === undefined) {
- return DynamicValue.fromUnknown(node);
- }
- let value = this.visit(closestDeclaration.initializer, context);
- for (const element of path) {
- let key;
- if (ts.isArrayBindingPattern(element.parent)) {
- key = element.parent.elements.indexOf(element);
- }
- else {
- const name = element.propertyName || element.name;
- if (ts.isIdentifier(name)) {
- key = name.text;
- }
- else {
- return DynamicValue.fromUnknown(element);
- }
- }
- value = this.accessHelper(element, value, key, context);
- if (value instanceof DynamicValue) {
- return value;
- }
- }
- return value;
- }
- stringNameFromPropertyName(node, context) {
- if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
- return node.text;
- }
- else if (ts.isComputedPropertyName(node)) {
- const literal = this.visitExpression(node.expression, context);
- return typeof literal === 'string' ? literal : undefined;
- }
- else {
- return undefined;
- }
- }
- getReference(node, context) {
- return new Reference(node, owningModule(context));
- }
- visitType(node, context) {
- if (ts.isLiteralTypeNode(node)) {
- return this.visitExpression(node.literal, context);
- }
- else if (ts.isTupleTypeNode(node)) {
- return this.visitTupleType(node, context);
- }
- else if (ts.isNamedTupleMember(node)) {
- return this.visitType(node.type, context);
- }
- else if (ts.isTypeOperatorNode(node) && node.operator === ts.SyntaxKind.ReadonlyKeyword) {
- return this.visitType(node.type, context);
- }
- else if (ts.isTypeQueryNode(node)) {
- return this.visitTypeQuery(node, context);
- }
- return DynamicValue.fromDynamicType(node);
- }
- visitTupleType(node, context) {
- const res = [];
- for (const elem of node.elements) {
- res.push(this.visitType(elem, context));
- }
- return res;
- }
- visitTypeQuery(node, context) {
- if (!ts.isIdentifier(node.exprName)) {
- return DynamicValue.fromUnknown(node);
- }
- const decl = this.host.getDeclarationOfIdentifier(node.exprName);
- if (decl === null) {
- return DynamicValue.fromUnknownIdentifier(node.exprName);
- }
- const declContext = { ...context, ...joinModuleContext(context, node, decl) };
- return this.visitDeclaration(decl.node, declContext);
- }
- }
- function isFunctionOrMethodReference(ref) {
- return (ts.isFunctionDeclaration(ref.node) ||
- ts.isMethodDeclaration(ref.node) ||
- ts.isFunctionExpression(ref.node));
- }
- function literal(value, reject) {
- if (value instanceof EnumValue) {
- value = value.resolved;
- }
- if (value instanceof DynamicValue ||
- value === null ||
- value === undefined ||
- typeof value === 'string' ||
- typeof value === 'number' ||
- typeof value === 'boolean') {
- return value;
- }
- return reject(value);
- }
- function isVariableDeclarationDeclared(node) {
- if (node.parent === undefined || !ts.isVariableDeclarationList(node.parent)) {
- return false;
- }
- const declList = node.parent;
- if (declList.parent === undefined || !ts.isVariableStatement(declList.parent)) {
- return false;
- }
- const varStmt = declList.parent;
- const modifiers = ts.getModifiers(varStmt);
- return (modifiers !== undefined && modifiers.some((mod) => mod.kind === ts.SyntaxKind.DeclareKeyword));
- }
- const EMPTY = {};
- function joinModuleContext(existing, node, decl) {
- if (typeof decl.viaModule === 'string' && decl.viaModule !== existing.absoluteModuleName) {
- return {
- absoluteModuleName: decl.viaModule,
- resolutionContext: node.getSourceFile().fileName,
- };
- }
- else {
- return EMPTY;
- }
- }
- function owningModule(context, override = null) {
- let specifier = context.absoluteModuleName;
- if (override !== null) {
- specifier = override.specifier;
- }
- if (specifier !== null) {
- return {
- specifier,
- resolutionContext: context.resolutionContext,
- };
- }
- else {
- return null;
- }
- }
- /**
- * Specifies the compilation mode that is used for the compilation.
- */
- exports.CompilationMode = void 0;
- (function (CompilationMode) {
- /**
- * Generates fully AOT compiled code using Ivy instructions.
- */
- CompilationMode[CompilationMode["FULL"] = 0] = "FULL";
- /**
- * Generates code using a stable, but intermediate format suitable to be published to NPM.
- */
- CompilationMode[CompilationMode["PARTIAL"] = 1] = "PARTIAL";
- /**
- * Generates code based on each individual source file without using its
- * dependencies (suitable for local dev edit/refresh workflow).
- */
- CompilationMode[CompilationMode["LOCAL"] = 2] = "LOCAL";
- })(exports.CompilationMode || (exports.CompilationMode = {}));
- exports.HandlerPrecedence = void 0;
- (function (HandlerPrecedence) {
- /**
- * Handler with PRIMARY precedence cannot overlap - there can only be one on a given class.
- *
- * If more than one PRIMARY handler matches a class, an error is produced.
- */
- HandlerPrecedence[HandlerPrecedence["PRIMARY"] = 0] = "PRIMARY";
- /**
- * Handlers with SHARED precedence can match any class, possibly in addition to a single PRIMARY
- * handler.
- *
- * It is not an error for a class to have any number of SHARED handlers.
- */
- HandlerPrecedence[HandlerPrecedence["SHARED"] = 1] = "SHARED";
- /**
- * Handlers with WEAK precedence that match a class are ignored if any handlers with stronger
- * precedence match a class.
- */
- HandlerPrecedence[HandlerPrecedence["WEAK"] = 2] = "WEAK";
- })(exports.HandlerPrecedence || (exports.HandlerPrecedence = {}));
- /**
- * A phase of compilation for which time is tracked in a distinct bucket.
- */
- exports.PerfPhase = void 0;
- (function (PerfPhase) {
- /**
- * The "default" phase which tracks time not spent in any other phase.
- */
- PerfPhase[PerfPhase["Unaccounted"] = 0] = "Unaccounted";
- /**
- * Time spent setting up the compiler, before a TypeScript program is created.
- *
- * This includes operations like configuring the `ts.CompilerHost` and any wrappers.
- */
- PerfPhase[PerfPhase["Setup"] = 1] = "Setup";
- /**
- * Time spent in `ts.createProgram`, including reading and parsing `ts.SourceFile`s in the
- * `ts.CompilerHost`.
- *
- * This might be an incremental program creation operation.
- */
- PerfPhase[PerfPhase["TypeScriptProgramCreate"] = 2] = "TypeScriptProgramCreate";
- /**
- * Time spent reconciling the contents of an old `ts.Program` with the new incremental one.
- *
- * Only present in incremental compilations.
- */
- PerfPhase[PerfPhase["Reconciliation"] = 3] = "Reconciliation";
- /**
- * Time spent updating an `NgCompiler` instance with a resource-only change.
- *
- * Only present in incremental compilations where the change was resource-only.
- */
- PerfPhase[PerfPhase["ResourceUpdate"] = 4] = "ResourceUpdate";
- /**
- * Time spent calculating the plain TypeScript diagnostics (structural and semantic).
- */
- PerfPhase[PerfPhase["TypeScriptDiagnostics"] = 5] = "TypeScriptDiagnostics";
- /**
- * Time spent in Angular analysis of individual classes in the program.
- */
- PerfPhase[PerfPhase["Analysis"] = 6] = "Analysis";
- /**
- * Time spent in Angular global analysis (synthesis of analysis information into a complete
- * understanding of the program).
- */
- PerfPhase[PerfPhase["Resolve"] = 7] = "Resolve";
- /**
- * Time spent building the import graph of the program in order to perform cycle detection.
- */
- PerfPhase[PerfPhase["CycleDetection"] = 8] = "CycleDetection";
- /**
- * Time spent generating the text of Type Check Blocks in order to perform template type checking.
- */
- PerfPhase[PerfPhase["TcbGeneration"] = 9] = "TcbGeneration";
- /**
- * Time spent updating the `ts.Program` with new Type Check Block code.
- */
- PerfPhase[PerfPhase["TcbUpdateProgram"] = 10] = "TcbUpdateProgram";
- /**
- * Time spent by TypeScript performing its emit operations, including downleveling and writing
- * output files.
- */
- PerfPhase[PerfPhase["TypeScriptEmit"] = 11] = "TypeScriptEmit";
- /**
- * Time spent by Angular performing code transformations of ASTs as they're about to be emitted.
- *
- * This includes the actual code generation step for templates, and occurs during the emit phase
- * (but is tracked separately from `TypeScriptEmit` time).
- */
- PerfPhase[PerfPhase["Compile"] = 12] = "Compile";
- /**
- * Time spent performing a `TemplateTypeChecker` autocompletion operation.
- */
- PerfPhase[PerfPhase["TtcAutocompletion"] = 13] = "TtcAutocompletion";
- /**
- * Time spent computing template type-checking diagnostics.
- */
- PerfPhase[PerfPhase["TtcDiagnostics"] = 14] = "TtcDiagnostics";
- /**
- * Time spent getting a `Symbol` from the `TemplateTypeChecker`.
- */
- PerfPhase[PerfPhase["TtcSymbol"] = 15] = "TtcSymbol";
- /**
- * Time spent by the Angular Language Service calculating a "get references" or a renaming
- * operation.
- */
- PerfPhase[PerfPhase["LsReferencesAndRenames"] = 16] = "LsReferencesAndRenames";
- /**
- * Time spent by the Angular Language Service calculating a "quick info" operation.
- */
- PerfPhase[PerfPhase["LsQuickInfo"] = 17] = "LsQuickInfo";
- /**
- * Time spent by the Angular Language Service calculating a "get type definition" or "get
- * definition" operation.
- */
- PerfPhase[PerfPhase["LsDefinition"] = 18] = "LsDefinition";
- /**
- * Time spent by the Angular Language Service calculating a "get completions" (AKA autocomplete)
- * operation.
- */
- PerfPhase[PerfPhase["LsCompletions"] = 19] = "LsCompletions";
- /**
- * Time spent by the Angular Language Service calculating a "view template typecheck block"
- * operation.
- */
- PerfPhase[PerfPhase["LsTcb"] = 20] = "LsTcb";
- /**
- * Time spent by the Angular Language Service calculating diagnostics.
- */
- PerfPhase[PerfPhase["LsDiagnostics"] = 21] = "LsDiagnostics";
- /**
- * Time spent by the Angular Language Service calculating a "get component locations for template"
- * operation.
- */
- PerfPhase[PerfPhase["LsComponentLocations"] = 22] = "LsComponentLocations";
- /**
- * Time spent by the Angular Language Service calculating signature help.
- */
- PerfPhase[PerfPhase["LsSignatureHelp"] = 23] = "LsSignatureHelp";
- /**
- * Time spent by the Angular Language Service calculating outlining spans.
- */
- PerfPhase[PerfPhase["OutliningSpans"] = 24] = "OutliningSpans";
- /**
- * Tracks the number of `PerfPhase`s, and must appear at the end of the list.
- */
- PerfPhase[PerfPhase["LAST"] = 25] = "LAST";
- /**
- * Time spent by the Angular Language Service calculating code fixes.
- */
- PerfPhase[PerfPhase["LsCodeFixes"] = 26] = "LsCodeFixes";
- /**
- * Time spent by the Angular Language Service to fix all detected same type errors.
- */
- PerfPhase[PerfPhase["LsCodeFixesAll"] = 27] = "LsCodeFixesAll";
- /**
- * Time spent computing possible Angular refactorings.
- */
- PerfPhase[PerfPhase["LSComputeApplicableRefactorings"] = 28] = "LSComputeApplicableRefactorings";
- /**
- * Time spent computing changes for applying a given refactoring.
- */
- PerfPhase[PerfPhase["LSApplyRefactoring"] = 29] = "LSApplyRefactoring";
- })(exports.PerfPhase || (exports.PerfPhase = {}));
- /**
- * Represents some occurrence during compilation, and is tracked with a counter.
- */
- exports.PerfEvent = void 0;
- (function (PerfEvent) {
- /**
- * Counts the number of `.d.ts` files in the program.
- */
- PerfEvent[PerfEvent["InputDtsFile"] = 0] = "InputDtsFile";
- /**
- * Counts the number of non-`.d.ts` files in the program.
- */
- PerfEvent[PerfEvent["InputTsFile"] = 1] = "InputTsFile";
- /**
- * An `@Component` class was analyzed.
- */
- PerfEvent[PerfEvent["AnalyzeComponent"] = 2] = "AnalyzeComponent";
- /**
- * An `@Directive` class was analyzed.
- */
- PerfEvent[PerfEvent["AnalyzeDirective"] = 3] = "AnalyzeDirective";
- /**
- * An `@Injectable` class was analyzed.
- */
- PerfEvent[PerfEvent["AnalyzeInjectable"] = 4] = "AnalyzeInjectable";
- /**
- * An `@NgModule` class was analyzed.
- */
- PerfEvent[PerfEvent["AnalyzeNgModule"] = 5] = "AnalyzeNgModule";
- /**
- * An `@Pipe` class was analyzed.
- */
- PerfEvent[PerfEvent["AnalyzePipe"] = 6] = "AnalyzePipe";
- /**
- * A trait was analyzed.
- *
- * In theory, this should be the sum of the `Analyze` counters for each decorator type.
- */
- PerfEvent[PerfEvent["TraitAnalyze"] = 7] = "TraitAnalyze";
- /**
- * A trait had a prior analysis available from an incremental program, and did not need to be
- * re-analyzed.
- */
- PerfEvent[PerfEvent["TraitReuseAnalysis"] = 8] = "TraitReuseAnalysis";
- /**
- * A `ts.SourceFile` directly changed between the prior program and a new incremental compilation.
- */
- PerfEvent[PerfEvent["SourceFilePhysicalChange"] = 9] = "SourceFilePhysicalChange";
- /**
- * A `ts.SourceFile` did not physically changed, but according to the file dependency graph, has
- * logically changed between the prior program and a new incremental compilation.
- */
- PerfEvent[PerfEvent["SourceFileLogicalChange"] = 10] = "SourceFileLogicalChange";
- /**
- * A `ts.SourceFile` has not logically changed and all of its analysis results were thus available
- * for reuse.
- */
- PerfEvent[PerfEvent["SourceFileReuseAnalysis"] = 11] = "SourceFileReuseAnalysis";
- /**
- * A Type Check Block (TCB) was generated.
- */
- PerfEvent[PerfEvent["GenerateTcb"] = 12] = "GenerateTcb";
- /**
- * A Type Check Block (TCB) could not be generated because inlining was disabled, and the block
- * would've required inlining.
- */
- PerfEvent[PerfEvent["SkipGenerateTcbNoInline"] = 13] = "SkipGenerateTcbNoInline";
- /**
- * A `.ngtypecheck.ts` file could be reused from the previous program and did not need to be
- * regenerated.
- */
- PerfEvent[PerfEvent["ReuseTypeCheckFile"] = 14] = "ReuseTypeCheckFile";
- /**
- * The template type-checking program required changes and had to be updated in an incremental
- * step.
- */
- PerfEvent[PerfEvent["UpdateTypeCheckProgram"] = 15] = "UpdateTypeCheckProgram";
- /**
- * The compiler was able to prove that a `ts.SourceFile` did not need to be re-emitted.
- */
- PerfEvent[PerfEvent["EmitSkipSourceFile"] = 16] = "EmitSkipSourceFile";
- /**
- * A `ts.SourceFile` was emitted.
- */
- PerfEvent[PerfEvent["EmitSourceFile"] = 17] = "EmitSourceFile";
- /**
- * Tracks the number of `PrefEvent`s, and must appear at the end of the list.
- */
- PerfEvent[PerfEvent["LAST"] = 18] = "LAST";
- })(exports.PerfEvent || (exports.PerfEvent = {}));
- /**
- * Represents a checkpoint during compilation at which the memory usage of the compiler should be
- * recorded.
- */
- exports.PerfCheckpoint = void 0;
- (function (PerfCheckpoint) {
- /**
- * The point at which the `PerfRecorder` was created, and ideally tracks memory used before any
- * compilation structures are created.
- */
- PerfCheckpoint[PerfCheckpoint["Initial"] = 0] = "Initial";
- /**
- * The point just after the `ts.Program` has been created.
- */
- PerfCheckpoint[PerfCheckpoint["TypeScriptProgramCreate"] = 1] = "TypeScriptProgramCreate";
- /**
- * The point just before Angular analysis starts.
- *
- * In the main usage pattern for the compiler, TypeScript diagnostics have been calculated at this
- * point, so the `ts.TypeChecker` has fully ingested the current program, all `ts.Type` structures
- * and `ts.Symbol`s have been created.
- */
- PerfCheckpoint[PerfCheckpoint["PreAnalysis"] = 2] = "PreAnalysis";
- /**
- * The point just after Angular analysis completes.
- */
- PerfCheckpoint[PerfCheckpoint["Analysis"] = 3] = "Analysis";
- /**
- * The point just after Angular resolution is complete.
- */
- PerfCheckpoint[PerfCheckpoint["Resolve"] = 4] = "Resolve";
- /**
- * The point just after Type Check Blocks (TCBs) have been generated.
- */
- PerfCheckpoint[PerfCheckpoint["TtcGeneration"] = 5] = "TtcGeneration";
- /**
- * The point just after the template type-checking program has been updated with any new TCBs.
- */
- PerfCheckpoint[PerfCheckpoint["TtcUpdateProgram"] = 6] = "TtcUpdateProgram";
- /**
- * The point just before emit begins.
- *
- * In the main usage pattern for the compiler, all template type-checking diagnostics have been
- * requested at this point.
- */
- PerfCheckpoint[PerfCheckpoint["PreEmit"] = 7] = "PreEmit";
- /**
- * The point just after the program has been fully emitted.
- */
- PerfCheckpoint[PerfCheckpoint["Emit"] = 8] = "Emit";
- /**
- * Tracks the number of `PerfCheckpoint`s, and must appear at the end of the list.
- */
- PerfCheckpoint[PerfCheckpoint["LAST"] = 9] = "LAST";
- })(exports.PerfCheckpoint || (exports.PerfCheckpoint = {}));
- exports.TraitState = void 0;
- (function (TraitState) {
- /**
- * Pending traits are freshly created and have never been analyzed.
- */
- TraitState[TraitState["Pending"] = 0] = "Pending";
- /**
- * Analyzed traits have successfully been analyzed, but are pending resolution.
- */
- TraitState[TraitState["Analyzed"] = 1] = "Analyzed";
- /**
- * Resolved traits have successfully been analyzed and resolved and are ready for compilation.
- */
- TraitState[TraitState["Resolved"] = 2] = "Resolved";
- /**
- * Skipped traits are no longer considered for compilation.
- */
- TraitState[TraitState["Skipped"] = 3] = "Skipped";
- })(exports.TraitState || (exports.TraitState = {}));
- /**
- * The value side of `Trait` exposes a helper to create a `Trait` in a pending state (by delegating
- * to `TraitImpl`).
- */
- const Trait = {
- pending: (handler, detected) => TraitImpl.pending(handler, detected),
- };
- /**
- * An implementation of the `Trait` type which transitions safely between the various
- * `TraitState`s.
- */
- class TraitImpl {
- state = exports.TraitState.Pending;
- handler;
- detected;
- analysis = null;
- symbol = null;
- resolution = null;
- analysisDiagnostics = null;
- resolveDiagnostics = null;
- typeCheckDiagnostics = null;
- constructor(handler, detected) {
- this.handler = handler;
- this.detected = detected;
- }
- toAnalyzed(analysis, diagnostics, symbol) {
- // Only pending traits can be analyzed.
- this.assertTransitionLegal(exports.TraitState.Pending, exports.TraitState.Analyzed);
- this.analysis = analysis;
- this.analysisDiagnostics = diagnostics;
- this.symbol = symbol;
- this.state = exports.TraitState.Analyzed;
- return this;
- }
- toResolved(resolution, diagnostics) {
- // Only analyzed traits can be resolved.
- this.assertTransitionLegal(exports.TraitState.Analyzed, exports.TraitState.Resolved);
- if (this.analysis === null) {
- throw new Error(`Cannot transition an Analyzed trait with a null analysis to Resolved`);
- }
- this.resolution = resolution;
- this.state = exports.TraitState.Resolved;
- this.resolveDiagnostics = diagnostics;
- this.typeCheckDiagnostics = null;
- return this;
- }
- toSkipped() {
- // Only pending traits can be skipped.
- this.assertTransitionLegal(exports.TraitState.Pending, exports.TraitState.Skipped);
- this.state = exports.TraitState.Skipped;
- return this;
- }
- /**
- * Verifies that the trait is currently in one of the `allowedState`s.
- *
- * If correctly used, the `Trait` type and transition methods prevent illegal transitions from
- * occurring. However, if a reference to the `TraitImpl` instance typed with the previous
- * interface is retained after calling one of its transition methods, it will allow for illegal
- * transitions to take place. Hence, this assertion provides a little extra runtime protection.
- */
- assertTransitionLegal(allowedState, transitionTo) {
- if (!(this.state === allowedState)) {
- throw new Error(`Assertion failure: cannot transition from ${exports.TraitState[this.state]} to ${exports.TraitState[transitionTo]}.`);
- }
- }
- /**
- * Construct a new `TraitImpl` in the pending state.
- */
- static pending(handler, detected) {
- return new TraitImpl(handler, detected);
- }
- }
- /**
- * The current context of a translator visitor as it traverses the AST tree.
- *
- * It tracks whether we are in the process of outputting a statement or an expression.
- */
- let Context$1 = class Context {
- isStatement;
- constructor(isStatement) {
- this.isStatement = isStatement;
- }
- get withExpressionMode() {
- return this.isStatement ? new Context(false) : this;
- }
- get withStatementMode() {
- return !this.isStatement ? new Context(true) : this;
- }
- };
- /**
- * Generates a helper for `ImportManagerConfig` to generate unique identifiers
- * for a given source file.
- */
- function createGenerateUniqueIdentifierHelper() {
- const generatedIdentifiers = new Set();
- const isGeneratedIdentifier = (sf, identifierName) => generatedIdentifiers.has(`${sf.fileName}@@${identifierName}`);
- const markIdentifierAsGenerated = (sf, identifierName) => generatedIdentifiers.add(`${sf.fileName}@@${identifierName}`);
- return (sourceFile, symbolName) => {
- const sf = sourceFile;
- if (sf.identifiers === undefined) {
- throw new Error('Source file unexpectedly lacks map of parsed `identifiers`.');
- }
- const isUniqueIdentifier = (name) => !sf.identifiers.has(name) && !isGeneratedIdentifier(sf, name);
- if (isUniqueIdentifier(symbolName)) {
- markIdentifierAsGenerated(sf, symbolName);
- return null;
- }
- let name = null;
- let counter = 1;
- do {
- name = `${symbolName}_${counter++}`;
- } while (!isUniqueIdentifier(name));
- markIdentifierAsGenerated(sf, name);
- return ts.factory.createUniqueName(name, ts.GeneratedIdentifierFlags.Optimistic);
- };
- }
- /**
- * Creates a TypeScript transform for the given import manager.
- *
- * - The transform updates existing imports with new symbols to be added.
- * - The transform adds new necessary imports.
- * - The transform inserts additional optional statements after imports.
- * - The transform deletes any nodes that are marked for deletion by the manager.
- */
- function createTsTransformForImportManager(manager, extraStatementsForFiles) {
- return (ctx) => {
- const { affectedFiles, newImports, updatedImports, reusedOriginalAliasDeclarations, deletedImports, } = manager.finalize();
- // If we re-used existing source file alias declarations, mark those as referenced so TypeScript
- // doesn't drop these thinking they are unused.
- if (reusedOriginalAliasDeclarations.size > 0) {
- const referencedAliasDeclarations = loadIsReferencedAliasDeclarationPatch(ctx);
- if (referencedAliasDeclarations !== null) {
- reusedOriginalAliasDeclarations.forEach((aliasDecl) => referencedAliasDeclarations.add(aliasDecl));
- }
- }
- // Update the set of affected files to include files that need extra statements to be inserted.
- if (extraStatementsForFiles !== undefined) {
- for (const [fileName, statements] of extraStatementsForFiles.entries()) {
- if (statements.length > 0) {
- affectedFiles.add(fileName);
- }
- }
- }
- const visitStatement = (node) => {
- if (!ts.isImportDeclaration(node)) {
- return node;
- }
- if (deletedImports.has(node)) {
- return undefined;
- }
- if (node.importClause === undefined || !ts.isImportClause(node.importClause)) {
- return node;
- }
- const clause = node.importClause;
- if (clause.namedBindings === undefined ||
- !ts.isNamedImports(clause.namedBindings) ||
- !updatedImports.has(clause.namedBindings)) {
- return node;
- }
- const newClause = ctx.factory.updateImportClause(clause, clause.isTypeOnly, clause.name, updatedImports.get(clause.namedBindings));
- const newImport = ctx.factory.updateImportDeclaration(node, node.modifiers, newClause, node.moduleSpecifier, node.attributes);
- // This tricks TypeScript into thinking that the `importClause` is still optimizable.
- // By default, TS assumes, no specifiers are elide-able if the clause of the "original
- // node" has changed. google3:
- // typescript/unstable/src/compiler/transformers/ts.ts;l=456;rcl=611254538.
- ts.setOriginalNode(newImport, {
- importClause: newClause,
- kind: newImport.kind,
- });
- return newImport;
- };
- return (sourceFile) => {
- if (!affectedFiles.has(sourceFile.fileName)) {
- return sourceFile;
- }
- sourceFile = ts.visitEachChild(sourceFile, visitStatement, ctx);
- // Filter out the existing imports and the source file body.
- // All new statements will be inserted between them.
- const extraStatements = extraStatementsForFiles?.get(sourceFile.fileName) ?? [];
- const existingImports = [];
- const body = [];
- for (const statement of sourceFile.statements) {
- if (isImportStatement(statement)) {
- existingImports.push(statement);
- }
- else {
- body.push(statement);
- }
- }
- return ctx.factory.updateSourceFile(sourceFile, [
- ...existingImports,
- ...(newImports.get(sourceFile.fileName) ?? []),
- ...extraStatements,
- ...body,
- ], sourceFile.isDeclarationFile, sourceFile.referencedFiles, sourceFile.typeReferenceDirectives, sourceFile.hasNoDefaultLib, sourceFile.libReferenceDirectives);
- };
- };
- }
- /** Whether the given statement is an import statement. */
- function isImportStatement(stmt) {
- return (ts.isImportDeclaration(stmt) || ts.isImportEqualsDeclaration(stmt) || ts.isNamespaceImport(stmt));
- }
- /** Attempts to efficiently re-use previous generated import requests. */
- function attemptToReuseGeneratedImports(tracker, request) {
- const requestHash = hashImportRequest(request);
- // In case the given import has been already generated previously, we just return
- // the previous generated identifier in order to avoid duplicate generated imports.
- const existingExactImport = tracker.directReuseCache.get(requestHash);
- if (existingExactImport !== undefined) {
- return existingExactImport;
- }
- const potentialNamespaceImport = tracker.namespaceImportReuseCache.get(request.exportModuleSpecifier);
- if (potentialNamespaceImport === undefined) {
- return null;
- }
- if (request.exportSymbolName === null) {
- return potentialNamespaceImport;
- }
- return [potentialNamespaceImport, ts.factory.createIdentifier(request.exportSymbolName)];
- }
- /** Captures the given import request and its generated reference node/path for future re-use. */
- function captureGeneratedImport(request, tracker, referenceNode) {
- tracker.directReuseCache.set(hashImportRequest(request), referenceNode);
- if (request.exportSymbolName === null && !Array.isArray(referenceNode)) {
- tracker.namespaceImportReuseCache.set(request.exportModuleSpecifier, referenceNode);
- }
- }
- /** Generates a unique hash for the given import request. */
- function hashImportRequest(req) {
- return `${req.requestedFile.fileName}:${req.exportModuleSpecifier}:${req.exportSymbolName}${req.unsafeAliasOverride ? ':' + req.unsafeAliasOverride : ''}`;
- }
- /** Attempts to re-use original source file imports for the given request. */
- function attemptToReuseExistingSourceFileImports(tracker, sourceFile, request) {
- // Walk through all source-file top-level statements and search for import declarations
- // that already match the specified "moduleName" and can be updated to import the
- // given symbol. If no matching import can be found, the last import in the source-file
- // will be used as starting point for a new import that will be generated.
- let candidateImportToBeUpdated = null;
- for (let i = sourceFile.statements.length - 1; i >= 0; i--) {
- const statement = sourceFile.statements[i];
- if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
- continue;
- }
- // Side-effect imports are ignored, or type-only imports.
- // TODO: Consider re-using type-only imports efficiently.
- if (!statement.importClause || statement.importClause.isTypeOnly) {
- continue;
- }
- const moduleSpecifier = statement.moduleSpecifier.text;
- // If the import does not match the module name, or requested target file, continue.
- // Note: In the future, we may consider performing better analysis here. E.g. resolve paths,
- // or try to detect re-usable symbols via type-checking.
- if (moduleSpecifier !== request.exportModuleSpecifier) {
- continue;
- }
- if (statement.importClause.namedBindings) {
- const namedBindings = statement.importClause.namedBindings;
- // A namespace import can be reused.
- if (ts.isNamespaceImport(namedBindings)) {
- tracker.reusedAliasDeclarations.add(namedBindings);
- if (request.exportSymbolName === null) {
- return namedBindings.name;
- }
- return [namedBindings.name, ts.factory.createIdentifier(request.exportSymbolName)];
- }
- // Named imports can be re-used if a specific symbol is requested.
- if (ts.isNamedImports(namedBindings) && request.exportSymbolName !== null) {
- const existingElement = namedBindings.elements.find((e) => {
- // TODO: Consider re-using type-only imports efficiently.
- let nameMatches;
- if (request.unsafeAliasOverride) {
- // If a specific alias is passed, both the original name and alias have to match.
- nameMatches =
- e.propertyName?.text === request.exportSymbolName &&
- e.name.text === request.unsafeAliasOverride;
- }
- else {
- nameMatches = e.propertyName
- ? e.propertyName.text === request.exportSymbolName
- : e.name.text === request.exportSymbolName;
- }
- return !e.isTypeOnly && nameMatches;
- });
- if (existingElement !== undefined) {
- tracker.reusedAliasDeclarations.add(existingElement);
- return existingElement.name;
- }
- // In case the symbol could not be found in an existing import, we
- // keep track of the import declaration as it can be updated to include
- // the specified symbol name without having to create a new import.
- candidateImportToBeUpdated = statement;
- }
- }
- }
- if (candidateImportToBeUpdated === null || request.exportSymbolName === null) {
- return null;
- }
- // We have a candidate import. Update it to import what we need.
- if (!tracker.updatedImports.has(candidateImportToBeUpdated)) {
- tracker.updatedImports.set(candidateImportToBeUpdated, []);
- }
- const symbolsToBeImported = tracker.updatedImports.get(candidateImportToBeUpdated);
- const propertyName = ts.factory.createIdentifier(request.exportSymbolName);
- const fileUniqueAlias = request.unsafeAliasOverride
- ? ts.factory.createIdentifier(request.unsafeAliasOverride)
- : tracker.generateUniqueIdentifier(sourceFile, request.exportSymbolName);
- // Since it can happen that multiple classes need to be imported within the
- // specified source file and we want to add the identifiers to the existing
- // import declaration, we need to keep track of the updated import declarations.
- // We can't directly update the import declaration for each identifier as this
- // would not be reflected in the AST— or would throw of update recording offsets.
- symbolsToBeImported.push({
- propertyName,
- fileUniqueAlias,
- });
- return fileUniqueAlias ?? propertyName;
- }
- /**
- * Preset configuration for forcing namespace imports.
- *
- * This preset is commonly used to avoid test differences to previous
- * versions of the `ImportManager`.
- */
- const presetImportManagerForceNamespaceImports = {
- // Forcing namespace imports also means no-reuse.
- // Re-using would otherwise become more complicated and we don't
- // expect re-usable namespace imports.
- disableOriginalSourceFileReuse: true,
- forceGenerateNamespacesForNewImports: true,
- };
- /**
- * Import manager that can be used to conveniently and efficiently generate
- * imports It efficiently re-uses existing source file imports, or previous
- * generated imports.
- *
- * These capabilities are important for efficient TypeScript transforms that
- * minimize structural changes to the dependency graph of source files, enabling
- * as much incremental re-use as possible.
- *
- * Those imports may be inserted via a TypeScript transform, or via manual string
- * manipulation using e.g. `magic-string`.
- */
- class ImportManager {
- /** List of new imports that will be inserted into given source files. */
- newImports = new Map();
- /**
- * Keeps track of imports marked for removal. The root-level key is the file from which the
- * import should be removed, the inner map key is the name of the module from which the symbol
- * is being imported. The value of the inner map is a set of symbol names that should be removed.
- * Note! the inner map tracks the original names of the imported symbols, not their local aliases.
- */
- removedImports = new Map();
- nextUniqueIndex = 0;
- config;
- reuseSourceFileImportsTracker;
- reuseGeneratedImportsTracker = {
- directReuseCache: new Map(),
- namespaceImportReuseCache: new Map(),
- };
- constructor(config = {}) {
- this.config = {
- shouldUseSingleQuotes: config.shouldUseSingleQuotes ?? (() => false),
- rewriter: config.rewriter ?? null,
- disableOriginalSourceFileReuse: config.disableOriginalSourceFileReuse ?? false,
- forceGenerateNamespacesForNewImports: config.forceGenerateNamespacesForNewImports ?? false,
- namespaceImportPrefix: config.namespaceImportPrefix ?? 'i',
- generateUniqueIdentifier: config.generateUniqueIdentifier ?? createGenerateUniqueIdentifierHelper(),
- };
- this.reuseSourceFileImportsTracker = {
- generateUniqueIdentifier: this.config.generateUniqueIdentifier,
- reusedAliasDeclarations: new Set(),
- updatedImports: new Map(),
- };
- }
- /** Adds a side-effect import for the given module. */
- addSideEffectImport(requestedFile, moduleSpecifier) {
- if (this.config.rewriter !== null) {
- moduleSpecifier = this.config.rewriter.rewriteSpecifier(moduleSpecifier, requestedFile.fileName);
- }
- this._getNewImportsTrackerForFile(requestedFile).sideEffectImports.add(moduleSpecifier);
- }
- addImport(request) {
- if (this.config.rewriter !== null) {
- if (request.exportSymbolName !== null) {
- request.exportSymbolName = this.config.rewriter.rewriteSymbol(request.exportSymbolName, request.exportModuleSpecifier);
- }
- request.exportModuleSpecifier = this.config.rewriter.rewriteSpecifier(request.exportModuleSpecifier, request.requestedFile.fileName);
- }
- // Remove the newly-added import from the set of removed imports.
- if (request.exportSymbolName !== null && !request.asTypeReference) {
- this.removedImports
- .get(request.requestedFile)
- ?.get(request.exportModuleSpecifier)
- ?.delete(request.exportSymbolName);
- }
- // Attempt to re-use previous identical import requests.
- const previousGeneratedImportRef = attemptToReuseGeneratedImports(this.reuseGeneratedImportsTracker, request);
- if (previousGeneratedImportRef !== null) {
- return createImportReference(!!request.asTypeReference, previousGeneratedImportRef);
- }
- // Generate a new one, and cache it.
- const resultImportRef = this._generateNewImport(request);
- captureGeneratedImport(request, this.reuseGeneratedImportsTracker, resultImportRef);
- return createImportReference(!!request.asTypeReference, resultImportRef);
- }
- /**
- * Marks all imported symbols with a specific name for removal.
- * Call `addImport` to undo this operation.
- * @param requestedFile File from which to remove the imports.
- * @param exportSymbolName Declared name of the symbol being removed.
- * @param moduleSpecifier Module from which the symbol is being imported.
- */
- removeImport(requestedFile, exportSymbolName, moduleSpecifier) {
- let moduleMap = this.removedImports.get(requestedFile);
- if (!moduleMap) {
- moduleMap = new Map();
- this.removedImports.set(requestedFile, moduleMap);
- }
- let removedSymbols = moduleMap.get(moduleSpecifier);
- if (!removedSymbols) {
- removedSymbols = new Set();
- moduleMap.set(moduleSpecifier, removedSymbols);
- }
- removedSymbols.add(exportSymbolName);
- }
- _generateNewImport(request) {
- const { requestedFile: sourceFile } = request;
- const disableOriginalSourceFileReuse = this.config.disableOriginalSourceFileReuse;
- const forceGenerateNamespacesForNewImports = this.config.forceGenerateNamespacesForNewImports;
- // If desired, attempt to re-use original source file imports as a base, or as much as possible.
- // This may involve updates to existing import named bindings.
- if (!disableOriginalSourceFileReuse) {
- const reuseResult = attemptToReuseExistingSourceFileImports(this.reuseSourceFileImportsTracker, sourceFile, request);
- if (reuseResult !== null) {
- return reuseResult;
- }
- }
- // A new import needs to be generated.
- // No candidate existing import was found.
- const { namedImports, namespaceImports } = this._getNewImportsTrackerForFile(sourceFile);
- // If a namespace import is requested, or the symbol should be forcibly
- // imported through namespace imports:
- if (request.exportSymbolName === null || forceGenerateNamespacesForNewImports) {
- let namespaceImportName = `${this.config.namespaceImportPrefix}${this.nextUniqueIndex++}`;
- if (this.config.rewriter) {
- namespaceImportName = this.config.rewriter.rewriteNamespaceImportIdentifier(namespaceImportName, request.exportModuleSpecifier);
- }
- const namespaceImport = ts.factory.createNamespaceImport(this.config.generateUniqueIdentifier(sourceFile, namespaceImportName) ??
- ts.factory.createIdentifier(namespaceImportName));
- namespaceImports.set(request.exportModuleSpecifier, namespaceImport);
- // Capture the generated namespace import alone, to allow re-use.
- captureGeneratedImport({ ...request, exportSymbolName: null }, this.reuseGeneratedImportsTracker, namespaceImport.name);
- if (request.exportSymbolName !== null) {
- return [namespaceImport.name, ts.factory.createIdentifier(request.exportSymbolName)];
- }
- return namespaceImport.name;
- }
- // Otherwise, an individual named import is requested.
- if (!namedImports.has(request.exportModuleSpecifier)) {
- namedImports.set(request.exportModuleSpecifier, []);
- }
- const exportSymbolName = ts.factory.createIdentifier(request.exportSymbolName);
- const fileUniqueName = request.unsafeAliasOverride
- ? null
- : this.config.generateUniqueIdentifier(sourceFile, request.exportSymbolName);
- let needsAlias;
- let specifierName;
- if (request.unsafeAliasOverride) {
- needsAlias = true;
- specifierName = ts.factory.createIdentifier(request.unsafeAliasOverride);
- }
- else if (fileUniqueName !== null) {
- needsAlias = true;
- specifierName = fileUniqueName;
- }
- else {
- needsAlias = false;
- specifierName = exportSymbolName;
- }
- namedImports
- .get(request.exportModuleSpecifier)
- .push(ts.factory.createImportSpecifier(false, needsAlias ? exportSymbolName : undefined, specifierName));
- return specifierName;
- }
- /**
- * Finalizes the import manager by computing all necessary import changes
- * and returning them.
- *
- * Changes are collected once at the end, after all imports are requested,
- * because this simplifies building up changes to existing imports that need
- * to be updated, and allows more trivial re-use of previous generated imports.
- */
- finalize() {
- const affectedFiles = new Set();
- const updatedImportsResult = new Map();
- const newImportsResult = new Map();
- const deletedImports = new Set();
- const importDeclarationsPerFile = new Map();
- const addNewImport = (fileName, importDecl) => {
- affectedFiles.add(fileName);
- if (newImportsResult.has(fileName)) {
- newImportsResult.get(fileName).push(importDecl);
- }
- else {
- newImportsResult.set(fileName, [importDecl]);
- }
- };
- // Collect original source file imports that need to be updated.
- this.reuseSourceFileImportsTracker.updatedImports.forEach((expressions, importDecl) => {
- const sourceFile = importDecl.getSourceFile();
- const namedBindings = importDecl.importClause.namedBindings;
- const moduleName = importDecl.moduleSpecifier.text;
- const newElements = namedBindings.elements
- .concat(expressions.map(({ propertyName, fileUniqueAlias }) => ts.factory.createImportSpecifier(false, fileUniqueAlias !== null ? propertyName : undefined, fileUniqueAlias ?? propertyName)))
- .filter((specifier) => this._canAddSpecifier(sourceFile, moduleName, specifier));
- affectedFiles.add(sourceFile.fileName);
- if (newElements.length === 0) {
- deletedImports.add(importDecl);
- }
- else {
- updatedImportsResult.set(namedBindings, ts.factory.updateNamedImports(namedBindings, newElements));
- }
- });
- this.removedImports.forEach((removeMap, sourceFile) => {
- if (removeMap.size === 0) {
- return;
- }
- let allImports = importDeclarationsPerFile.get(sourceFile);
- if (!allImports) {
- allImports = sourceFile.statements.filter(ts.isImportDeclaration);
- importDeclarationsPerFile.set(sourceFile, allImports);
- }
- for (const node of allImports) {
- if (!node.importClause?.namedBindings ||
- !ts.isNamedImports(node.importClause.namedBindings) ||
- this.reuseSourceFileImportsTracker.updatedImports.has(node) ||
- deletedImports.has(node)) {
- continue;
- }
- const namedBindings = node.importClause.namedBindings;
- const moduleName = node.moduleSpecifier.text;
- const newImports = namedBindings.elements.filter((specifier) => this._canAddSpecifier(sourceFile, moduleName, specifier));
- if (newImports.length === 0) {
- affectedFiles.add(sourceFile.fileName);
- deletedImports.add(node);
- }
- else if (newImports.length !== namedBindings.elements.length) {
- affectedFiles.add(sourceFile.fileName);
- updatedImportsResult.set(namedBindings, ts.factory.updateNamedImports(namedBindings, newImports));
- }
- }
- });
- // Collect all new imports to be added. Named imports, namespace imports or side-effects.
- this.newImports.forEach(({ namedImports, namespaceImports, sideEffectImports }, sourceFile) => {
- const useSingleQuotes = this.config.shouldUseSingleQuotes(sourceFile);
- const fileName = sourceFile.fileName;
- sideEffectImports.forEach((moduleName) => {
- addNewImport(fileName, ts.factory.createImportDeclaration(undefined, undefined, ts.factory.createStringLiteral(moduleName)));
- });
- namespaceImports.forEach((namespaceImport, moduleName) => {
- const newImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, namespaceImport), ts.factory.createStringLiteral(moduleName, useSingleQuotes));
- // IMPORTANT: Set the original TS node to the `ts.ImportDeclaration`. This allows
- // downstream transforms such as tsickle to properly process references to this import.
- //
- // This operation is load-bearing in g3 as some imported modules contain special metadata
- // generated by clutz, which tsickle uses to transform imports and references to those
- // imports. See: `google3: node_modules/tsickle/src/googmodule.ts;l=637-640;rcl=615418148`
- ts.setOriginalNode(namespaceImport.name, newImport);
- addNewImport(fileName, newImport);
- });
- namedImports.forEach((specifiers, moduleName) => {
- const filteredSpecifiers = specifiers.filter((specifier) => this._canAddSpecifier(sourceFile, moduleName, specifier));
- if (filteredSpecifiers.length > 0) {
- const newImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports(filteredSpecifiers)), ts.factory.createStringLiteral(moduleName, useSingleQuotes));
- addNewImport(fileName, newImport);
- }
- });
- });
- return {
- affectedFiles,
- newImports: newImportsResult,
- updatedImports: updatedImportsResult,
- reusedOriginalAliasDeclarations: this.reuseSourceFileImportsTracker.reusedAliasDeclarations,
- deletedImports,
- };
- }
- /**
- * Gets a TypeScript transform for the import manager.
- *
- * @param extraStatementsMap Additional set of statements to be inserted
- * for given source files after their imports. E.g. top-level constants.
- */
- toTsTransform(extraStatementsMap) {
- return createTsTransformForImportManager(this, extraStatementsMap);
- }
- /**
- * Transforms a single file as a shorthand, using {@link toTsTransform}.
- *
- * @param extraStatementsMap Additional set of statements to be inserted
- * for given source files after their imports. E.g. top-level constants.
- */
- transformTsFile(ctx, file, extraStatementsAfterImports) {
- const extraStatementsMap = extraStatementsAfterImports
- ? new Map([[file.fileName, extraStatementsAfterImports]])
- : undefined;
- return this.toTsTransform(extraStatementsMap)(ctx)(file);
- }
- _getNewImportsTrackerForFile(file) {
- if (!this.newImports.has(file)) {
- this.newImports.set(file, {
- namespaceImports: new Map(),
- namedImports: new Map(),
- sideEffectImports: new Set(),
- });
- }
- return this.newImports.get(file);
- }
- _canAddSpecifier(sourceFile, moduleSpecifier, specifier) {
- return !this.removedImports
- .get(sourceFile)
- ?.get(moduleSpecifier)
- ?.has((specifier.propertyName || specifier.name).text);
- }
- }
- /** Creates an import reference based on the given identifier, or nested access. */
- function createImportReference(asTypeReference, ref) {
- if (asTypeReference) {
- return Array.isArray(ref) ? ts.factory.createQualifiedName(ref[0], ref[1]) : ref;
- }
- else {
- return Array.isArray(ref) ? ts.factory.createPropertyAccessExpression(ref[0], ref[1]) : ref;
- }
- }
- const UNARY_OPERATORS$1 = new Map([
- [UnaryOperator.Minus, '-'],
- [UnaryOperator.Plus, '+'],
- ]);
- const BINARY_OPERATORS$1 = new Map([
- [BinaryOperator.And, '&&'],
- [BinaryOperator.Bigger, '>'],
- [BinaryOperator.BiggerEquals, '>='],
- [BinaryOperator.BitwiseAnd, '&'],
- [BinaryOperator.BitwiseOr, '|'],
- [BinaryOperator.Divide, '/'],
- [BinaryOperator.Equals, '=='],
- [BinaryOperator.Identical, '==='],
- [BinaryOperator.Lower, '<'],
- [BinaryOperator.LowerEquals, '<='],
- [BinaryOperator.Minus, '-'],
- [BinaryOperator.Modulo, '%'],
- [BinaryOperator.Multiply, '*'],
- [BinaryOperator.NotEquals, '!='],
- [BinaryOperator.NotIdentical, '!=='],
- [BinaryOperator.Or, '||'],
- [BinaryOperator.Plus, '+'],
- [BinaryOperator.NullishCoalesce, '??'],
- ]);
- class ExpressionTranslatorVisitor {
- factory;
- imports;
- contextFile;
- downlevelTaggedTemplates;
- downlevelVariableDeclarations;
- recordWrappedNode;
- constructor(factory, imports, contextFile, options) {
- this.factory = factory;
- this.imports = imports;
- this.contextFile = contextFile;
- this.downlevelTaggedTemplates = options.downlevelTaggedTemplates === true;
- this.downlevelVariableDeclarations = options.downlevelVariableDeclarations === true;
- this.recordWrappedNode = options.recordWrappedNode || (() => { });
- }
- visitDeclareVarStmt(stmt, context) {
- const varType = this.downlevelVariableDeclarations
- ? 'var'
- : stmt.hasModifier(exports.StmtModifier.Final)
- ? 'const'
- : 'let';
- return this.attachComments(this.factory.createVariableDeclaration(stmt.name, stmt.value?.visitExpression(this, context.withExpressionMode), varType), stmt.leadingComments);
- }
- visitDeclareFunctionStmt(stmt, context) {
- return this.attachComments(this.factory.createFunctionDeclaration(stmt.name, stmt.params.map((param) => param.name), this.factory.createBlock(this.visitStatements(stmt.statements, context.withStatementMode))), stmt.leadingComments);
- }
- visitExpressionStmt(stmt, context) {
- return this.attachComments(this.factory.createExpressionStatement(stmt.expr.visitExpression(this, context.withStatementMode)), stmt.leadingComments);
- }
- visitReturnStmt(stmt, context) {
- return this.attachComments(this.factory.createReturnStatement(stmt.value.visitExpression(this, context.withExpressionMode)), stmt.leadingComments);
- }
- visitIfStmt(stmt, context) {
- return this.attachComments(this.factory.createIfStatement(stmt.condition.visitExpression(this, context), this.factory.createBlock(this.visitStatements(stmt.trueCase, context.withStatementMode)), stmt.falseCase.length > 0
- ? this.factory.createBlock(this.visitStatements(stmt.falseCase, context.withStatementMode))
- : null), stmt.leadingComments);
- }
- visitReadVarExpr(ast, _context) {
- const identifier = this.factory.createIdentifier(ast.name);
- this.setSourceMapRange(identifier, ast.sourceSpan);
- return identifier;
- }
- visitWriteVarExpr(expr, context) {
- const assignment = this.factory.createAssignment(this.setSourceMapRange(this.factory.createIdentifier(expr.name), expr.sourceSpan), expr.value.visitExpression(this, context));
- return context.isStatement
- ? assignment
- : this.factory.createParenthesizedExpression(assignment);
- }
- visitWriteKeyExpr(expr, context) {
- const exprContext = context.withExpressionMode;
- const target = this.factory.createElementAccess(expr.receiver.visitExpression(this, exprContext), expr.index.visitExpression(this, exprContext));
- const assignment = this.factory.createAssignment(target, expr.value.visitExpression(this, exprContext));
- return context.isStatement
- ? assignment
- : this.factory.createParenthesizedExpression(assignment);
- }
- visitWritePropExpr(expr, context) {
- const target = this.factory.createPropertyAccess(expr.receiver.visitExpression(this, context), expr.name);
- return this.factory.createAssignment(target, expr.value.visitExpression(this, context));
- }
- visitInvokeFunctionExpr(ast, context) {
- return this.setSourceMapRange(this.factory.createCallExpression(ast.fn.visitExpression(this, context), ast.args.map((arg) => arg.visitExpression(this, context)), ast.pure), ast.sourceSpan);
- }
- visitTaggedTemplateLiteralExpr(ast, context) {
- return this.setSourceMapRange(this.createTaggedTemplateExpression(ast.tag.visitExpression(this, context), this.getTemplateLiteralFromAst(ast.template, context)), ast.sourceSpan);
- }
- visitTemplateLiteralExpr(ast, context) {
- return this.setSourceMapRange(this.factory.createTemplateLiteral(this.getTemplateLiteralFromAst(ast, context)), ast.sourceSpan);
- }
- visitInstantiateExpr(ast, context) {
- return this.factory.createNewExpression(ast.classExpr.visitExpression(this, context), ast.args.map((arg) => arg.visitExpression(this, context)));
- }
- visitLiteralExpr(ast, _context) {
- return this.setSourceMapRange(this.factory.createLiteral(ast.value), ast.sourceSpan);
- }
- visitLocalizedString(ast, context) {
- // A `$localize` message consists of `messageParts` and `expressions`, which get interleaved
- // together. The interleaved pieces look like:
- // `[messagePart0, expression0, messagePart1, expression1, messagePart2]`
- //
- // Note that there is always a message part at the start and end, and so therefore
- // `messageParts.length === expressions.length + 1`.
- //
- // Each message part may be prefixed with "metadata", which is wrapped in colons (:) delimiters.
- // The metadata is attached to the first and subsequent message parts by calls to
- // `serializeI18nHead()` and `serializeI18nTemplatePart()` respectively.
- //
- // The first message part (i.e. `ast.messageParts[0]`) is used to initialize `messageParts`
- // array.
- const elements = [createTemplateElement(ast.serializeI18nHead())];
- const expressions = [];
- for (let i = 0; i < ast.expressions.length; i++) {
- const placeholder = this.setSourceMapRange(ast.expressions[i].visitExpression(this, context), ast.getPlaceholderSourceSpan(i));
- expressions.push(placeholder);
- elements.push(createTemplateElement(ast.serializeI18nTemplatePart(i + 1)));
- }
- const localizeTag = this.factory.createIdentifier('$localize');
- return this.setSourceMapRange(this.createTaggedTemplateExpression(localizeTag, { elements, expressions }), ast.sourceSpan);
- }
- createTaggedTemplateExpression(tag, template) {
- return this.downlevelTaggedTemplates
- ? this.createES5TaggedTemplateFunctionCall(tag, template)
- : this.factory.createTaggedTemplate(tag, template);
- }
- /**
- * Translate the tagged template literal into a call that is compatible with ES5, using the
- * imported `__makeTemplateObject` helper for ES5 formatted output.
- */
- createES5TaggedTemplateFunctionCall(tagHandler, { elements, expressions }) {
- // Ensure that the `__makeTemplateObject()` helper has been imported.
- const __makeTemplateObjectHelper = this.imports.addImport({
- exportModuleSpecifier: 'tslib',
- exportSymbolName: '__makeTemplateObject',
- requestedFile: this.contextFile,
- });
- // Collect up the cooked and raw strings into two separate arrays.
- const cooked = [];
- const raw = [];
- for (const element of elements) {
- cooked.push(this.factory.setSourceMapRange(this.factory.createLiteral(element.cooked), element.range));
- raw.push(this.factory.setSourceMapRange(this.factory.createLiteral(element.raw), element.range));
- }
- // Generate the helper call in the form: `__makeTemplateObject([cooked], [raw]);`
- const templateHelperCall = this.factory.createCallExpression(__makeTemplateObjectHelper, [this.factory.createArrayLiteral(cooked), this.factory.createArrayLiteral(raw)],
- /* pure */ false);
- // Finally create the tagged handler call in the form:
- // `tag(__makeTemplateObject([cooked], [raw]), ...expressions);`
- return this.factory.createCallExpression(tagHandler, [templateHelperCall, ...expressions],
- /* pure */ false);
- }
- visitExternalExpr(ast, _context) {
- if (ast.value.name === null) {
- if (ast.value.moduleName === null) {
- throw new Error('Invalid import without name nor moduleName');
- }
- return this.imports.addImport({
- exportModuleSpecifier: ast.value.moduleName,
- exportSymbolName: null,
- requestedFile: this.contextFile,
- });
- }
- // If a moduleName is specified, this is a normal import. If there's no module name, it's a
- // reference to a global/ambient symbol.
- if (ast.value.moduleName !== null) {
- // This is a normal import. Find the imported module.
- return this.imports.addImport({
- exportModuleSpecifier: ast.value.moduleName,
- exportSymbolName: ast.value.name,
- requestedFile: this.contextFile,
- });
- }
- else {
- // The symbol is ambient, so just reference it.
- return this.factory.createIdentifier(ast.value.name);
- }
- }
- visitConditionalExpr(ast, context) {
- let cond = ast.condition.visitExpression(this, context);
- // Ordinarily the ternary operator is right-associative. The following are equivalent:
- // `a ? b : c ? d : e` => `a ? b : (c ? d : e)`
- //
- // However, occasionally Angular needs to produce a left-associative conditional, such as in
- // the case of a null-safe navigation production: `{{a?.b ? c : d}}`. This template produces
- // a ternary of the form:
- // `a == null ? null : rest of expression`
- // If the rest of the expression is also a ternary though, this would produce the form:
- // `a == null ? null : a.b ? c : d`
- // which, if left as right-associative, would be incorrectly associated as:
- // `a == null ? null : (a.b ? c : d)`
- //
- // In such cases, the left-associativity needs to be enforced with parentheses:
- // `(a == null ? null : a.b) ? c : d`
- //
- // Such parentheses could always be included in the condition (guaranteeing correct behavior) in
- // all cases, but this has a code size cost. Instead, parentheses are added only when a
- // conditional expression is directly used as the condition of another.
- //
- // TODO(alxhub): investigate better logic for precendence of conditional operators
- if (ast.condition instanceof ConditionalExpr) {
- // The condition of this ternary needs to be wrapped in parentheses to maintain
- // left-associativity.
- cond = this.factory.createParenthesizedExpression(cond);
- }
- return this.factory.createConditional(cond, ast.trueCase.visitExpression(this, context), ast.falseCase.visitExpression(this, context));
- }
- visitDynamicImportExpr(ast, context) {
- const urlExpression = typeof ast.url === 'string'
- ? this.factory.createLiteral(ast.url)
- : ast.url.visitExpression(this, context);
- if (ast.urlComment) {
- this.factory.attachComments(urlExpression, [leadingComment(ast.urlComment, true)]);
- }
- return this.factory.createDynamicImport(urlExpression);
- }
- visitNotExpr(ast, context) {
- return this.factory.createUnaryExpression('!', ast.condition.visitExpression(this, context));
- }
- visitFunctionExpr(ast, context) {
- return this.factory.createFunctionExpression(ast.name ?? null, ast.params.map((param) => param.name), this.factory.createBlock(this.visitStatements(ast.statements, context)));
- }
- visitArrowFunctionExpr(ast, context) {
- return this.factory.createArrowFunctionExpression(ast.params.map((param) => param.name), Array.isArray(ast.body)
- ? this.factory.createBlock(this.visitStatements(ast.body, context))
- : ast.body.visitExpression(this, context));
- }
- visitBinaryOperatorExpr(ast, context) {
- if (!BINARY_OPERATORS$1.has(ast.operator)) {
- throw new Error(`Unknown binary operator: ${BinaryOperator[ast.operator]}`);
- }
- return this.factory.createBinaryExpression(ast.lhs.visitExpression(this, context), BINARY_OPERATORS$1.get(ast.operator), ast.rhs.visitExpression(this, context));
- }
- visitReadPropExpr(ast, context) {
- return this.factory.createPropertyAccess(ast.receiver.visitExpression(this, context), ast.name);
- }
- visitReadKeyExpr(ast, context) {
- return this.factory.createElementAccess(ast.receiver.visitExpression(this, context), ast.index.visitExpression(this, context));
- }
- visitLiteralArrayExpr(ast, context) {
- return this.factory.createArrayLiteral(ast.entries.map((expr) => this.setSourceMapRange(expr.visitExpression(this, context), ast.sourceSpan)));
- }
- visitLiteralMapExpr(ast, context) {
- const properties = ast.entries.map((entry) => {
- return {
- propertyName: entry.key,
- quoted: entry.quoted,
- value: entry.value.visitExpression(this, context),
- };
- });
- return this.setSourceMapRange(this.factory.createObjectLiteral(properties), ast.sourceSpan);
- }
- visitCommaExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitTemplateLiteralElementExpr(ast, context) {
- throw new Error('Method not implemented');
- }
- visitWrappedNodeExpr(ast, _context) {
- this.recordWrappedNode(ast);
- return ast.node;
- }
- visitTypeofExpr(ast, context) {
- return this.factory.createTypeOfExpression(ast.expr.visitExpression(this, context));
- }
- visitUnaryOperatorExpr(ast, context) {
- if (!UNARY_OPERATORS$1.has(ast.operator)) {
- throw new Error(`Unknown unary operator: ${UnaryOperator[ast.operator]}`);
- }
- return this.factory.createUnaryExpression(UNARY_OPERATORS$1.get(ast.operator), ast.expr.visitExpression(this, context));
- }
- visitStatements(statements, context) {
- return statements
- .map((stmt) => stmt.visitStatement(this, context))
- .filter((stmt) => stmt !== undefined);
- }
- setSourceMapRange(ast, span) {
- return this.factory.setSourceMapRange(ast, createRange(span));
- }
- attachComments(statement, leadingComments) {
- if (leadingComments !== undefined) {
- this.factory.attachComments(statement, leadingComments);
- }
- return statement;
- }
- getTemplateLiteralFromAst(ast, context) {
- return {
- elements: ast.elements.map((e) => createTemplateElement({
- cooked: e.text,
- raw: e.rawText,
- range: e.sourceSpan ?? ast.sourceSpan,
- })),
- expressions: ast.expressions.map((e) => e.visitExpression(this, context)),
- };
- }
- }
- /**
- * Convert a cooked-raw string object into one that can be used by the AST factories.
- */
- function createTemplateElement({ cooked, raw, range, }) {
- return { cooked, raw, range: createRange(range) };
- }
- /**
- * Convert an OutputAST source-span into a range that can be used by the AST factories.
- */
- function createRange(span) {
- if (span === null) {
- return null;
- }
- const { start, end } = span;
- const { url, content } = start.file;
- if (!url) {
- return null;
- }
- return {
- url,
- content,
- start: { offset: start.offset, line: start.line, column: start.col },
- end: { offset: end.offset, line: end.line, column: end.col },
- };
- }
- const INELIGIBLE = {};
- /**
- * Determines whether the provided type can be emitted, which means that it can be safely emitted
- * into a different location.
- *
- * If this function returns true, a `TypeEmitter` should be able to succeed. Vice versa, if this
- * function returns false, then using the `TypeEmitter` should not be attempted as it is known to
- * fail.
- */
- function canEmitType(type, canEmit) {
- return canEmitTypeWorker(type);
- function canEmitTypeWorker(type) {
- return visitNode(type) !== INELIGIBLE;
- }
- // To determine whether a type can be emitted, we have to recursively look through all type nodes.
- // If an unsupported type node is found at any position within the type, then the `INELIGIBLE`
- // constant is returned to stop the recursive walk as the type as a whole cannot be emitted in
- // that case. Otherwise, the result of visiting all child nodes determines the result. If no
- // ineligible type reference node is found then the walk returns `undefined`, indicating that
- // no type node was visited that could not be emitted.
- function visitNode(node) {
- // `import('module')` type nodes are not supported, as it may require rewriting the module
- // specifier which is currently not done.
- if (ts.isImportTypeNode(node)) {
- return INELIGIBLE;
- }
- // Emitting a type reference node in a different context requires that an import for the type
- // can be created. If a type reference node cannot be emitted, `INELIGIBLE` is returned to stop
- // the walk.
- if (ts.isTypeReferenceNode(node) && !canEmitTypeReference(node)) {
- return INELIGIBLE;
- }
- else {
- return ts.forEachChild(node, visitNode);
- }
- }
- function canEmitTypeReference(type) {
- if (!canEmit(type)) {
- return false;
- }
- // The type can be emitted if either it does not have any type arguments, or all of them can be
- // emitted.
- return type.typeArguments === undefined || type.typeArguments.every(canEmitTypeWorker);
- }
- }
- /**
- * Given a `ts.TypeNode`, this class derives an equivalent `ts.TypeNode` that has been emitted into
- * a different context.
- *
- * For example, consider the following code:
- *
- * ```ts
- * import {NgIterable} from '@angular/core';
- *
- * class NgForOf<T, U extends NgIterable<T>> {}
- * ```
- *
- * Here, the generic type parameters `T` and `U` can be emitted into a different context, as the
- * type reference to `NgIterable` originates from an absolute module import so that it can be
- * emitted anywhere, using that same module import. The process of emitting translates the
- * `NgIterable` type reference to a type reference that is valid in the context in which it is
- * emitted, for example:
- *
- * ```ts
- * import * as i0 from '@angular/core';
- * import * as i1 from '@angular/common';
- *
- * const _ctor1: <T, U extends i0.NgIterable<T>>(o: Pick<i1.NgForOf<T, U>, 'ngForOf'>):
- * i1.NgForOf<T, U>;
- * ```
- *
- * Notice how the type reference for `NgIterable` has been translated into a qualified name,
- * referring to the namespace import that was created.
- */
- class TypeEmitter {
- translator;
- constructor(translator) {
- this.translator = translator;
- }
- emitType(type) {
- const typeReferenceTransformer = (context) => {
- const visitNode = (node) => {
- if (ts.isImportTypeNode(node)) {
- throw new Error('Unable to emit import type');
- }
- if (ts.isTypeReferenceNode(node)) {
- return this.emitTypeReference(node);
- }
- else if (ts.isLiteralExpression(node)) {
- // TypeScript would typically take the emit text for a literal expression from the source
- // file itself. As the type node is being emitted into a different file, however,
- // TypeScript would extract the literal text from the wrong source file. To mitigate this
- // issue the literal is cloned and explicitly marked as synthesized by setting its text
- // range to a negative range, forcing TypeScript to determine the node's literal text from
- // the synthesized node's text instead of the incorrect source file.
- let clone;
- if (ts.isStringLiteral(node)) {
- clone = ts.factory.createStringLiteral(node.text);
- }
- else if (ts.isNumericLiteral(node)) {
- clone = ts.factory.createNumericLiteral(node.text);
- }
- else if (ts.isBigIntLiteral(node)) {
- clone = ts.factory.createBigIntLiteral(node.text);
- }
- else if (ts.isNoSubstitutionTemplateLiteral(node)) {
- clone = ts.factory.createNoSubstitutionTemplateLiteral(node.text, node.rawText);
- }
- else if (ts.isRegularExpressionLiteral(node)) {
- clone = ts.factory.createRegularExpressionLiteral(node.text);
- }
- else {
- throw new Error(`Unsupported literal kind ${ts.SyntaxKind[node.kind]}`);
- }
- ts.setTextRange(clone, { pos: -1, end: -1 });
- return clone;
- }
- else {
- return ts.visitEachChild(node, visitNode, context);
- }
- };
- return (node) => ts.visitNode(node, visitNode, ts.isTypeNode);
- };
- return ts.transform(type, [typeReferenceTransformer]).transformed[0];
- }
- emitTypeReference(type) {
- // Determine the reference that the type corresponds with.
- const translatedType = this.translator(type);
- if (translatedType === null) {
- throw new Error('Unable to emit an unresolved reference');
- }
- // Emit the type arguments, if any.
- let typeArguments = undefined;
- if (type.typeArguments !== undefined) {
- typeArguments = ts.factory.createNodeArray(type.typeArguments.map((typeArg) => this.emitType(typeArg)));
- }
- return ts.factory.updateTypeReferenceNode(type, translatedType.typeName, typeArguments);
- }
- }
- /*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
- /**
- * Creates a TypeScript node representing a numeric value.
- */
- function tsNumericExpression$1(value) {
- // As of TypeScript 5.3 negative numbers are represented as `prefixUnaryOperator` and passing a
- // negative number (even as a string) into `createNumericLiteral` will result in an error.
- if (value < 0) {
- const operand = ts.factory.createNumericLiteral(Math.abs(value));
- return ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, operand);
- }
- return ts.factory.createNumericLiteral(value);
- }
- function translateType(type, contextFile, reflector, refEmitter, imports) {
- return type.visitType(new TypeTranslatorVisitor(imports, contextFile, reflector, refEmitter), new Context$1(false));
- }
- class TypeTranslatorVisitor {
- imports;
- contextFile;
- reflector;
- refEmitter;
- constructor(imports, contextFile, reflector, refEmitter) {
- this.imports = imports;
- this.contextFile = contextFile;
- this.reflector = reflector;
- this.refEmitter = refEmitter;
- }
- visitBuiltinType(type, context) {
- switch (type.name) {
- case BuiltinTypeName.Bool:
- return ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
- case BuiltinTypeName.Dynamic:
- return ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
- case BuiltinTypeName.Int:
- case BuiltinTypeName.Number:
- return ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
- case BuiltinTypeName.String:
- return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
- case BuiltinTypeName.None:
- return ts.factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword);
- default:
- throw new Error(`Unsupported builtin type: ${BuiltinTypeName[type.name]}`);
- }
- }
- visitExpressionType(type, context) {
- const typeNode = this.translateExpression(type.value, context);
- if (type.typeParams === null) {
- return typeNode;
- }
- if (!ts.isTypeReferenceNode(typeNode)) {
- throw new Error('An ExpressionType with type arguments must translate into a TypeReferenceNode');
- }
- else if (typeNode.typeArguments !== undefined) {
- throw new Error(`An ExpressionType with type arguments cannot have multiple levels of type arguments`);
- }
- const typeArgs = type.typeParams.map((param) => this.translateType(param, context));
- return ts.factory.createTypeReferenceNode(typeNode.typeName, typeArgs);
- }
- visitArrayType(type, context) {
- return ts.factory.createArrayTypeNode(this.translateType(type.of, context));
- }
- visitMapType(type, context) {
- const parameter = ts.factory.createParameterDeclaration(undefined, undefined, 'key', undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword));
- const typeArgs = type.valueType !== null
- ? this.translateType(type.valueType, context)
- : ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
- const indexSignature = ts.factory.createIndexSignature(undefined, [parameter], typeArgs);
- return ts.factory.createTypeLiteralNode([indexSignature]);
- }
- visitTransplantedType(ast, context) {
- const node = ast.type instanceof Reference ? ast.type.node : ast.type;
- if (!ts.isTypeNode(node)) {
- throw new Error(`A TransplantedType must wrap a TypeNode`);
- }
- const viaModule = ast.type instanceof Reference ? ast.type.bestGuessOwningModule : null;
- const emitter = new TypeEmitter((typeRef) => this.translateTypeReference(typeRef, context, viaModule));
- return emitter.emitType(node);
- }
- visitReadVarExpr(ast, context) {
- if (ast.name === null) {
- throw new Error(`ReadVarExpr with no variable name in type`);
- }
- return ts.factory.createTypeQueryNode(ts.factory.createIdentifier(ast.name));
- }
- visitWriteVarExpr(expr, context) {
- throw new Error('Method not implemented.');
- }
- visitWriteKeyExpr(expr, context) {
- throw new Error('Method not implemented.');
- }
- visitWritePropExpr(expr, context) {
- throw new Error('Method not implemented.');
- }
- visitInvokeFunctionExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitTaggedTemplateLiteralExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitTemplateLiteralExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitTemplateLiteralElementExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitInstantiateExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitLiteralExpr(ast, context) {
- if (ast.value === null) {
- return ts.factory.createLiteralTypeNode(ts.factory.createNull());
- }
- else if (ast.value === undefined) {
- return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword);
- }
- else if (typeof ast.value === 'boolean') {
- return ts.factory.createLiteralTypeNode(ast.value ? ts.factory.createTrue() : ts.factory.createFalse());
- }
- else if (typeof ast.value === 'number') {
- return ts.factory.createLiteralTypeNode(tsNumericExpression$1(ast.value));
- }
- else {
- return ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(ast.value));
- }
- }
- visitLocalizedString(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitExternalExpr(ast, context) {
- if (ast.value.moduleName === null || ast.value.name === null) {
- throw new Error(`Import unknown module or symbol`);
- }
- const typeName = this.imports.addImport({
- exportModuleSpecifier: ast.value.moduleName,
- exportSymbolName: ast.value.name,
- requestedFile: this.contextFile,
- asTypeReference: true,
- });
- const typeArguments = ast.typeParams !== null
- ? ast.typeParams.map((type) => this.translateType(type, context))
- : undefined;
- return ts.factory.createTypeReferenceNode(typeName, typeArguments);
- }
- visitConditionalExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitDynamicImportExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitNotExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitFunctionExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitArrowFunctionExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitUnaryOperatorExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitBinaryOperatorExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitReadPropExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitReadKeyExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitLiteralArrayExpr(ast, context) {
- const values = ast.entries.map((expr) => this.translateExpression(expr, context));
- return ts.factory.createTupleTypeNode(values);
- }
- visitLiteralMapExpr(ast, context) {
- const entries = ast.entries.map((entry) => {
- const { key, quoted } = entry;
- const type = this.translateExpression(entry.value, context);
- return ts.factory.createPropertySignature(
- /* modifiers */ undefined,
- /* name */ quoted ? ts.factory.createStringLiteral(key) : key,
- /* questionToken */ undefined,
- /* type */ type);
- });
- return ts.factory.createTypeLiteralNode(entries);
- }
- visitCommaExpr(ast, context) {
- throw new Error('Method not implemented.');
- }
- visitWrappedNodeExpr(ast, context) {
- const node = ast.node;
- if (ts.isEntityName(node)) {
- return ts.factory.createTypeReferenceNode(node, /* typeArguments */ undefined);
- }
- else if (ts.isTypeNode(node)) {
- return node;
- }
- else if (ts.isLiteralExpression(node)) {
- return ts.factory.createLiteralTypeNode(node);
- }
- else {
- throw new Error(`Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts.SyntaxKind[node.kind]}`);
- }
- }
- visitTypeofExpr(ast, context) {
- const typeNode = this.translateExpression(ast.expr, context);
- if (!ts.isTypeReferenceNode(typeNode)) {
- throw new Error(`The target of a typeof expression must be a type reference, but it was
- ${ts.SyntaxKind[typeNode.kind]}`);
- }
- return ts.factory.createTypeQueryNode(typeNode.typeName);
- }
- translateType(type, context) {
- const typeNode = type.visitType(this, context);
- if (!ts.isTypeNode(typeNode)) {
- throw new Error(`A Type must translate to a TypeNode, but was ${ts.SyntaxKind[typeNode.kind]}`);
- }
- return typeNode;
- }
- translateExpression(expr, context) {
- const typeNode = expr.visitExpression(this, context);
- if (!ts.isTypeNode(typeNode)) {
- throw new Error(`An Expression must translate to a TypeNode, but was ${ts.SyntaxKind[typeNode.kind]}`);
- }
- return typeNode;
- }
- translateTypeReference(type, context, viaModule) {
- const target = ts.isIdentifier(type.typeName) ? type.typeName : type.typeName.right;
- const declaration = this.reflector.getDeclarationOfIdentifier(target);
- if (declaration === null) {
- throw new Error(`Unable to statically determine the declaration file of type node ${target.text}`);
- }
- let owningModule = viaModule;
- if (typeof declaration.viaModule === 'string') {
- owningModule = {
- specifier: declaration.viaModule,
- resolutionContext: type.getSourceFile().fileName,
- };
- }
- const reference = new Reference(declaration.node, declaration.viaModule === AmbientImport ? AmbientImport : owningModule);
- const emittedType = this.refEmitter.emit(reference, this.contextFile, exports.ImportFlags.NoAliasing | exports.ImportFlags.AllowTypeImports | exports.ImportFlags.AllowAmbientReferences);
- assertSuccessfulReferenceEmit(emittedType, target, 'type');
- const typeNode = this.translateExpression(emittedType.expression, context);
- if (!ts.isTypeReferenceNode(typeNode)) {
- throw new Error(`Expected TypeReferenceNode for emitted reference, got ${ts.SyntaxKind[typeNode.kind]}.`);
- }
- return typeNode;
- }
- }
- /**
- * Different optimizers use different annotations on a function or method call to indicate its pure
- * status.
- */
- var PureAnnotation;
- (function (PureAnnotation) {
- /**
- * Closure's annotation for purity is `@pureOrBreakMyCode`, but this needs to be in a semantic
- * (jsdoc) enabled comment. Thus, the actual comment text for Closure must include the `*` that
- * turns a `/*` comment into a `/**` comment, as well as surrounding whitespace.
- */
- PureAnnotation["CLOSURE"] = "* @pureOrBreakMyCode ";
- PureAnnotation["TERSER"] = "@__PURE__";
- })(PureAnnotation || (PureAnnotation = {}));
- const UNARY_OPERATORS = {
- '+': ts.SyntaxKind.PlusToken,
- '-': ts.SyntaxKind.MinusToken,
- '!': ts.SyntaxKind.ExclamationToken,
- };
- const BINARY_OPERATORS = {
- '&&': ts.SyntaxKind.AmpersandAmpersandToken,
- '>': ts.SyntaxKind.GreaterThanToken,
- '>=': ts.SyntaxKind.GreaterThanEqualsToken,
- '&': ts.SyntaxKind.AmpersandToken,
- '|': ts.SyntaxKind.BarToken,
- '/': ts.SyntaxKind.SlashToken,
- '==': ts.SyntaxKind.EqualsEqualsToken,
- '===': ts.SyntaxKind.EqualsEqualsEqualsToken,
- '<': ts.SyntaxKind.LessThanToken,
- '<=': ts.SyntaxKind.LessThanEqualsToken,
- '-': ts.SyntaxKind.MinusToken,
- '%': ts.SyntaxKind.PercentToken,
- '*': ts.SyntaxKind.AsteriskToken,
- '!=': ts.SyntaxKind.ExclamationEqualsToken,
- '!==': ts.SyntaxKind.ExclamationEqualsEqualsToken,
- '||': ts.SyntaxKind.BarBarToken,
- '+': ts.SyntaxKind.PlusToken,
- '??': ts.SyntaxKind.QuestionQuestionToken,
- };
- const VAR_TYPES = {
- 'const': ts.NodeFlags.Const,
- 'let': ts.NodeFlags.Let,
- 'var': ts.NodeFlags.None,
- };
- /**
- * A TypeScript flavoured implementation of the AstFactory.
- */
- class TypeScriptAstFactory {
- annotateForClosureCompiler;
- externalSourceFiles = new Map();
- constructor(annotateForClosureCompiler) {
- this.annotateForClosureCompiler = annotateForClosureCompiler;
- }
- attachComments = attachComments;
- createArrayLiteral = ts.factory.createArrayLiteralExpression;
- createAssignment(target, value) {
- return ts.factory.createBinaryExpression(target, ts.SyntaxKind.EqualsToken, value);
- }
- createBinaryExpression(leftOperand, operator, rightOperand) {
- return ts.factory.createBinaryExpression(leftOperand, BINARY_OPERATORS[operator], rightOperand);
- }
- createBlock(body) {
- return ts.factory.createBlock(body);
- }
- createCallExpression(callee, args, pure) {
- const call = ts.factory.createCallExpression(callee, undefined, args);
- if (pure) {
- ts.addSyntheticLeadingComment(call, ts.SyntaxKind.MultiLineCommentTrivia, this.annotateForClosureCompiler ? PureAnnotation.CLOSURE : PureAnnotation.TERSER,
- /* trailing newline */ false);
- }
- return call;
- }
- createConditional(condition, whenTrue, whenFalse) {
- return ts.factory.createConditionalExpression(condition, undefined, whenTrue, undefined, whenFalse);
- }
- createElementAccess = ts.factory.createElementAccessExpression;
- createExpressionStatement = ts.factory.createExpressionStatement;
- createDynamicImport(url) {
- return ts.factory.createCallExpression(ts.factory.createToken(ts.SyntaxKind.ImportKeyword),
- /* type */ undefined, [typeof url === 'string' ? ts.factory.createStringLiteral(url) : url]);
- }
- createFunctionDeclaration(functionName, parameters, body) {
- if (!ts.isBlock(body)) {
- throw new Error(`Invalid syntax, expected a block, but got ${ts.SyntaxKind[body.kind]}.`);
- }
- return ts.factory.createFunctionDeclaration(undefined, undefined, functionName, undefined, parameters.map((param) => ts.factory.createParameterDeclaration(undefined, undefined, param)), undefined, body);
- }
- createFunctionExpression(functionName, parameters, body) {
- if (!ts.isBlock(body)) {
- throw new Error(`Invalid syntax, expected a block, but got ${ts.SyntaxKind[body.kind]}.`);
- }
- return ts.factory.createFunctionExpression(undefined, undefined, functionName ?? undefined, undefined, parameters.map((param) => ts.factory.createParameterDeclaration(undefined, undefined, param)), undefined, body);
- }
- createArrowFunctionExpression(parameters, body) {
- if (ts.isStatement(body) && !ts.isBlock(body)) {
- throw new Error(`Invalid syntax, expected a block, but got ${ts.SyntaxKind[body.kind]}.`);
- }
- return ts.factory.createArrowFunction(undefined, undefined, parameters.map((param) => ts.factory.createParameterDeclaration(undefined, undefined, param)), undefined, undefined, body);
- }
- createIdentifier = ts.factory.createIdentifier;
- createIfStatement(condition, thenStatement, elseStatement) {
- return ts.factory.createIfStatement(condition, thenStatement, elseStatement ?? undefined);
- }
- createLiteral(value) {
- if (value === undefined) {
- return ts.factory.createIdentifier('undefined');
- }
- else if (value === null) {
- return ts.factory.createNull();
- }
- else if (typeof value === 'boolean') {
- return value ? ts.factory.createTrue() : ts.factory.createFalse();
- }
- else if (typeof value === 'number') {
- return tsNumericExpression$1(value);
- }
- else {
- return ts.factory.createStringLiteral(value);
- }
- }
- createNewExpression(expression, args) {
- return ts.factory.createNewExpression(expression, undefined, args);
- }
- createObjectLiteral(properties) {
- return ts.factory.createObjectLiteralExpression(properties.map((prop) => ts.factory.createPropertyAssignment(prop.quoted
- ? ts.factory.createStringLiteral(prop.propertyName)
- : ts.factory.createIdentifier(prop.propertyName), prop.value)));
- }
- createParenthesizedExpression = ts.factory.createParenthesizedExpression;
- createPropertyAccess = ts.factory.createPropertyAccessExpression;
- createReturnStatement(expression) {
- return ts.factory.createReturnStatement(expression ?? undefined);
- }
- createTaggedTemplate(tag, template) {
- return ts.factory.createTaggedTemplateExpression(tag, undefined, this.createTemplateLiteral(template));
- }
- createTemplateLiteral(template) {
- let templateLiteral;
- const length = template.elements.length;
- const head = template.elements[0];
- if (length === 1) {
- templateLiteral = ts.factory.createNoSubstitutionTemplateLiteral(head.cooked, head.raw);
- }
- else {
- const spans = [];
- // Create the middle parts
- for (let i = 1; i < length - 1; i++) {
- const { cooked, raw, range } = template.elements[i];
- const middle = createTemplateMiddle(cooked, raw);
- if (range !== null) {
- this.setSourceMapRange(middle, range);
- }
- spans.push(ts.factory.createTemplateSpan(template.expressions[i - 1], middle));
- }
- // Create the tail part
- const resolvedExpression = template.expressions[length - 2];
- const templatePart = template.elements[length - 1];
- const templateTail = createTemplateTail(templatePart.cooked, templatePart.raw);
- if (templatePart.range !== null) {
- this.setSourceMapRange(templateTail, templatePart.range);
- }
- spans.push(ts.factory.createTemplateSpan(resolvedExpression, templateTail));
- // Put it all together
- templateLiteral = ts.factory.createTemplateExpression(ts.factory.createTemplateHead(head.cooked, head.raw), spans);
- }
- if (head.range !== null) {
- this.setSourceMapRange(templateLiteral, head.range);
- }
- return templateLiteral;
- }
- createThrowStatement = ts.factory.createThrowStatement;
- createTypeOfExpression = ts.factory.createTypeOfExpression;
- createUnaryExpression(operator, operand) {
- return ts.factory.createPrefixUnaryExpression(UNARY_OPERATORS[operator], operand);
- }
- createVariableDeclaration(variableName, initializer, type) {
- return ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
- ts.factory.createVariableDeclaration(variableName, undefined, undefined, initializer ?? undefined),
- ], VAR_TYPES[type]));
- }
- setSourceMapRange(node, sourceMapRange) {
- if (sourceMapRange === null) {
- return node;
- }
- const url = sourceMapRange.url;
- if (!this.externalSourceFiles.has(url)) {
- this.externalSourceFiles.set(url, ts.createSourceMapSource(url, sourceMapRange.content, (pos) => pos));
- }
- const source = this.externalSourceFiles.get(url);
- ts.setSourceMapRange(node, {
- pos: sourceMapRange.start.offset,
- end: sourceMapRange.end.offset,
- source,
- });
- return node;
- }
- }
- // HACK: Use this in place of `ts.createTemplateMiddle()`.
- // Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed.
- function createTemplateMiddle(cooked, raw) {
- const node = ts.factory.createTemplateHead(cooked, raw);
- node.kind = ts.SyntaxKind.TemplateMiddle;
- return node;
- }
- // HACK: Use this in place of `ts.createTemplateTail()`.
- // Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed.
- function createTemplateTail(cooked, raw) {
- const node = ts.factory.createTemplateHead(cooked, raw);
- node.kind = ts.SyntaxKind.TemplateTail;
- return node;
- }
- /**
- * Attach the given `leadingComments` to the `statement` node.
- *
- * @param statement The statement that will have comments attached.
- * @param leadingComments The comments to attach to the statement.
- */
- function attachComments(statement, leadingComments) {
- for (const comment of leadingComments) {
- const commentKind = comment.multiline
- ? ts.SyntaxKind.MultiLineCommentTrivia
- : ts.SyntaxKind.SingleLineCommentTrivia;
- if (comment.multiline) {
- ts.addSyntheticLeadingComment(statement, commentKind, comment.toString(), comment.trailingNewline);
- }
- else {
- for (const line of comment.toString().split('\n')) {
- ts.addSyntheticLeadingComment(statement, commentKind, line, comment.trailingNewline);
- }
- }
- }
- }
- function translateExpression(contextFile, expression, imports, options = {}) {
- return expression.visitExpression(new ExpressionTranslatorVisitor(new TypeScriptAstFactory(options.annotateForClosureCompiler === true), imports, contextFile, options), new Context$1(false));
- }
- function translateStatement(contextFile, statement, imports, options = {}) {
- return statement.visitStatement(new ExpressionTranslatorVisitor(new TypeScriptAstFactory(options.annotateForClosureCompiler === true), imports, contextFile, options), new Context$1(true));
- }
- /**
- * Create a `ts.Diagnostic` which indicates the given class is part of the declarations of two or
- * more NgModules.
- *
- * The resulting `ts.Diagnostic` will have a context entry for each NgModule showing the point where
- * the directive/pipe exists in its `declarations` (if possible).
- */
- function makeDuplicateDeclarationError(node, data, kind) {
- const context = [];
- for (const decl of data) {
- if (decl.rawDeclarations === null) {
- continue;
- }
- // Try to find the reference to the declaration within the declarations array, to hang the
- // error there. If it can't be found, fall back on using the NgModule's name.
- const contextNode = decl.ref.getOriginForDiagnostics(decl.rawDeclarations, decl.ngModule.name);
- context.push(makeRelatedInformation(contextNode, `'${node.name.text}' is listed in the declarations of the NgModule '${decl.ngModule.name.text}'.`));
- }
- // Finally, produce the diagnostic.
- return makeDiagnostic(exports.ErrorCode.NGMODULE_DECLARATION_NOT_UNIQUE, node.name, `The ${kind} '${node.name.text}' is declared by more than one NgModule.`, context);
- }
- /**
- * Creates a `FatalDiagnosticError` for a node that did not evaluate to the expected type. The
- * diagnostic that is created will include details on why the value is incorrect, i.e. it includes
- * a representation of the actual type that was unsupported, or in the case of a dynamic value the
- * trace to the node where the dynamic value originated.
- *
- * @param node The node for which the diagnostic should be produced.
- * @param value The evaluated value that has the wrong type.
- * @param messageText The message text of the error.
- */
- function createValueHasWrongTypeError(node, value, messageText) {
- let chainedMessage;
- let relatedInformation;
- if (value instanceof DynamicValue) {
- chainedMessage = 'Value could not be determined statically.';
- relatedInformation = traceDynamicValue(node, value);
- }
- else if (value instanceof Reference) {
- const target = value.debugName !== null ? `'${value.debugName}'` : 'an anonymous declaration';
- chainedMessage = `Value is a reference to ${target}.`;
- const referenceNode = identifierOfNode(value.node) ?? value.node;
- relatedInformation = [makeRelatedInformation(referenceNode, 'Reference is declared here.')];
- }
- else {
- chainedMessage = `Value is of type '${describeResolvedType(value)}'.`;
- }
- const chain = {
- messageText,
- category: ts.DiagnosticCategory.Error,
- code: 0,
- next: [
- {
- messageText: chainedMessage,
- category: ts.DiagnosticCategory.Message,
- code: 0,
- },
- ],
- };
- return new FatalDiagnosticError(exports.ErrorCode.VALUE_HAS_WRONG_TYPE, node, chain, relatedInformation);
- }
- /**
- * Gets the diagnostics for a set of provider classes.
- * @param providerClasses Classes that should be checked.
- * @param providersDeclaration Node that declares the providers array.
- * @param registry Registry that keeps track of the registered injectable classes.
- */
- function getProviderDiagnostics(providerClasses, providersDeclaration, registry) {
- const diagnostics = [];
- for (const provider of providerClasses) {
- const injectableMeta = registry.getInjectableMeta(provider.node);
- if (injectableMeta !== null) {
- // The provided type is recognized as injectable, so we don't report a diagnostic for this
- // provider.
- continue;
- }
- const contextNode = provider.getOriginForDiagnostics(providersDeclaration);
- diagnostics.push(makeDiagnostic(exports.ErrorCode.UNDECORATED_PROVIDER, contextNode, `The class '${provider.node.name.text}' cannot be created via dependency injection, as it does not have an Angular decorator. This will result in an error at runtime.
- Either add the @Injectable() decorator to '${provider.node.name.text}', or configure a different provider (such as a provider with 'useFactory').
- `, [makeRelatedInformation(provider.node, `'${provider.node.name.text}' is declared here.`)]));
- }
- return diagnostics;
- }
- function getDirectiveDiagnostics(node, injectableRegistry, evaluator, reflector, scopeRegistry, strictInjectionParameters, kind) {
- let diagnostics = [];
- const addDiagnostics = (more) => {
- if (more === null) {
- return;
- }
- else if (diagnostics === null) {
- diagnostics = Array.isArray(more) ? more : [more];
- }
- else if (Array.isArray(more)) {
- diagnostics.push(...more);
- }
- else {
- diagnostics.push(more);
- }
- };
- const duplicateDeclarations = scopeRegistry.getDuplicateDeclarations(node);
- if (duplicateDeclarations !== null) {
- addDiagnostics(makeDuplicateDeclarationError(node, duplicateDeclarations, kind));
- }
- addDiagnostics(checkInheritanceOfInjectable(node, injectableRegistry, reflector, evaluator, strictInjectionParameters, kind));
- return diagnostics;
- }
- function validateHostDirectives(origin, hostDirectives, metaReader) {
- const diagnostics = [];
- for (const current of hostDirectives) {
- if (!isHostDirectiveMetaForGlobalMode(current)) {
- throw new Error('Impossible state: diagnostics code path for local compilation');
- }
- const hostMeta = flattenInheritedDirectiveMetadata(metaReader, current.directive);
- if (hostMeta === null) {
- diagnostics.push(makeDiagnostic(exports.ErrorCode.HOST_DIRECTIVE_INVALID, current.directive.getOriginForDiagnostics(origin), `${current.directive.debugName} must be a standalone directive to be used as a host directive`));
- continue;
- }
- if (!hostMeta.isStandalone) {
- diagnostics.push(makeDiagnostic(exports.ErrorCode.HOST_DIRECTIVE_NOT_STANDALONE, current.directive.getOriginForDiagnostics(origin), `Host directive ${hostMeta.name} must be standalone`));
- }
- if (hostMeta.isComponent) {
- diagnostics.push(makeDiagnostic(exports.ErrorCode.HOST_DIRECTIVE_COMPONENT, current.directive.getOriginForDiagnostics(origin), `Host directive ${hostMeta.name} cannot be a component`));
- }
- const requiredInputNames = Array.from(hostMeta.inputs)
- .filter((input) => input.required)
- .map((input) => input.classPropertyName);
- validateHostDirectiveMappings('input', current, hostMeta, origin, diagnostics, requiredInputNames.length > 0 ? new Set(requiredInputNames) : null);
- validateHostDirectiveMappings('output', current, hostMeta, origin, diagnostics, null);
- }
- return diagnostics;
- }
- function validateHostDirectiveMappings(bindingType, hostDirectiveMeta, meta, origin, diagnostics, requiredBindings) {
- if (!isHostDirectiveMetaForGlobalMode(hostDirectiveMeta)) {
- throw new Error('Impossible state: diagnostics code path for local compilation');
- }
- const className = meta.name;
- const hostDirectiveMappings = bindingType === 'input' ? hostDirectiveMeta.inputs : hostDirectiveMeta.outputs;
- const existingBindings = bindingType === 'input' ? meta.inputs : meta.outputs;
- const exposedRequiredBindings = new Set();
- for (const publicName in hostDirectiveMappings) {
- if (hostDirectiveMappings.hasOwnProperty(publicName)) {
- const bindings = existingBindings.getByBindingPropertyName(publicName);
- if (bindings === null) {
- diagnostics.push(makeDiagnostic(exports.ErrorCode.HOST_DIRECTIVE_UNDEFINED_BINDING, hostDirectiveMeta.directive.getOriginForDiagnostics(origin), `Directive ${className} does not have an ${bindingType} with a public name of ${publicName}.`));
- }
- else if (requiredBindings !== null) {
- for (const field of bindings) {
- if (requiredBindings.has(field.classPropertyName)) {
- exposedRequiredBindings.add(field.classPropertyName);
- }
- }
- }
- const remappedPublicName = hostDirectiveMappings[publicName];
- const bindingsForPublicName = existingBindings.getByBindingPropertyName(remappedPublicName);
- if (bindingsForPublicName !== null) {
- for (const binding of bindingsForPublicName) {
- if (binding.bindingPropertyName !== publicName) {
- diagnostics.push(makeDiagnostic(exports.ErrorCode.HOST_DIRECTIVE_CONFLICTING_ALIAS, hostDirectiveMeta.directive.getOriginForDiagnostics(origin), `Cannot alias ${bindingType} ${publicName} of host directive ${className} to ${remappedPublicName}, because it already has a different ${bindingType} with the same public name.`));
- }
- }
- }
- }
- }
- if (requiredBindings !== null && requiredBindings.size !== exposedRequiredBindings.size) {
- const missingBindings = [];
- for (const publicName of requiredBindings) {
- if (!exposedRequiredBindings.has(publicName)) {
- const name = existingBindings.getByClassPropertyName(publicName);
- if (name) {
- missingBindings.push(`'${name.bindingPropertyName}'`);
- }
- }
- }
- diagnostics.push(makeDiagnostic(exports.ErrorCode.HOST_DIRECTIVE_MISSING_REQUIRED_BINDING, hostDirectiveMeta.directive.getOriginForDiagnostics(origin), `Required ${bindingType}${missingBindings.length === 1 ? '' : 's'} ${missingBindings.join(', ')} from host directive ${className} must be exposed.`));
- }
- }
- function getUndecoratedClassWithAngularFeaturesDiagnostic(node) {
- return makeDiagnostic(exports.ErrorCode.UNDECORATED_CLASS_USING_ANGULAR_FEATURES, node.name, `Class is using Angular features but is not decorated. Please add an explicit ` +
- `Angular decorator.`);
- }
- function checkInheritanceOfInjectable(node, injectableRegistry, reflector, evaluator, strictInjectionParameters, kind) {
- const classWithCtor = findInheritedCtor(node, injectableRegistry, reflector, evaluator);
- if (classWithCtor === null || classWithCtor.isCtorValid) {
- // The class does not inherit a constructor, or the inherited constructor is compatible
- // with DI; no need to report a diagnostic.
- return null;
- }
- if (!classWithCtor.isDecorated) {
- // The inherited constructor exists in a class that does not have an Angular decorator.
- // This is an error, as there won't be a factory definition available for DI to invoke
- // the constructor.
- return getInheritedUndecoratedCtorDiagnostic(node, classWithCtor.ref, kind);
- }
- if (isFromDtsFile(classWithCtor.ref.node)) {
- // The inherited class is declared in a declaration file, in which case there is not enough
- // information to detect invalid constructors as `@Inject()` metadata is not present in the
- // declaration file. Consequently, we have to accept such occurrences, although they might
- // still fail at runtime.
- return null;
- }
- if (!strictInjectionParameters || isAbstractClassDeclaration(node)) {
- // An invalid constructor is only reported as error under `strictInjectionParameters` and
- // only for concrete classes; follow the same exclusions for derived types.
- return null;
- }
- return getInheritedInvalidCtorDiagnostic(node, classWithCtor.ref, kind);
- }
- function findInheritedCtor(node, injectableRegistry, reflector, evaluator) {
- if (!reflector.isClass(node) || reflector.getConstructorParameters(node) !== null) {
- // We should skip nodes that aren't classes. If a constructor exists, then no base class
- // definition is required on the runtime side - it's legal to inherit from any class.
- return null;
- }
- // The extends clause is an expression which can be as dynamic as the user wants. Try to
- // evaluate it, but fall back on ignoring the clause if it can't be understood. This is a View
- // Engine compatibility hack: View Engine ignores 'extends' expressions that it cannot understand.
- let baseClass = readBaseClass(node, reflector, evaluator);
- while (baseClass !== null) {
- if (baseClass === 'dynamic') {
- return null;
- }
- const injectableMeta = injectableRegistry.getInjectableMeta(baseClass.node);
- if (injectableMeta !== null) {
- if (injectableMeta.ctorDeps !== null) {
- // The class has an Angular decorator with a constructor.
- return {
- ref: baseClass,
- isCtorValid: injectableMeta.ctorDeps !== 'invalid',
- isDecorated: true,
- };
- }
- }
- else {
- const baseClassConstructorParams = reflector.getConstructorParameters(baseClass.node);
- if (baseClassConstructorParams !== null) {
- // The class is not decorated, but it does have constructor. An undecorated class is only
- // allowed to have a constructor without parameters, otherwise it is invalid.
- return {
- ref: baseClass,
- isCtorValid: baseClassConstructorParams.length === 0,
- isDecorated: false,
- };
- }
- }
- // Go up the chain and continue
- baseClass = readBaseClass(baseClass.node, reflector, evaluator);
- }
- return null;
- }
- function getInheritedInvalidCtorDiagnostic(node, baseClass, kind) {
- const baseClassName = baseClass.debugName;
- return makeDiagnostic(exports.ErrorCode.INJECTABLE_INHERITS_INVALID_CONSTRUCTOR, node.name, `The ${kind.toLowerCase()} ${node.name.text} inherits its constructor from ${baseClassName}, ` +
- `but the latter has a constructor parameter that is not compatible with dependency injection. ` +
- `Either add an explicit constructor to ${node.name.text} or change ${baseClassName}'s constructor to ` +
- `use parameters that are valid for DI.`);
- }
- function getInheritedUndecoratedCtorDiagnostic(node, baseClass, kind) {
- const baseClassName = baseClass.debugName;
- const baseNeedsDecorator = kind === 'Component' || kind === 'Directive' ? 'Directive' : 'Injectable';
- return makeDiagnostic(exports.ErrorCode.DIRECTIVE_INHERITS_UNDECORATED_CTOR, node.name, `The ${kind.toLowerCase()} ${node.name.text} inherits its constructor from ${baseClassName}, ` +
- `but the latter does not have an Angular decorator of its own. Dependency injection will not be able to ` +
- `resolve the parameters of ${baseClassName}'s constructor. Either add a @${baseNeedsDecorator} decorator ` +
- `to ${baseClassName}, or add an explicit constructor to ${node.name.text}.`);
- }
- /**
- * Throws `FatalDiagnosticError` with error code `LOCAL_COMPILATION_UNRESOLVED_CONST`
- * if the compilation mode is local and the value is not resolved due to being imported
- * from external files. This is a common scenario for errors in local compilation mode,
- * and so this helper can be used to quickly generate the relevant errors.
- *
- * @param nodeToHighlight Node to be highlighted in teh error message.
- * Will default to value.node if not provided.
- */
- function assertLocalCompilationUnresolvedConst(compilationMode, value, nodeToHighlight, errorMessage) {
- if (compilationMode === exports.CompilationMode.LOCAL &&
- value instanceof DynamicValue &&
- value.isFromUnknownIdentifier()) {
- throw new FatalDiagnosticError(exports.ErrorCode.LOCAL_COMPILATION_UNRESOLVED_CONST, nodeToHighlight ?? value.node, errorMessage);
- }
- }
- exports.ComponentScopeKind = void 0;
- (function (ComponentScopeKind) {
- ComponentScopeKind[ComponentScopeKind["NgModule"] = 0] = "NgModule";
- ComponentScopeKind[ComponentScopeKind["Standalone"] = 1] = "Standalone";
- })(exports.ComponentScopeKind || (exports.ComponentScopeKind = {}));
- /**
- * Validates that the initializer member is compatible with the given class
- * member in terms of field access and visibility.
- *
- * @throws {FatalDiagnosticError} If the recognized initializer API is
- * incompatible.
- */
- function validateAccessOfInitializerApiMember({ api, call }, member) {
- if (!api.allowedAccessLevels.includes(member.accessLevel)) {
- throw new FatalDiagnosticError(exports.ErrorCode.INITIALIZER_API_DISALLOWED_MEMBER_VISIBILITY, call, makeDiagnosticChain(`Cannot use "${api.functionName}" on a class member that is declared as ${classMemberAccessLevelToString(member.accessLevel)}.`, [
- makeDiagnosticChain(`Update the class field to be either: ` +
- api.allowedAccessLevels.map((l) => classMemberAccessLevelToString(l)).join(', ')),
- ]));
- }
- }
- /**
- * Attempts to identify an Angular initializer function call.
- *
- * Note that multiple possible initializer API function names can be specified,
- * allowing for checking multiple types in one pass.
- *
- * @returns The parsed initializer API, or null if none was found.
- */
- function tryParseInitializerApi(functions, expression, reflector, importTracker) {
- if (!ts.isCallExpression(expression)) {
- return null;
- }
- const staticResult = parseTopLevelCall(expression, functions, importTracker) ||
- parseTopLevelRequiredCall(expression, functions, importTracker) ||
- parseTopLevelCallFromNamespace(expression, functions, importTracker);
- if (staticResult === null) {
- return null;
- }
- const { api, apiReference, isRequired } = staticResult;
- // Once we've statically determined that the initializer is one of the APIs we're looking for, we
- // need to verify it using the type checker which accounts for things like shadowed variables.
- // This should be done as the absolute last step since using the type check can be expensive.
- const resolvedImport = reflector.getImportOfIdentifier(apiReference);
- if (resolvedImport === null ||
- api.functionName !== resolvedImport.name ||
- api.owningModule !== resolvedImport.from) {
- return null;
- }
- return {
- api,
- call: expression,
- isRequired,
- };
- }
- /**
- * Attempts to parse a top-level call to an initializer function,
- * e.g. `prop = input()`. Returns null if it can't be parsed.
- */
- function parseTopLevelCall(call, functions, importTracker) {
- const node = call.expression;
- if (!ts.isIdentifier(node)) {
- return null;
- }
- const matchingApi = functions.find((fn) => importTracker.isPotentialReferenceToNamedImport(node, fn.functionName, fn.owningModule));
- if (matchingApi === undefined) {
- return null;
- }
- return { api: matchingApi, apiReference: node, isRequired: false };
- }
- /**
- * Attempts to parse a top-level call to a required initializer,
- * e.g. `prop = input.required()`. Returns null if it can't be parsed.
- */
- function parseTopLevelRequiredCall(call, functions, importTracker) {
- const node = call.expression;
- if (!ts.isPropertyAccessExpression(node) ||
- !ts.isIdentifier(node.expression) ||
- node.name.text !== 'required') {
- return null;
- }
- const expression = node.expression;
- const matchingApi = functions.find((fn) => importTracker.isPotentialReferenceToNamedImport(expression, fn.functionName, fn.owningModule));
- if (matchingApi === undefined) {
- return null;
- }
- return { api: matchingApi, apiReference: expression, isRequired: true };
- }
- /**
- * Attempts to parse a top-level call to a function referenced via a namespace import,
- * e.g. `prop = core.input.required()`. Returns null if it can't be parsed.
- */
- function parseTopLevelCallFromNamespace(call, functions, importTracker) {
- const node = call.expression;
- if (!ts.isPropertyAccessExpression(node)) {
- return null;
- }
- let apiReference = null;
- let matchingApi = undefined;
- let isRequired = false;
- // `prop = core.input()`
- if (ts.isIdentifier(node.expression) && ts.isIdentifier(node.name)) {
- const namespaceRef = node.expression;
- apiReference = node.name;
- matchingApi = functions.find((fn) => node.name.text === fn.functionName &&
- importTracker.isPotentialReferenceToNamespaceImport(namespaceRef, fn.owningModule));
- }
- else if (
- // `prop = core.input.required()`
- ts.isPropertyAccessExpression(node.expression) &&
- ts.isIdentifier(node.expression.expression) &&
- ts.isIdentifier(node.expression.name) &&
- node.name.text === 'required') {
- const potentialName = node.expression.name.text;
- const namespaceRef = node.expression.expression;
- apiReference = node.expression.name;
- matchingApi = functions.find((fn) => fn.functionName === potentialName &&
- importTracker.isPotentialReferenceToNamespaceImport(namespaceRef, fn.owningModule));
- isRequired = true;
- }
- if (matchingApi === undefined || apiReference === null) {
- return null;
- }
- return { api: matchingApi, apiReference, isRequired };
- }
- /**
- * Parses and validates input and output initializer function options.
- *
- * This currently only parses the `alias` option and returns it. The other
- * options for signal inputs are runtime constructs that aren't relevant at
- * compile time.
- */
- function parseAndValidateInputAndOutputOptions(optionsNode) {
- if (!ts.isObjectLiteralExpression(optionsNode)) {
- throw new FatalDiagnosticError(exports.ErrorCode.VALUE_HAS_WRONG_TYPE, optionsNode, 'Argument needs to be an object literal that is statically analyzable.');
- }
- const options = reflectObjectLiteral(optionsNode);
- let alias = undefined;
- if (options.has('alias')) {
- const aliasExpr = options.get('alias');
- if (!ts.isStringLiteralLike(aliasExpr)) {
- throw new FatalDiagnosticError(exports.ErrorCode.VALUE_HAS_WRONG_TYPE, aliasExpr, 'Alias needs to be a string that is statically analyzable.');
- }
- alias = aliasExpr.text;
- }
- return { alias };
- }
- /** Represents a function that can declare an input. */
- const INPUT_INITIALIZER_FN = {
- functionName: 'input',
- owningModule: '@angular/core',
- // Inputs are accessed from parents, via the `property` instruction.
- // Conceptually, the fields need to be publicly readable, but in practice,
- // accessing `protected` or `private` members works at runtime, so we can allow
- // cases where the input is intentionally not part of the public API, programmatically.
- // Note: `private` is omitted intentionally as this would be a conceptual confusion point.
- allowedAccessLevels: [
- exports.ClassMemberAccessLevel.PublicWritable,
- exports.ClassMemberAccessLevel.PublicReadonly,
- exports.ClassMemberAccessLevel.Protected,
- ],
- };
- /**
- * Attempts to parse a signal input class member. Returns the parsed
- * input mapping if possible.
- */
- function tryParseSignalInputMapping(member, reflector, importTracker) {
- if (member.value === null) {
- return null;
- }
- const signalInput = tryParseInitializerApi([INPUT_INITIALIZER_FN], member.value, reflector, importTracker);
- if (signalInput === null) {
- return null;
- }
- validateAccessOfInitializerApiMember(signalInput, member);
- const optionsNode = (signalInput.isRequired ? signalInput.call.arguments[0] : signalInput.call.arguments[1]);
- const options = optionsNode !== undefined ? parseAndValidateInputAndOutputOptions(optionsNode) : null;
- const classPropertyName = member.name;
- return {
- isSignal: true,
- classPropertyName,
- bindingPropertyName: options?.alias ?? classPropertyName,
- required: signalInput.isRequired,
- // Signal inputs do not capture complex transform metadata.
- // See more details in the `transform` type of `InputMapping`.
- transform: null,
- };
- }
- /** Represents a function that can declare a model. */
- const MODEL_INITIALIZER_FN = {
- functionName: 'model',
- owningModule: '@angular/core',
- // Inputs are accessed from parents, via the `property` instruction.
- // Conceptually, the fields need to be publicly readable, but in practice,
- // accessing `protected` or `private` members works at runtime, so we can allow
- // cases where the input is intentionally not part of the public API, programmatically.
- allowedAccessLevels: [
- exports.ClassMemberAccessLevel.PublicWritable,
- exports.ClassMemberAccessLevel.PublicReadonly,
- exports.ClassMemberAccessLevel.Protected,
- ],
- };
- /**
- * Attempts to parse a model class member. Returns the parsed model mapping if possible.
- */
- function tryParseSignalModelMapping(member, reflector, importTracker) {
- if (member.value === null) {
- return null;
- }
- const model = tryParseInitializerApi([MODEL_INITIALIZER_FN], member.value, reflector, importTracker);
- if (model === null) {
- return null;
- }
- validateAccessOfInitializerApiMember(model, member);
- const optionsNode = (model.isRequired ? model.call.arguments[0] : model.call.arguments[1]);
- const options = optionsNode !== undefined ? parseAndValidateInputAndOutputOptions(optionsNode) : null;
- const classPropertyName = member.name;
- const bindingPropertyName = options?.alias ?? classPropertyName;
- return {
- call: model.call,
- input: {
- isSignal: true,
- transform: null,
- classPropertyName,
- bindingPropertyName,
- required: model.isRequired,
- },
- output: {
- isSignal: false,
- classPropertyName,
- bindingPropertyName: bindingPropertyName + 'Change',
- },
- };
- }
- // Outputs are accessed from parents, via the `listener` instruction.
- // Conceptually, the fields need to be publicly readable, but in practice,
- // accessing `protected` or `private` members works at runtime, so we can allow
- // such outputs that may not want to expose the `OutputRef` as part of the
- // component API, programmatically.
- // Note: `private` is omitted intentionally as this would be a conceptual confusion point.
- const allowedAccessLevels = [
- exports.ClassMemberAccessLevel.PublicWritable,
- exports.ClassMemberAccessLevel.PublicReadonly,
- exports.ClassMemberAccessLevel.Protected,
- ];
- /** Possible functions that can declare an output. */
- const OUTPUT_INITIALIZER_FNS = [
- {
- functionName: 'output',
- owningModule: '@angular/core',
- allowedAccessLevels,
- },
- {
- functionName: 'outputFromObservable',
- owningModule: '@angular/core/rxjs-interop',
- allowedAccessLevels,
- },
- ];
- /**
- * Attempts to parse a signal output class member. Returns the parsed
- * input mapping if possible.
- */
- function tryParseInitializerBasedOutput(member, reflector, importTracker) {
- if (member.value === null) {
- return null;
- }
- const output = tryParseInitializerApi(OUTPUT_INITIALIZER_FNS, member.value, reflector, importTracker);
- if (output === null) {
- return null;
- }
- if (output.isRequired) {
- throw new FatalDiagnosticError(exports.ErrorCode.INITIALIZER_API_NO_REQUIRED_FUNCTION, output.call, `Output does not support ".required()".`);
- }
- validateAccessOfInitializerApiMember(output, member);
- // Options are the first parameter for `output()`, while for
- // the interop `outputFromObservable()` they are the second argument.
- const optionsNode = (output.api.functionName === 'output' ? output.call.arguments[0] : output.call.arguments[1]);
- const options = optionsNode !== undefined ? parseAndValidateInputAndOutputOptions(optionsNode) : null;
- const classPropertyName = member.name;
- return {
- call: output.call,
- metadata: {
- // Outputs are not signal-based.
- isSignal: false,
- classPropertyName,
- bindingPropertyName: options?.alias ?? classPropertyName,
- },
- };
- }
- /** Possible names of query initializer APIs. */
- const queryFunctionNames = [
- 'viewChild',
- 'viewChildren',
- 'contentChild',
- 'contentChildren',
- ];
- /** Possible query initializer API functions. */
- const QUERY_INITIALIZER_FNS = queryFunctionNames.map((fnName) => ({
- functionName: fnName,
- owningModule: '@angular/core',
- // Queries are accessed from within static blocks, via the query definition functions.
- // Conceptually, the fields could access private members— even ES private fields.
- // Support for ES private fields requires special caution and complexity when partial
- // output is linked— hence not supported. TS private members are allowed in static blocks.
- allowedAccessLevels: [
- exports.ClassMemberAccessLevel.PublicWritable,
- exports.ClassMemberAccessLevel.PublicReadonly,
- exports.ClassMemberAccessLevel.Protected,
- exports.ClassMemberAccessLevel.Private,
- ],
- }));
- // The `descendants` option is enabled by default, except for content children.
- const defaultDescendantsValue = (type) => type !== 'contentChildren';
- /**
- * Attempts to detect a possible query definition for the given class member.
- *
- * This function checks for all possible variants of queries and matches the
- * first one. The query is then analyzed and its resolved metadata is returned.
- *
- * @returns Resolved query metadata, or null if no query is declared.
- */
- function tryParseSignalQueryFromInitializer(member, reflector, importTracker) {
- if (member.value === null) {
- return null;
- }
- const query = tryParseInitializerApi(QUERY_INITIALIZER_FNS, member.value, reflector, importTracker);
- if (query === null) {
- return null;
- }
- validateAccessOfInitializerApiMember(query, member);
- const { functionName } = query.api;
- const isSingleQuery = functionName === 'viewChild' || functionName === 'contentChild';
- const predicateNode = query.call.arguments[0];
- if (predicateNode === undefined) {
- throw new FatalDiagnosticError(exports.ErrorCode.VALUE_HAS_WRONG_TYPE, query.call, 'No locator specified.');
- }
- const optionsNode = query.call.arguments[1];
- if (optionsNode !== undefined && !ts.isObjectLiteralExpression(optionsNode)) {
- throw new FatalDiagnosticError(exports.ErrorCode.VALUE_HAS_WRONG_TYPE, optionsNode, 'Argument needs to be an object literal.');
- }
- const options = optionsNode && reflectObjectLiteral(optionsNode);
- const read = options?.has('read') ? parseReadOption(options.get('read')) : null;
- const descendants = options?.has('descendants')
- ? parseDescendantsOption(options.get('descendants'))
- : defaultDescendantsValue(functionName);
- return {
- name: functionName,
- call: query.call,
- metadata: {
- isSignal: true,
- propertyName: member.name,
- static: false,
- emitDistinctChangesOnly: true,
- predicate: parseLocator(predicateNode, reflector),
- first: isSingleQuery,
- read,
- descendants,
- },
- };
- }
- /** Parses the locator/predicate of the query. */
- function parseLocator(expression, reflector) {
- // Attempt to unwrap `forwardRef` calls.
- const unwrappedExpression = tryUnwrapForwardRef(expression, reflector);
- if (unwrappedExpression !== null) {
- expression = unwrappedExpression;
- }
- if (ts.isStringLiteralLike(expression)) {
- return [expression.text];
- }
- return createMayBeForwardRefExpression(new WrappedNodeExpr(expression), unwrappedExpression !== null ? 2 /* ForwardRefHandling.Unwrapped */ : 0 /* ForwardRefHandling.None */);
- }
- /**
- * Parses the `read` option of a query.
- *
- * We only support the following patterns for the `read` option:
- * - `read: someImport.BLA`,
- * - `read: BLA`
- *
- * That is because we cannot trivially support complex expressions,
- * especially those referencing `this`. The read provider token will
- * live outside of the class in the static class definition.
- */
- function parseReadOption(value) {
- if (ts.isExpressionWithTypeArguments(value) ||
- ts.isParenthesizedExpression(value) ||
- ts.isAsExpression(value)) {
- return parseReadOption(value.expression);
- }
- if ((ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression)) ||
- ts.isIdentifier(value)) {
- return new WrappedNodeExpr(value);
- }
- throw new FatalDiagnosticError(exports.ErrorCode.VALUE_NOT_LITERAL, value, `Query "read" option expected a literal class reference.`);
- }
- /** Parses the `descendants` option of a query. */
- function parseDescendantsOption(value) {
- if (value.kind === ts.SyntaxKind.TrueKeyword) {
- return true;
- }
- else if (value.kind === ts.SyntaxKind.FalseKeyword) {
- return false;
- }
- throw new FatalDiagnosticError(exports.ErrorCode.VALUE_HAS_WRONG_TYPE, value, `Expected "descendants" option to be a boolean literal.`);
- }
- const EMPTY_OBJECT = {};
- const queryDecoratorNames = [
- 'ViewChild',
- 'ViewChildren',
- 'ContentChild',
- 'ContentChildren',
- ];
- const QUERY_TYPES = new Set(queryDecoratorNames);
- /**
- * Helper function to extract metadata from a `Directive` or `Component`. `Directive`s without a
- * selector are allowed to be used for abstract base classes. These abstract directives should not
- * appear in the declarations of an `NgModule` and additional verification is done when processing
- * the module.
- */
- function extractDirectiveMetadata(clazz, decorator, reflector, importTracker, evaluator, refEmitter, referencesRegistry, isCore, annotateForClosureCompiler, compilationMode, defaultSelector, strictStandalone, implicitStandaloneValue) {
- let directive;
- if (decorator.args === null || decorator.args.length === 0) {
- directive = new Map();
- }
- else if (decorator.args.length !== 1) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARITY_WRONG, decorator.node, `Incorrect number of arguments to @${decorator.name} decorator`);
- }
- else {
- const meta = unwrapExpression(decorator.args[0]);
- if (!ts.isObjectLiteralExpression(meta)) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, `@${decorator.name} argument must be an object literal`);
- }
- directive = reflectObjectLiteral(meta);
- }
- if (directive.has('jit')) {
- // The only allowed value is true, so there's no need to expand further.
- return { jitForced: true };
- }
- const members = reflector.getMembersOfClass(clazz);
- // Precompute a list of ts.ClassElements that have decorators. This includes things like @Input,
- // @Output, @HostBinding, etc.
- const decoratedElements = members.filter((member) => !member.isStatic && member.decorators !== null);
- const coreModule = isCore ? undefined : '@angular/core';
- // Construct the map of inputs both from the @Directive/@Component
- // decorator, and the decorated fields.
- const inputsFromMeta = parseInputsArray(clazz, directive, evaluator, reflector, refEmitter, compilationMode);
- const inputsFromFields = parseInputFields(clazz, members, evaluator, reflector, importTracker, refEmitter, isCore, compilationMode, inputsFromMeta, decorator);
- const inputs = ClassPropertyMapping.fromMappedObject({ ...inputsFromMeta, ...inputsFromFields });
- // And outputs.
- const outputsFromMeta = parseOutputsArray(directive, evaluator);
- const outputsFromFields = parseOutputFields(clazz, decorator, members, isCore, reflector, importTracker, evaluator, outputsFromMeta);
- const outputs = ClassPropertyMapping.fromMappedObject({ ...outputsFromMeta, ...outputsFromFields });
- // Parse queries of fields.
- const { viewQueries, contentQueries } = parseQueriesOfClassFields(members, reflector, importTracker, evaluator, isCore);
- if (directive.has('queries')) {
- const signalQueryFields = new Set([...viewQueries, ...contentQueries].filter((q) => q.isSignal).map((q) => q.propertyName));
- const queriesFromDecorator = extractQueriesFromDecorator(directive.get('queries'), reflector, evaluator, isCore);
- // Checks if the query is already declared/reserved via class members declaration.
- // If so, we throw a fatal diagnostic error to prevent this unintentional pattern.
- const checkAndUnwrapQuery = (q) => {
- if (signalQueryFields.has(q.metadata.propertyName)) {
- throw new FatalDiagnosticError(exports.ErrorCode.INITIALIZER_API_DECORATOR_METADATA_COLLISION, q.expr, `Query is declared multiple times. "@${decorator.name}" declares a query for the same property.`);
- }
- return q.metadata;
- };
- contentQueries.push(...queriesFromDecorator.content.map((q) => checkAndUnwrapQuery(q)));
- viewQueries.push(...queriesFromDecorator.view.map((q) => checkAndUnwrapQuery(q)));
- }
- // Parse the selector.
- let selector = defaultSelector;
- if (directive.has('selector')) {
- const expr = directive.get('selector');
- const resolved = evaluator.evaluate(expr);
- assertLocalCompilationUnresolvedConst(compilationMode, resolved, null, 'Unresolved identifier found for @Component.selector field! Did you ' +
- 'import this identifier from a file outside of the compilation unit? ' +
- 'This is not allowed when Angular compiler runs in local mode. Possible ' +
- 'solutions: 1) Move the declarations into a file within the compilation ' +
- 'unit, 2) Inline the selector');
- if (typeof resolved !== 'string') {
- throw createValueHasWrongTypeError(expr, resolved, `selector must be a string`);
- }
- // use default selector in case selector is an empty string
- selector = resolved === '' ? defaultSelector : resolved;
- if (!selector) {
- throw new FatalDiagnosticError(exports.ErrorCode.DIRECTIVE_MISSING_SELECTOR, expr, `Directive ${clazz.name.text} has no selector, please add it!`);
- }
- }
- const host = extractHostBindings(decoratedElements, evaluator, coreModule, compilationMode, directive);
- const providers = directive.has('providers')
- ? new WrappedNodeExpr(annotateForClosureCompiler
- ? wrapFunctionExpressionsInParens(directive.get('providers'))
- : directive.get('providers'))
- : null;
- // Determine if `ngOnChanges` is a lifecycle hook defined on the component.
- const usesOnChanges = members.some((member) => !member.isStatic && member.kind === exports.ClassMemberKind.Method && member.name === 'ngOnChanges');
- // Parse exportAs.
- let exportAs = null;
- if (directive.has('exportAs')) {
- const expr = directive.get('exportAs');
- const resolved = evaluator.evaluate(expr);
- assertLocalCompilationUnresolvedConst(compilationMode, resolved, null, 'Unresolved identifier found for exportAs field! Did you import this ' +
- 'identifier from a file outside of the compilation unit? This is not ' +
- 'allowed when Angular compiler runs in local mode. Possible solutions: ' +
- '1) Move the declarations into a file within the compilation unit, ' +
- '2) Inline the selector');
- if (typeof resolved !== 'string') {
- throw createValueHasWrongTypeError(expr, resolved, `exportAs must be a string`);
- }
- exportAs = resolved.split(',').map((part) => part.trim());
- }
- const rawCtorDeps = getConstructorDependencies(clazz, reflector, isCore);
- // Non-abstract directives (those with a selector) require valid constructor dependencies, whereas
- // abstract directives are allowed to have invalid dependencies, given that a subclass may call
- // the constructor explicitly.
- const ctorDeps = selector !== null
- ? validateConstructorDependencies(clazz, rawCtorDeps)
- : unwrapConstructorDependencies(rawCtorDeps);
- // Structural directives must have a `TemplateRef` dependency.
- const isStructural = ctorDeps !== null &&
- ctorDeps !== 'invalid' &&
- ctorDeps.some((dep) => dep.token instanceof ExternalExpr &&
- dep.token.value.moduleName === '@angular/core' &&
- dep.token.value.name === 'TemplateRef');
- let isStandalone = implicitStandaloneValue;
- if (directive.has('standalone')) {
- const expr = directive.get('standalone');
- const resolved = evaluator.evaluate(expr);
- if (typeof resolved !== 'boolean') {
- throw createValueHasWrongTypeError(expr, resolved, `standalone flag must be a boolean`);
- }
- isStandalone = resolved;
- if (!isStandalone && strictStandalone) {
- throw new FatalDiagnosticError(exports.ErrorCode.NON_STANDALONE_NOT_ALLOWED, expr, `Only standalone components/directives are allowed when 'strictStandalone' is enabled.`);
- }
- }
- let isSignal = false;
- if (directive.has('signals')) {
- const expr = directive.get('signals');
- const resolved = evaluator.evaluate(expr);
- if (typeof resolved !== 'boolean') {
- throw createValueHasWrongTypeError(expr, resolved, `signals flag must be a boolean`);
- }
- isSignal = resolved;
- }
- // Detect if the component inherits from another class
- const usesInheritance = reflector.hasBaseClass(clazz);
- const sourceFile = clazz.getSourceFile();
- const type = wrapTypeReference(reflector, clazz);
- const rawHostDirectives = directive.get('hostDirectives') || null;
- const hostDirectives = rawHostDirectives === null
- ? null
- : extractHostDirectives(rawHostDirectives, evaluator, compilationMode, createForwardRefResolver(isCore));
- if (compilationMode !== exports.CompilationMode.LOCAL && hostDirectives !== null) {
- // In global compilation mode where we do type checking, the template type-checker will need to
- // import host directive types, so add them as referenced by `clazz`. This will ensure that
- // libraries are required to export host directives which are visible from publicly exported
- // components.
- referencesRegistry.add(clazz, ...hostDirectives.map((hostDir) => {
- if (!isHostDirectiveMetaForGlobalMode(hostDir)) {
- throw new Error('Impossible state');
- }
- return hostDir.directive;
- }));
- }
- const metadata = {
- name: clazz.name.text,
- deps: ctorDeps,
- host: {
- ...host,
- },
- lifecycle: {
- usesOnChanges,
- },
- inputs: inputs.toJointMappedObject(toR3InputMetadata),
- outputs: outputs.toDirectMappedObject(),
- queries: contentQueries,
- viewQueries,
- selector,
- fullInheritance: false,
- type,
- typeArgumentCount: reflector.getGenericArityOfClass(clazz) || 0,
- typeSourceSpan: createSourceSpan(clazz.name),
- usesInheritance,
- exportAs,
- providers,
- isStandalone,
- isSignal,
- hostDirectives: hostDirectives?.map((hostDir) => toHostDirectiveMetadata(hostDir, sourceFile, refEmitter)) ||
- null,
- };
- return {
- jitForced: false,
- decorator: directive,
- metadata,
- inputs,
- outputs,
- isStructural,
- hostDirectives,
- rawHostDirectives,
- // Track inputs from class metadata. This is useful for migration efforts.
- inputFieldNamesFromMetadataArray: new Set(Object.values(inputsFromMeta).map((i) => i.classPropertyName)),
- };
- }
- function extractDecoratorQueryMetadata(exprNode, name, args, propertyName, reflector, evaluator) {
- if (args.length === 0) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARITY_WRONG, exprNode, `@${name} must have arguments`);
- }
- const first = name === 'ViewChild' || name === 'ContentChild';
- const forwardReferenceTarget = tryUnwrapForwardRef(args[0], reflector);
- const node = forwardReferenceTarget ?? args[0];
- const arg = evaluator.evaluate(node);
- /** Whether or not this query should collect only static results (see view/api.ts) */
- let isStatic = false;
- // Extract the predicate
- let predicate = null;
- if (arg instanceof Reference || arg instanceof DynamicValue) {
- // References and predicates that could not be evaluated statically are emitted as is.
- predicate = createMayBeForwardRefExpression(new WrappedNodeExpr(node), forwardReferenceTarget !== null ? 2 /* ForwardRefHandling.Unwrapped */ : 0 /* ForwardRefHandling.None */);
- }
- else if (typeof arg === 'string') {
- predicate = [arg];
- }
- else if (isStringArrayOrDie(arg, `@${name} predicate`, node)) {
- predicate = arg;
- }
- else {
- throw createValueHasWrongTypeError(node, arg, `@${name} predicate cannot be interpreted`);
- }
- // Extract the read and descendants options.
- let read = null;
- // The default value for descendants is true for every decorator except @ContentChildren.
- let descendants = name !== 'ContentChildren';
- let emitDistinctChangesOnly = emitDistinctChangesOnlyDefaultValue;
- if (args.length === 2) {
- const optionsExpr = unwrapExpression(args[1]);
- if (!ts.isObjectLiteralExpression(optionsExpr)) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARG_NOT_LITERAL, optionsExpr, `@${name} options must be an object literal`);
- }
- const options = reflectObjectLiteral(optionsExpr);
- if (options.has('read')) {
- read = new WrappedNodeExpr(options.get('read'));
- }
- if (options.has('descendants')) {
- const descendantsExpr = options.get('descendants');
- const descendantsValue = evaluator.evaluate(descendantsExpr);
- if (typeof descendantsValue !== 'boolean') {
- throw createValueHasWrongTypeError(descendantsExpr, descendantsValue, `@${name} options.descendants must be a boolean`);
- }
- descendants = descendantsValue;
- }
- if (options.has('emitDistinctChangesOnly')) {
- const emitDistinctChangesOnlyExpr = options.get('emitDistinctChangesOnly');
- const emitDistinctChangesOnlyValue = evaluator.evaluate(emitDistinctChangesOnlyExpr);
- if (typeof emitDistinctChangesOnlyValue !== 'boolean') {
- throw createValueHasWrongTypeError(emitDistinctChangesOnlyExpr, emitDistinctChangesOnlyValue, `@${name} options.emitDistinctChangesOnly must be a boolean`);
- }
- emitDistinctChangesOnly = emitDistinctChangesOnlyValue;
- }
- if (options.has('static')) {
- const staticValue = evaluator.evaluate(options.get('static'));
- if (typeof staticValue !== 'boolean') {
- throw createValueHasWrongTypeError(node, staticValue, `@${name} options.static must be a boolean`);
- }
- isStatic = staticValue;
- }
- }
- else if (args.length > 2) {
- // Too many arguments.
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARITY_WRONG, node, `@${name} has too many arguments`);
- }
- return {
- isSignal: false,
- propertyName,
- predicate,
- first,
- descendants,
- read,
- static: isStatic,
- emitDistinctChangesOnly,
- };
- }
- function extractHostBindings(members, evaluator, coreModule, compilationMode, metadata) {
- let bindings;
- if (metadata && metadata.has('host')) {
- bindings = evaluateHostExpressionBindings(metadata.get('host'), evaluator);
- }
- else {
- bindings = parseHostBindings({});
- }
- filterToMembersWithDecorator(members, 'HostBinding', coreModule).forEach(({ member, decorators }) => {
- decorators.forEach((decorator) => {
- let hostPropertyName = member.name;
- if (decorator.args !== null && decorator.args.length > 0) {
- if (decorator.args.length !== 1) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARITY_WRONG, decorator.node, `@HostBinding can have at most one argument, got ${decorator.args.length} argument(s)`);
- }
- const resolved = evaluator.evaluate(decorator.args[0]);
- // Specific error for local compilation mode if the argument cannot be resolved
- assertLocalCompilationUnresolvedConst(compilationMode, resolved, null, "Unresolved identifier found for @HostBinding's argument! Did " +
- 'you import this identifier from a file outside of the compilation ' +
- 'unit? This is not allowed when Angular compiler runs in local mode. ' +
- 'Possible solutions: 1) Move the declaration into a file within ' +
- 'the compilation unit, 2) Inline the argument');
- if (typeof resolved !== 'string') {
- throw createValueHasWrongTypeError(decorator.node, resolved, `@HostBinding's argument must be a string`);
- }
- hostPropertyName = resolved;
- }
- // Since this is a decorator, we know that the value is a class member. Always access it
- // through `this` so that further down the line it can't be confused for a literal value
- // (e.g. if there's a property called `true`). There is no size penalty, because all
- // values (except literals) are converted to `ctx.propName` eventually.
- bindings.properties[hostPropertyName] = getSafePropertyAccessString('this', member.name);
- });
- });
- filterToMembersWithDecorator(members, 'HostListener', coreModule).forEach(({ member, decorators }) => {
- decorators.forEach((decorator) => {
- let eventName = member.name;
- let args = [];
- if (decorator.args !== null && decorator.args.length > 0) {
- if (decorator.args.length > 2) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARITY_WRONG, decorator.args[2], `@HostListener can have at most two arguments`);
- }
- const resolved = evaluator.evaluate(decorator.args[0]);
- // Specific error for local compilation mode if the event name cannot be resolved
- assertLocalCompilationUnresolvedConst(compilationMode, resolved, null, "Unresolved identifier found for @HostListener's event name " +
- 'argument! Did you import this identifier from a file outside of ' +
- 'the compilation unit? This is not allowed when Angular compiler ' +
- 'runs in local mode. Possible solutions: 1) Move the declaration ' +
- 'into a file within the compilation unit, 2) Inline the argument');
- if (typeof resolved !== 'string') {
- throw createValueHasWrongTypeError(decorator.args[0], resolved, `@HostListener's event name argument must be a string`);
- }
- eventName = resolved;
- if (decorator.args.length === 2) {
- const expression = decorator.args[1];
- const resolvedArgs = evaluator.evaluate(decorator.args[1]);
- if (!isStringArrayOrDie(resolvedArgs, '@HostListener.args', expression)) {
- throw createValueHasWrongTypeError(decorator.args[1], resolvedArgs, `@HostListener's second argument must be a string array`);
- }
- args = resolvedArgs;
- }
- }
- bindings.listeners[eventName] = `${member.name}(${args.join(',')})`;
- });
- });
- return bindings;
- }
- function extractQueriesFromDecorator(queryData, reflector, evaluator, isCore) {
- const content = [];
- const view = [];
- if (!ts.isObjectLiteralExpression(queryData)) {
- throw new FatalDiagnosticError(exports.ErrorCode.VALUE_HAS_WRONG_TYPE, queryData, 'Decorator queries metadata must be an object literal');
- }
- reflectObjectLiteral(queryData).forEach((queryExpr, propertyName) => {
- queryExpr = unwrapExpression(queryExpr);
- if (!ts.isNewExpression(queryExpr)) {
- throw new FatalDiagnosticError(exports.ErrorCode.VALUE_HAS_WRONG_TYPE, queryData, 'Decorator query metadata must be an instance of a query type');
- }
- const queryType = ts.isPropertyAccessExpression(queryExpr.expression)
- ? queryExpr.expression.name
- : queryExpr.expression;
- if (!ts.isIdentifier(queryType)) {
- throw new FatalDiagnosticError(exports.ErrorCode.VALUE_HAS_WRONG_TYPE, queryData, 'Decorator query metadata must be an instance of a query type');
- }
- const type = reflector.getImportOfIdentifier(queryType);
- if (type === null ||
- (!isCore && type.from !== '@angular/core') ||
- !QUERY_TYPES.has(type.name)) {
- throw new FatalDiagnosticError(exports.ErrorCode.VALUE_HAS_WRONG_TYPE, queryData, 'Decorator query metadata must be an instance of a query type');
- }
- const query = extractDecoratorQueryMetadata(queryExpr, type.name, queryExpr.arguments || [], propertyName, reflector, evaluator);
- if (type.name.startsWith('Content')) {
- content.push({ expr: queryExpr, metadata: query });
- }
- else {
- view.push({ expr: queryExpr, metadata: query });
- }
- });
- return { content, view };
- }
- function parseDirectiveStyles(directive, evaluator, compilationMode) {
- const expression = directive.get('styles');
- if (!expression) {
- return null;
- }
- const evaluated = evaluator.evaluate(expression);
- const value = typeof evaluated === 'string' ? [evaluated] : evaluated;
- // Check if the identifier used for @Component.styles cannot be resolved in local compilation
- // mode. if the case, an error specific to this situation is generated.
- if (compilationMode === exports.CompilationMode.LOCAL) {
- let unresolvedNode = null;
- if (Array.isArray(value)) {
- const entry = value.find((e) => e instanceof DynamicValue && e.isFromUnknownIdentifier());
- unresolvedNode = entry?.node ?? null;
- }
- else if (value instanceof DynamicValue && value.isFromUnknownIdentifier()) {
- unresolvedNode = value.node;
- }
- if (unresolvedNode !== null) {
- throw new FatalDiagnosticError(exports.ErrorCode.LOCAL_COMPILATION_UNRESOLVED_CONST, unresolvedNode, 'Unresolved identifier found for @Component.styles field! Did you import ' +
- 'this identifier from a file outside of the compilation unit? This is ' +
- 'not allowed when Angular compiler runs in local mode. Possible ' +
- 'solutions: 1) Move the declarations into a file within the compilation ' +
- 'unit, 2) Inline the styles, 3) Move the styles into separate files and ' +
- 'include it using @Component.styleUrls');
- }
- }
- if (!isStringArrayOrDie(value, 'styles', expression)) {
- throw createValueHasWrongTypeError(expression, value, `Failed to resolve @Component.styles to a string or an array of strings`);
- }
- return value;
- }
- function parseFieldStringArrayValue(directive, field, evaluator) {
- if (!directive.has(field)) {
- return null;
- }
- // Resolve the field of interest from the directive metadata to a string[].
- const expression = directive.get(field);
- const value = evaluator.evaluate(expression);
- if (!isStringArrayOrDie(value, field, expression)) {
- throw createValueHasWrongTypeError(expression, value, `Failed to resolve @Directive.${field} to a string array`);
- }
- return value;
- }
- function isStringArrayOrDie(value, name, node) {
- if (!Array.isArray(value)) {
- return false;
- }
- for (let i = 0; i < value.length; i++) {
- if (typeof value[i] !== 'string') {
- throw createValueHasWrongTypeError(node, value[i], `Failed to resolve ${name} at position ${i} to a string`);
- }
- }
- return true;
- }
- function tryGetQueryFromFieldDecorator(member, reflector, evaluator, isCore) {
- const decorators = member.decorators;
- if (decorators === null) {
- return null;
- }
- const queryDecorators = getAngularDecorators(decorators, queryDecoratorNames, isCore);
- if (queryDecorators.length === 0) {
- return null;
- }
- if (queryDecorators.length !== 1) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_COLLISION, member.node ?? queryDecorators[0].node, 'Cannot combine multiple query decorators.');
- }
- const decorator = queryDecorators[0];
- const node = member.node || decorator.node;
- // Throw in case of `@Input() @ContentChild('foo') foo: any`, which is not supported in Ivy
- if (decorators.some((v) => v.name === 'Input')) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_COLLISION, node, 'Cannot combine @Input decorators with query decorators');
- }
- if (!isPropertyTypeMember(member)) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_UNEXPECTED, node, 'Query decorator must go on a property-type member');
- }
- // Either the decorator was aliased, or is referenced directly with
- // the proper query name.
- const name = (decorator.import?.name ?? decorator.name);
- return {
- name,
- decorator,
- metadata: extractDecoratorQueryMetadata(node, name, decorator.args || [], member.name, reflector, evaluator),
- };
- }
- function isPropertyTypeMember(member) {
- return (member.kind === exports.ClassMemberKind.Getter ||
- member.kind === exports.ClassMemberKind.Setter ||
- member.kind === exports.ClassMemberKind.Property);
- }
- function parseMappingStringArray(values) {
- return values.reduce((results, value) => {
- if (typeof value !== 'string') {
- throw new Error('Mapping value must be a string');
- }
- const [bindingPropertyName, fieldName] = parseMappingString(value);
- results[fieldName] = bindingPropertyName;
- return results;
- }, {});
- }
- function parseMappingString(value) {
- // Either the value is 'field' or 'field: property'. In the first case, `property` will
- // be undefined, in which case the field name should also be used as the property name.
- const [fieldName, bindingPropertyName] = value.split(':', 2).map((str) => str.trim());
- return [bindingPropertyName ?? fieldName, fieldName];
- }
- /** Parses the `inputs` array of a directive/component decorator. */
- function parseInputsArray(clazz, decoratorMetadata, evaluator, reflector, refEmitter, compilationMode) {
- const inputsField = decoratorMetadata.get('inputs');
- if (inputsField === undefined) {
- return {};
- }
- const inputs = {};
- const inputsArray = evaluator.evaluate(inputsField);
- if (!Array.isArray(inputsArray)) {
- throw createValueHasWrongTypeError(inputsField, inputsArray, `Failed to resolve @Directive.inputs to an array`);
- }
- for (let i = 0; i < inputsArray.length; i++) {
- const value = inputsArray[i];
- if (typeof value === 'string') {
- // If the value is a string, we treat it as a mapping string.
- const [bindingPropertyName, classPropertyName] = parseMappingString(value);
- inputs[classPropertyName] = {
- bindingPropertyName,
- classPropertyName,
- required: false,
- transform: null,
- // Note: Signal inputs are not allowed with the array form.
- isSignal: false,
- };
- }
- else if (value instanceof Map) {
- // If it's a map, we treat it as a config object.
- const name = value.get('name');
- const alias = value.get('alias');
- const required = value.get('required');
- let transform = null;
- if (typeof name !== 'string') {
- throw createValueHasWrongTypeError(inputsField, name, `Value at position ${i} of @Directive.inputs array must have a "name" property`);
- }
- if (value.has('transform')) {
- const transformValue = value.get('transform');
- if (!(transformValue instanceof DynamicValue) && !(transformValue instanceof Reference)) {
- throw createValueHasWrongTypeError(inputsField, transformValue, `Transform of value at position ${i} of @Directive.inputs array must be a function`);
- }
- transform = parseDecoratorInputTransformFunction(clazz, name, transformValue, reflector, refEmitter, compilationMode);
- }
- inputs[name] = {
- classPropertyName: name,
- bindingPropertyName: typeof alias === 'string' ? alias : name,
- required: required === true,
- // Note: Signal inputs are not allowed with the array form.
- isSignal: false,
- transform,
- };
- }
- else {
- throw createValueHasWrongTypeError(inputsField, value, `@Directive.inputs array can only contain strings or object literals`);
- }
- }
- return inputs;
- }
- /** Attempts to find a given Angular decorator on the class member. */
- function tryGetDecoratorOnMember(member, decoratorName, isCore) {
- if (member.decorators === null) {
- return null;
- }
- for (const decorator of member.decorators) {
- if (isAngularDecorator(decorator, decoratorName, isCore)) {
- return decorator;
- }
- }
- return null;
- }
- function tryParseInputFieldMapping(clazz, member, evaluator, reflector, importTracker, isCore, refEmitter, compilationMode) {
- const classPropertyName = member.name;
- const decorator = tryGetDecoratorOnMember(member, 'Input', isCore);
- const signalInputMapping = tryParseSignalInputMapping(member, reflector, importTracker);
- const modelInputMapping = tryParseSignalModelMapping(member, reflector, importTracker);
- if (decorator !== null && signalInputMapping !== null) {
- throw new FatalDiagnosticError(exports.ErrorCode.INITIALIZER_API_WITH_DISALLOWED_DECORATOR, decorator.node, `Using @Input with a signal input is not allowed.`);
- }
- if (decorator !== null && modelInputMapping !== null) {
- throw new FatalDiagnosticError(exports.ErrorCode.INITIALIZER_API_WITH_DISALLOWED_DECORATOR, decorator.node, `Using @Input with a model input is not allowed.`);
- }
- // Check `@Input` case.
- if (decorator !== null) {
- if (decorator.args !== null && decorator.args.length > 1) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARITY_WRONG, decorator.node, `@${decorator.name} can have at most one argument, got ${decorator.args.length} argument(s)`);
- }
- const optionsNode = decorator.args !== null && decorator.args.length === 1 ? decorator.args[0] : undefined;
- const options = optionsNode !== undefined ? evaluator.evaluate(optionsNode) : null;
- const required = options instanceof Map ? options.get('required') === true : false;
- // To preserve old behavior: Even though TypeScript types ensure proper options are
- // passed, we sanity check for unsupported values here again.
- if (options !== null && typeof options !== 'string' && !(options instanceof Map)) {
- throw createValueHasWrongTypeError(decorator.node, options, `@${decorator.name} decorator argument must resolve to a string or an object literal`);
- }
- let alias = null;
- if (typeof options === 'string') {
- alias = options;
- }
- else if (options instanceof Map && typeof options.get('alias') === 'string') {
- alias = options.get('alias');
- }
- const publicInputName = alias ?? classPropertyName;
- let transform = null;
- if (options instanceof Map && options.has('transform')) {
- const transformValue = options.get('transform');
- if (!(transformValue instanceof DynamicValue) && !(transformValue instanceof Reference)) {
- throw createValueHasWrongTypeError(optionsNode, transformValue, `Input transform must be a function`);
- }
- transform = parseDecoratorInputTransformFunction(clazz, classPropertyName, transformValue, reflector, refEmitter, compilationMode);
- }
- return {
- isSignal: false,
- classPropertyName,
- bindingPropertyName: publicInputName,
- transform,
- required,
- };
- }
- // Look for signal inputs. e.g. `memberName = input()`
- if (signalInputMapping !== null) {
- return signalInputMapping;
- }
- if (modelInputMapping !== null) {
- return modelInputMapping.input;
- }
- return null;
- }
- /** Parses the class members that declare inputs (via decorator or initializer). */
- function parseInputFields(clazz, members, evaluator, reflector, importTracker, refEmitter, isCore, compilationMode, inputsFromClassDecorator, classDecorator) {
- const inputs = {};
- for (const member of members) {
- const classPropertyName = member.name;
- const inputMapping = tryParseInputFieldMapping(clazz, member, evaluator, reflector, importTracker, isCore, refEmitter, compilationMode);
- if (inputMapping === null) {
- continue;
- }
- if (member.isStatic) {
- throw new FatalDiagnosticError(exports.ErrorCode.INCORRECTLY_DECLARED_ON_STATIC_MEMBER, member.node ?? clazz, `Input "${member.name}" is incorrectly declared as static member of "${clazz.name.text}".`);
- }
- // Validate that signal inputs are not accidentally declared in the `inputs` metadata.
- if (inputMapping.isSignal && inputsFromClassDecorator.hasOwnProperty(classPropertyName)) {
- throw new FatalDiagnosticError(exports.ErrorCode.INITIALIZER_API_DECORATOR_METADATA_COLLISION, member.node ?? clazz, `Input "${member.name}" is also declared as non-signal in @${classDecorator.name}.`);
- }
- inputs[classPropertyName] = inputMapping;
- }
- return inputs;
- }
- /**
- * Parses the `transform` function and its type for a decorator `@Input`.
- *
- * This logic verifies feasibility of extracting the transform write type
- * into a different place, so that the input write type can be captured at
- * a later point in a static acceptance member.
- *
- * Note: This is not needed for signal inputs where the transform type is
- * automatically captured in the type of the `InputSignal`.
- *
- */
- function parseDecoratorInputTransformFunction(clazz, classPropertyName, value, reflector, refEmitter, compilationMode) {
- // In local compilation mode we can skip type checking the function args. This is because usually
- // the type check is done in a separate build which runs in full compilation mode. So here we skip
- // all the diagnostics.
- if (compilationMode === exports.CompilationMode.LOCAL) {
- const node = value instanceof Reference ? value.getIdentityIn(clazz.getSourceFile()) : value.node;
- // This should never be null since we know the reference originates
- // from the same file, but we null check it just in case.
- if (node === null) {
- throw createValueHasWrongTypeError(value.node, value, 'Input transform function could not be referenced');
- }
- return {
- node,
- type: new Reference(ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
- };
- }
- const definition = reflector.getDefinitionOfFunction(value.node);
- if (definition === null) {
- throw createValueHasWrongTypeError(value.node, value, 'Input transform must be a function');
- }
- if (definition.typeParameters !== null && definition.typeParameters.length > 0) {
- throw createValueHasWrongTypeError(value.node, value, 'Input transform function cannot be generic');
- }
- if (definition.signatureCount > 1) {
- throw createValueHasWrongTypeError(value.node, value, 'Input transform function cannot have multiple signatures');
- }
- const members = reflector.getMembersOfClass(clazz);
- for (const member of members) {
- const conflictingName = `ngAcceptInputType_${classPropertyName}`;
- if (member.name === conflictingName && member.isStatic) {
- throw new FatalDiagnosticError(exports.ErrorCode.CONFLICTING_INPUT_TRANSFORM, value.node, `Class cannot have both a transform function on Input ${classPropertyName} and a static member called ${conflictingName}`);
- }
- }
- const node = value instanceof Reference ? value.getIdentityIn(clazz.getSourceFile()) : value.node;
- // This should never be null since we know the reference originates
- // from the same file, but we null check it just in case.
- if (node === null) {
- throw createValueHasWrongTypeError(value.node, value, 'Input transform function could not be referenced');
- }
- // Skip over `this` parameters since they're typing the context, not the actual parameter.
- // `this` parameters are guaranteed to be first if they exist, and the only to distinguish them
- // is using the name, TS doesn't have a special AST for them.
- const firstParam = definition.parameters[0]?.name === 'this' ? definition.parameters[1] : definition.parameters[0];
- // Treat functions with no arguments as `unknown` since returning
- // the same value from the transform function is valid.
- if (!firstParam) {
- return {
- node,
- type: new Reference(ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
- };
- }
- // This should be caught by `noImplicitAny` already, but null check it just in case.
- if (!firstParam.type) {
- throw createValueHasWrongTypeError(value.node, value, 'Input transform function first parameter must have a type');
- }
- if (firstParam.node.dotDotDotToken) {
- throw createValueHasWrongTypeError(value.node, value, 'Input transform function first parameter cannot be a spread parameter');
- }
- assertEmittableInputType(firstParam.type, clazz.getSourceFile(), reflector, refEmitter);
- const viaModule = value instanceof Reference ? value.bestGuessOwningModule : null;
- return { node, type: new Reference(firstParam.type, viaModule) };
- }
- /**
- * Verifies that a type and all types contained within
- * it can be referenced in a specific context file.
- */
- function assertEmittableInputType(type, contextFile, reflector, refEmitter) {
- (function walk(node) {
- if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) {
- const declaration = reflector.getDeclarationOfIdentifier(node.typeName);
- if (declaration !== null) {
- // If the type is declared in a different file, we have to check that it can be imported
- // into the context file. If they're in the same file, we need to verify that they're
- // exported, otherwise TS won't emit it to the .d.ts.
- if (declaration.node.getSourceFile() !== contextFile) {
- const emittedType = refEmitter.emit(new Reference(declaration.node, declaration.viaModule === AmbientImport ? AmbientImport : null), contextFile, exports.ImportFlags.NoAliasing |
- exports.ImportFlags.AllowTypeImports |
- exports.ImportFlags.AllowRelativeDtsImports |
- exports.ImportFlags.AllowAmbientReferences);
- assertSuccessfulReferenceEmit(emittedType, node, 'type');
- }
- else if (!reflector.isStaticallyExported(declaration.node)) {
- throw new FatalDiagnosticError(exports.ErrorCode.SYMBOL_NOT_EXPORTED, type, `Symbol must be exported in order to be used as the type of an Input transform function`, [makeRelatedInformation(declaration.node, `The symbol is declared here.`)]);
- }
- }
- }
- node.forEachChild(walk);
- })(type);
- }
- /**
- * Iterates through all specified class members and attempts to detect
- * view and content queries defined.
- *
- * Queries may be either defined via decorators, or through class member
- * initializers for signal-based queries.
- */
- function parseQueriesOfClassFields(members, reflector, importTracker, evaluator, isCore) {
- const viewQueries = [];
- const contentQueries = [];
- // For backwards compatibility, decorator-based queries are grouped and
- // ordered in a specific way. The order needs to match with what we had in:
- // https://github.com/angular/angular/blob/8737544d6963bf664f752de273e919575cca08ac/packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts#L94-L111.
- const decoratorViewChild = [];
- const decoratorViewChildren = [];
- const decoratorContentChild = [];
- const decoratorContentChildren = [];
- for (const member of members) {
- const decoratorQuery = tryGetQueryFromFieldDecorator(member, reflector, evaluator, isCore);
- const signalQuery = tryParseSignalQueryFromInitializer(member, reflector, importTracker);
- if (decoratorQuery !== null && signalQuery !== null) {
- throw new FatalDiagnosticError(exports.ErrorCode.INITIALIZER_API_WITH_DISALLOWED_DECORATOR, decoratorQuery.decorator.node, `Using @${decoratorQuery.name} with a signal-based query is not allowed.`);
- }
- const queryNode = decoratorQuery?.decorator.node ?? signalQuery?.call;
- if (queryNode !== undefined && member.isStatic) {
- throw new FatalDiagnosticError(exports.ErrorCode.INCORRECTLY_DECLARED_ON_STATIC_MEMBER, queryNode, `Query is incorrectly declared on a static class member.`);
- }
- if (decoratorQuery !== null) {
- switch (decoratorQuery.name) {
- case 'ViewChild':
- decoratorViewChild.push(decoratorQuery.metadata);
- break;
- case 'ViewChildren':
- decoratorViewChildren.push(decoratorQuery.metadata);
- break;
- case 'ContentChild':
- decoratorContentChild.push(decoratorQuery.metadata);
- break;
- case 'ContentChildren':
- decoratorContentChildren.push(decoratorQuery.metadata);
- break;
- }
- }
- else if (signalQuery !== null) {
- switch (signalQuery.name) {
- case 'viewChild':
- case 'viewChildren':
- viewQueries.push(signalQuery.metadata);
- break;
- case 'contentChild':
- case 'contentChildren':
- contentQueries.push(signalQuery.metadata);
- break;
- }
- }
- }
- return {
- viewQueries: [...viewQueries, ...decoratorViewChild, ...decoratorViewChildren],
- contentQueries: [...contentQueries, ...decoratorContentChild, ...decoratorContentChildren],
- };
- }
- /** Parses the `outputs` array of a directive/component. */
- function parseOutputsArray(directive, evaluator) {
- const metaValues = parseFieldStringArrayValue(directive, 'outputs', evaluator);
- return metaValues ? parseMappingStringArray(metaValues) : EMPTY_OBJECT;
- }
- /** Parses the class members that are outputs. */
- function parseOutputFields(clazz, classDecorator, members, isCore, reflector, importTracker, evaluator, outputsFromMeta) {
- const outputs = {};
- for (const member of members) {
- const decoratorOutput = tryParseDecoratorOutput(member, evaluator, isCore);
- const initializerOutput = tryParseInitializerBasedOutput(member, reflector, importTracker);
- const modelMapping = tryParseSignalModelMapping(member, reflector, importTracker);
- if (decoratorOutput !== null && initializerOutput !== null) {
- throw new FatalDiagnosticError(exports.ErrorCode.INITIALIZER_API_WITH_DISALLOWED_DECORATOR, decoratorOutput.decorator.node, `Using "@Output" with "output()" is not allowed.`);
- }
- if (decoratorOutput !== null && modelMapping !== null) {
- throw new FatalDiagnosticError(exports.ErrorCode.INITIALIZER_API_WITH_DISALLOWED_DECORATOR, decoratorOutput.decorator.node, `Using @Output with a model input is not allowed.`);
- }
- const queryNode = decoratorOutput?.decorator.node ?? initializerOutput?.call ?? modelMapping?.call;
- if (queryNode !== undefined && member.isStatic) {
- throw new FatalDiagnosticError(exports.ErrorCode.INCORRECTLY_DECLARED_ON_STATIC_MEMBER, queryNode, `Output is incorrectly declared on a static class member.`);
- }
- let bindingPropertyName;
- if (decoratorOutput !== null) {
- bindingPropertyName = decoratorOutput.metadata.bindingPropertyName;
- }
- else if (initializerOutput !== null) {
- bindingPropertyName = initializerOutput.metadata.bindingPropertyName;
- }
- else if (modelMapping !== null) {
- bindingPropertyName = modelMapping.output.bindingPropertyName;
- }
- else {
- continue;
- }
- // Validate that initializer-based outputs are not accidentally declared
- // in the `outputs` class metadata.
- if ((initializerOutput !== null || modelMapping !== null) &&
- outputsFromMeta.hasOwnProperty(member.name)) {
- throw new FatalDiagnosticError(exports.ErrorCode.INITIALIZER_API_DECORATOR_METADATA_COLLISION, member.node ?? clazz, `Output "${member.name}" is unexpectedly declared in @${classDecorator.name} as well.`);
- }
- outputs[member.name] = bindingPropertyName;
- }
- return outputs;
- }
- /** Attempts to parse a decorator-based @Output. */
- function tryParseDecoratorOutput(member, evaluator, isCore) {
- const decorator = tryGetDecoratorOnMember(member, 'Output', isCore);
- if (decorator === null) {
- return null;
- }
- if (decorator.args !== null && decorator.args.length > 1) {
- throw new FatalDiagnosticError(exports.ErrorCode.DECORATOR_ARITY_WRONG, decorator.node, `@Output can have at most one argument, got ${decorator.args.length} argument(s)`);
- }
- const classPropertyName = member.name;
- let alias = null;
- if (decorator.args?.length === 1) {
- const resolvedAlias = evaluator.evaluate(decorator.args[0]);
- if (typeof resolvedAlias !== 'string') {
- throw createValueHasWrongTypeError(decorator.node, resolvedAlias, `@Output decorator argument must resolve to a string`);
- }
- alias = resolvedAlias;
- }
- return {
- decorator,
- metadata: {
- isSignal: false,
- classPropertyName,
- bindingPropertyName: alias ?? classPropertyName,
- },
- };
- }
- function evaluateHostExpressionBindings(hostExpr, evaluator) {
- const hostMetaMap = evaluator.evaluate(hostExpr);
- if (!(hostMetaMap instanceof Map)) {
- throw createValueHasWrongTypeError(hostExpr, hostMetaMap, `Decorator host metadata must be an object`);
- }
- const hostMetadata = {};
- hostMetaMap.forEach((value, key) => {
- // Resolve Enum references to their declared value.
- if (value instanceof EnumValue) {
- value = value.resolved;
- }
- if (typeof key !== 'string') {
- throw createValueHasWrongTypeError(hostExpr, key, `Decorator host metadata must be a string -> string object, but found unparseable key`);
- }
- if (typeof value == 'string') {
- hostMetadata[key] = value;
- }
- else if (value instanceof DynamicValue) {
- hostMetadata[key] = new WrappedNodeExpr(value.node);
- }
- else {
- throw createValueHasWrongTypeError(hostExpr, value, `Decorator host metadata must be a string -> string object, but found unparseable value`);
- }
- });
- const bindings = parseHostBindings(hostMetadata);
- const errors = verifyHostBindings(bindings, createSourceSpan(hostExpr));
- if (errors.length > 0) {
- throw new FatalDiagnosticError(exports.ErrorCode.HOST_BINDING_PARSE_ERROR, getHostBindingErrorNode(errors[0], hostExpr), errors.map((error) => error.msg).join('\n'));
- }
- return bindings;
- }
- /**
- * Attempts to match a parser error to the host binding expression that caused it.
- * @param error Error to match.
- * @param hostExpr Expression declaring the host bindings.
- */
- function getHostBindingErrorNode(error, hostExpr) {
- // In the most common case the `host` object is an object literal with string values. We can
- // confidently match the error to its expression by looking at the string value that the parser
- // failed to parse and the initializers for each of the properties. If we fail to match, we fall
- // back to the old behavior where the error is reported on the entire `host` object.
- if (ts.isObjectLiteralExpression(hostExpr) && error.relatedError instanceof ParserError) {
- for (const prop of hostExpr.properties) {
- if (ts.isPropertyAssignment(prop) &&
- ts.isStringLiteralLike(prop.initializer) &&
- prop.initializer.text === error.relatedError.input) {
- return prop.initializer;
- }
- }
- }
- return hostExpr;
- }
- /**
- * Extracts and prepares the host directives metadata from an array literal expression.
- * @param rawHostDirectives Expression that defined the `hostDirectives`.
- */
- function extractHostDirectives(rawHostDirectives, evaluator, compilationMode, forwardRefResolver) {
- const resolved = evaluator.evaluate(rawHostDirectives, forwardRefResolver);
- if (!Array.isArray(resolved)) {
- throw createValueHasWrongTypeError(rawHostDirectives, resolved, 'hostDirectives must be an array');
- }
- return resolved.map((value) => {
- const hostReference = value instanceof Map ? value.get('directive') : value;
- // Diagnostics
- if (compilationMode !== exports.CompilationMode.LOCAL) {
- if (!(hostReference instanceof Reference)) {
- throw createValueHasWrongTypeError(rawHostDirectives, hostReference, 'Host directive must be a reference');
- }
- if (!isNamedClassDeclaration(hostReference.node)) {
- throw createValueHasWrongTypeError(rawHostDirectives, hostReference, 'Host directive reference must be a class');
- }
- }
- let directive;
- let nameForErrors = (fieldName) => '@Directive.hostDirectives';
- if (compilationMode === exports.CompilationMode.LOCAL && hostReference instanceof DynamicValue) {
- // At the moment in local compilation we only support simple array for host directives, i.e.,
- // an array consisting of the directive identifiers. We don't support forward refs or other
- // expressions applied on externally imported directives. The main reason is simplicity, and
- // that almost nobody wants to use host directives this way (e.g., what would be the point of
- // forward ref for imported symbols?!)
- if (!ts.isIdentifier(hostReference.node) &&
- !ts.isPropertyAccessExpression(hostReference.node)) {
- throw new FatalDiagnosticError(exports.ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION, hostReference.node, `In local compilation mode, host directive cannot be an expression. Use an identifier instead`);
- }
- directive = new WrappedNodeExpr(hostReference.node);
- }
- else if (hostReference instanceof Reference) {
- directive = hostReference;
- nameForErrors = (fieldName) => `@Directive.hostDirectives.${directive.node.name.text}.${fieldName}`;
- }
- else {
- throw new Error('Impossible state');
- }
- const meta = {
- directive,
- isForwardReference: hostReference instanceof Reference && hostReference.synthetic,
- inputs: parseHostDirectivesMapping('inputs', value, nameForErrors('input'), rawHostDirectives),
- outputs: parseHostDirectivesMapping('outputs', value, nameForErrors('output'), rawHostDirectives),
- };
- return meta;
- });
- }
- /**
- * Parses the expression that defines the `inputs` or `outputs` of a host directive.
- * @param field Name of the field that is being parsed.
- * @param resolvedValue Evaluated value of the expression that defined the field.
- * @param classReference Reference to the host directive class.
- * @param sourceExpression Expression that the host directive is referenced in.
- */
- function parseHostDirectivesMapping(field, resolvedValue, nameForErrors, sourceExpression) {
- if (resolvedValue instanceof Map && resolvedValue.has(field)) {
- const rawInputs = resolvedValue.get(field);
- if (isStringArrayOrDie(rawInputs, nameForErrors, sourceExpression)) {
- return parseMappingStringArray(rawInputs);
- }
- }
- return null;
- }
- /** Converts the parsed host directive information into metadata. */
- function toHostDirectiveMetadata(hostDirective, context, refEmitter) {
- let directive;
- if (hostDirective.directive instanceof Reference) {
- directive = toR3Reference(hostDirective.directive.node, hostDirective.directive, context, refEmitter);
- }
- else {
- directive = {
- value: hostDirective.directive,
- type: hostDirective.directive,
- };
- }
- return {
- directive,
- isForwardReference: hostDirective.isForwardReference,
- inputs: hostDirective.inputs || null,
- outputs: hostDirective.outputs || null,
- };
- }
- /** Converts the parsed input information into metadata. */
- function toR3InputMetadata(mapping) {
- return {
- classPropertyName: mapping.classPropertyName,
- bindingPropertyName: mapping.bindingPropertyName,
- required: mapping.required,
- transformFunction: mapping.transform !== null ? new WrappedNodeExpr(mapping.transform.node) : null,
- isSignal: mapping.isSignal,
- };
- }
- const NgOriginalFile = Symbol('NgOriginalFile');
- exports.UpdateMode = void 0;
- (function (UpdateMode) {
- /**
- * A complete update creates a completely new overlay of type-checking code on top of the user's
- * original program, which doesn't include type-checking code from previous calls to
- * `updateFiles`.
- */
- UpdateMode[UpdateMode["Complete"] = 0] = "Complete";
- /**
- * An incremental update changes the contents of some files in the type-checking program without
- * reverting any prior changes.
- */
- UpdateMode[UpdateMode["Incremental"] = 1] = "Incremental";
- })(exports.UpdateMode || (exports.UpdateMode = {}));
- /**
- * A `Symbol` which is used to patch extension data onto `ts.SourceFile`s.
- */
- const NgExtension = Symbol('NgExtension');
- /**
- * Narrows a `ts.SourceFile` if it has an `NgExtension` property.
- */
- function isExtended(sf) {
- return sf[NgExtension] !== undefined;
- }
- /**
- * Returns the `NgExtensionData` for a given `ts.SourceFile`, adding it if none exists.
- */
- function sfExtensionData(sf) {
- const extSf = sf;
- if (extSf[NgExtension] !== undefined) {
- // The file already has extension data, so return it directly.
- return extSf[NgExtension];
- }
- // The file has no existing extension data, so add it and return it.
- const extension = {
- isTopLevelShim: false,
- fileShim: null,
- originalReferencedFiles: null,
- taggedReferenceFiles: null,
- };
- extSf[NgExtension] = extension;
- return extension;
- }
- /**
- * Check whether `sf` is a per-file shim `ts.SourceFile`.
- */
- function isFileShimSourceFile(sf) {
- return isExtended(sf) && sf[NgExtension].fileShim !== null;
- }
- /**
- * Check whether `sf` is a shim `ts.SourceFile` (either a per-file shim or a top-level shim).
- */
- function isShim(sf) {
- return isExtended(sf) && (sf[NgExtension].fileShim !== null || sf[NgExtension].isTopLevelShim);
- }
- /**
- * Copy any shim data from one `ts.SourceFile` to another.
- */
- function copyFileShimData(from, to) {
- if (!isFileShimSourceFile(from)) {
- return;
- }
- sfExtensionData(to).fileShim = sfExtensionData(from).fileShim;
- }
- /**
- * For those `ts.SourceFile`s in the `program` which have previously been tagged by a
- * `ShimReferenceTagger`, restore the original `referencedFiles` array that does not have shim tags.
- */
- function untagAllTsFiles(program) {
- for (const sf of program.getSourceFiles()) {
- untagTsFile(sf);
- }
- }
- /**
- * For those `ts.SourceFile`s in the `program` which have previously been tagged by a
- * `ShimReferenceTagger`, re-apply the effects of tagging by updating the `referencedFiles` array to
- * the tagged version produced previously.
- */
- function retagAllTsFiles(program) {
- for (const sf of program.getSourceFiles()) {
- retagTsFile(sf);
- }
- }
- /**
- * Restore the original `referencedFiles` for the given `ts.SourceFile`.
- */
- function untagTsFile(sf) {
- if (sf.isDeclarationFile || !isExtended(sf)) {
- return;
- }
- const ext = sfExtensionData(sf);
- if (ext.originalReferencedFiles !== null) {
- sf.referencedFiles = ext.originalReferencedFiles;
- }
- }
- /**
- * Apply the previously tagged `referencedFiles` to the given `ts.SourceFile`, if it was previously
- * tagged.
- */
- function retagTsFile(sf) {
- if (sf.isDeclarationFile || !isExtended(sf)) {
- return;
- }
- const ext = sfExtensionData(sf);
- if (ext.taggedReferenceFiles !== null) {
- sf.referencedFiles = ext.taggedReferenceFiles;
- }
- }
- /**
- * Describes the scope of the caller's interest in template type-checking results.
- */
- exports.OptimizeFor = void 0;
- (function (OptimizeFor) {
- /**
- * Indicates that a consumer of a `TemplateTypeChecker` is only interested in results for a
- * given file, and wants them as fast as possible.
- *
- * Calling `TemplateTypeChecker` methods successively for multiple files while specifying
- * `OptimizeFor.SingleFile` can result in significant unnecessary overhead overall.
- */
- OptimizeFor[OptimizeFor["SingleFile"] = 0] = "SingleFile";
- /**
- * Indicates that a consumer of a `TemplateTypeChecker` intends to query for results pertaining
- * to the entire user program, and so the type-checker should internally optimize for this case.
- *
- * Initial calls to retrieve type-checking information may take longer, but repeated calls to
- * gather information for the whole user program will be significantly faster with this mode of
- * optimization.
- */
- OptimizeFor[OptimizeFor["WholeProgram"] = 1] = "WholeProgram";
- })(exports.OptimizeFor || (exports.OptimizeFor = {}));
- /**
- * Discriminant of an autocompletion source (a `Completion`).
- */
- var CompletionKind;
- (function (CompletionKind) {
- CompletionKind[CompletionKind["Reference"] = 0] = "Reference";
- CompletionKind[CompletionKind["Variable"] = 1] = "Variable";
- CompletionKind[CompletionKind["LetDeclaration"] = 2] = "LetDeclaration";
- })(CompletionKind || (CompletionKind = {}));
- /**
- * Which kind of Angular Trait the import targets.
- */
- exports.PotentialImportKind = void 0;
- (function (PotentialImportKind) {
- PotentialImportKind[PotentialImportKind["NgModule"] = 0] = "NgModule";
- PotentialImportKind[PotentialImportKind["Standalone"] = 1] = "Standalone";
- })(exports.PotentialImportKind || (exports.PotentialImportKind = {}));
- /**
- * Possible modes in which to look up a potential import.
- */
- exports.PotentialImportMode = void 0;
- (function (PotentialImportMode) {
- /** Whether an import is standalone is inferred based on its metadata. */
- PotentialImportMode[PotentialImportMode["Normal"] = 0] = "Normal";
- /**
- * An import is assumed to be standalone and is imported directly. This is useful for migrations
- * where a declaration wasn't standalone when the program was created, but will become standalone
- * as a part of the migration.
- */
- PotentialImportMode[PotentialImportMode["ForceDirect"] = 1] = "ForceDirect";
- })(exports.PotentialImportMode || (exports.PotentialImportMode = {}));
- exports.SymbolKind = void 0;
- (function (SymbolKind) {
- SymbolKind[SymbolKind["Input"] = 0] = "Input";
- SymbolKind[SymbolKind["Output"] = 1] = "Output";
- SymbolKind[SymbolKind["Binding"] = 2] = "Binding";
- SymbolKind[SymbolKind["Reference"] = 3] = "Reference";
- SymbolKind[SymbolKind["Variable"] = 4] = "Variable";
- SymbolKind[SymbolKind["Directive"] = 5] = "Directive";
- SymbolKind[SymbolKind["Element"] = 6] = "Element";
- SymbolKind[SymbolKind["Template"] = 7] = "Template";
- SymbolKind[SymbolKind["Expression"] = 8] = "Expression";
- SymbolKind[SymbolKind["DomBinding"] = 9] = "DomBinding";
- SymbolKind[SymbolKind["Pipe"] = 10] = "Pipe";
- SymbolKind[SymbolKind["LetDeclaration"] = 11] = "LetDeclaration";
- })(exports.SymbolKind || (exports.SymbolKind = {}));
- /**
- * Constructs a `ts.Diagnostic` for a given `ParseSourceSpan` within a template.
- */
- function makeTemplateDiagnostic(id, mapping, span, category, code, messageText, relatedMessages) {
- if (mapping.type === 'direct') {
- let relatedInformation = undefined;
- if (relatedMessages !== undefined) {
- relatedInformation = [];
- for (const relatedMessage of relatedMessages) {
- relatedInformation.push({
- category: ts.DiagnosticCategory.Message,
- code: 0,
- file: relatedMessage.sourceFile,
- start: relatedMessage.start,
- length: relatedMessage.end - relatedMessage.start,
- messageText: relatedMessage.text,
- });
- }
- }
- // For direct mappings, the error is shown inline as ngtsc was able to pinpoint a string
- // constant within the `@Component` decorator for the template. This allows us to map the error
- // directly into the bytes of the source file.
- return {
- source: 'ngtsc',
- code,
- category,
- messageText,
- file: mapping.node.getSourceFile(),
- sourceFile: mapping.node.getSourceFile(),
- typeCheckId: id,
- start: span.start.offset,
- length: span.end.offset - span.start.offset,
- relatedInformation,
- };
- }
- else if (mapping.type === 'indirect' || mapping.type === 'external') {
- // For indirect mappings (template was declared inline, but ngtsc couldn't map it directly
- // to a string constant in the decorator), the component's file name is given with a suffix
- // indicating it's not the TS file being displayed, but a template.
- // For external temoplates, the HTML filename is used.
- const componentSf = mapping.componentClass.getSourceFile();
- const componentName = mapping.componentClass.name.text;
- const fileName = mapping.type === 'indirect'
- ? `${componentSf.fileName} (${componentName} template)`
- : mapping.templateUrl;
- let relatedInformation = [];
- if (relatedMessages !== undefined) {
- for (const relatedMessage of relatedMessages) {
- relatedInformation.push({
- category: ts.DiagnosticCategory.Message,
- code: 0,
- file: relatedMessage.sourceFile,
- start: relatedMessage.start,
- length: relatedMessage.end - relatedMessage.start,
- messageText: relatedMessage.text,
- });
- }
- }
- let sf;
- try {
- sf = getParsedTemplateSourceFile(fileName, mapping);
- }
- catch (e) {
- const failureChain = makeDiagnosticChain(`Failed to report an error in '${fileName}' at ${span.start.line + 1}:${span.start.col + 1}`, [makeDiagnosticChain(e?.stack ?? `${e}`)]);
- return {
- source: 'ngtsc',
- category,
- code,
- messageText: addDiagnosticChain(messageText, [failureChain]),
- file: componentSf,
- sourceFile: componentSf,
- typeCheckId: id,
- // mapping.node represents either the 'template' or 'templateUrl' expression. getStart()
- // and getEnd() are used because they don't include surrounding whitespace.
- start: mapping.node.getStart(),
- length: mapping.node.getEnd() - mapping.node.getStart(),
- relatedInformation,
- };
- }
- relatedInformation.push({
- category: ts.DiagnosticCategory.Message,
- code: 0,
- file: componentSf,
- // mapping.node represents either the 'template' or 'templateUrl' expression. getStart()
- // and getEnd() are used because they don't include surrounding whitespace.
- start: mapping.node.getStart(),
- length: mapping.node.getEnd() - mapping.node.getStart(),
- messageText: `Error occurs in the template of component ${componentName}.`,
- });
- return {
- source: 'ngtsc',
- category,
- code,
- messageText,
- file: sf,
- sourceFile: componentSf,
- typeCheckId: id,
- start: span.start.offset,
- length: span.end.offset - span.start.offset,
- // Show a secondary message indicating the component whose template contains the error.
- relatedInformation,
- };
- }
- else {
- throw new Error(`Unexpected source mapping type: ${mapping.type}`);
- }
- }
- const TemplateSourceFile = Symbol('TemplateSourceFile');
- function getParsedTemplateSourceFile(fileName, mapping) {
- if (mapping[TemplateSourceFile] === undefined) {
- mapping[TemplateSourceFile] = parseTemplateAsSourceFile(fileName, mapping.template);
- }
- return mapping[TemplateSourceFile];
- }
- function parseTemplateAsSourceFile(fileName, template) {
- // TODO(alxhub): investigate creating a fake `ts.SourceFile` here instead of invoking the TS
- // parser against the template (HTML is just really syntactically invalid TypeScript code ;).
- return ts.createSourceFile(fileName, template, ts.ScriptTarget.Latest,
- /* setParentNodes */ false, ts.ScriptKind.JSX);
- }
- const TYPE_CHECK_ID_MAP = Symbol('TypeCheckId');
- function getTypeCheckId(clazz) {
- const sf = clazz.getSourceFile();
- if (sf[TYPE_CHECK_ID_MAP] === undefined) {
- sf[TYPE_CHECK_ID_MAP] = new Map();
- }
- if (sf[TYPE_CHECK_ID_MAP].get(clazz) === undefined) {
- sf[TYPE_CHECK_ID_MAP].set(clazz, `tcb${sf[TYPE_CHECK_ID_MAP].size + 1}`);
- }
- return sf[TYPE_CHECK_ID_MAP].get(clazz);
- }
- const parseSpanComment = /^(\d+),(\d+)$/;
- /**
- * Reads the trailing comments and finds the first match which is a span comment (i.e. 4,10) on a
- * node and returns it as an `AbsoluteSourceSpan`.
- *
- * Will return `null` if no trailing comments on the node match the expected form of a source span.
- */
- function readSpanComment(node, sourceFile = node.getSourceFile()) {
- return (ts.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
- if (kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
- return null;
- }
- const commentText = sourceFile.text.substring(pos + 2, end - 2);
- const match = commentText.match(parseSpanComment);
- if (match === null) {
- return null;
- }
- return new AbsoluteSourceSpan(+match[1], +match[2]);
- }) || null);
- }
- /** Used to identify what type the comment is. */
- var CommentTriviaType;
- (function (CommentTriviaType) {
- CommentTriviaType["DIAGNOSTIC"] = "D";
- CommentTriviaType["EXPRESSION_TYPE_IDENTIFIER"] = "T";
- })(CommentTriviaType || (CommentTriviaType = {}));
- /** Identifies what the TCB expression is for (for example, a directive declaration). */
- var ExpressionIdentifier;
- (function (ExpressionIdentifier) {
- ExpressionIdentifier["DIRECTIVE"] = "DIR";
- ExpressionIdentifier["COMPONENT_COMPLETION"] = "COMPCOMP";
- ExpressionIdentifier["EVENT_PARAMETER"] = "EP";
- ExpressionIdentifier["VARIABLE_AS_EXPRESSION"] = "VAE";
- })(ExpressionIdentifier || (ExpressionIdentifier = {}));
- /** Tags the node with the given expression identifier. */
- function addExpressionIdentifier(node, identifier) {
- ts.addSyntheticTrailingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, `${CommentTriviaType.EXPRESSION_TYPE_IDENTIFIER}:${identifier}`,
- /* hasTrailingNewLine */ false);
- }
- const IGNORE_FOR_DIAGNOSTICS_MARKER = `${CommentTriviaType.DIAGNOSTIC}:ignore`;
- /**
- * Tag the `ts.Node` with an indication that any errors arising from the evaluation of the node
- * should be ignored.
- */
- function markIgnoreDiagnostics(node) {
- ts.addSyntheticTrailingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, IGNORE_FOR_DIAGNOSTICS_MARKER,
- /* hasTrailingNewLine */ false);
- }
- /** Returns true if the node has a marker that indicates diagnostics errors should be ignored. */
- function hasIgnoreForDiagnosticsMarker(node, sourceFile) {
- return (ts.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
- if (kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
- return null;
- }
- const commentText = sourceFile.text.substring(pos + 2, end - 2);
- return commentText === IGNORE_FOR_DIAGNOSTICS_MARKER;
- }) === true);
- }
- function makeRecursiveVisitor(visitor) {
- function recursiveVisitor(node) {
- const res = visitor(node);
- return res !== null ? res : node.forEachChild(recursiveVisitor);
- }
- return recursiveVisitor;
- }
- function getSpanFromOptions(opts) {
- let withSpan = null;
- if (opts.withSpan !== undefined) {
- if (opts.withSpan instanceof AbsoluteSourceSpan) {
- withSpan = opts.withSpan;
- }
- else {
- withSpan = { start: opts.withSpan.start.offset, end: opts.withSpan.end.offset };
- }
- }
- return withSpan;
- }
- /**
- * Given a `ts.Node` with finds the first node whose matching the criteria specified
- * by the `FindOptions`.
- *
- * Returns `null` when no `ts.Node` matches the given conditions.
- */
- function findFirstMatchingNode(tcb, opts) {
- const withSpan = getSpanFromOptions(opts);
- const withExpressionIdentifier = opts.withExpressionIdentifier;
- const sf = tcb.getSourceFile();
- const visitor = makeRecursiveVisitor((node) => {
- if (!opts.filter(node)) {
- return null;
- }
- if (withSpan !== null) {
- const comment = readSpanComment(node, sf);
- if (comment === null || withSpan.start !== comment.start || withSpan.end !== comment.end) {
- return null;
- }
- }
- if (withExpressionIdentifier !== undefined &&
- !hasExpressionIdentifier(sf, node, withExpressionIdentifier)) {
- return null;
- }
- return node;
- });
- return tcb.forEachChild(visitor) ?? null;
- }
- /**
- * Given a `ts.Node` with source span comments, finds the first node whose source span comment
- * matches the given `sourceSpan`. Additionally, the `filter` function allows matching only
- * `ts.Nodes` of a given type, which provides the ability to select only matches of a given type
- * when there may be more than one.
- *
- * Returns `null` when no `ts.Node` matches the given conditions.
- */
- function findAllMatchingNodes(tcb, opts) {
- const withSpan = getSpanFromOptions(opts);
- const withExpressionIdentifier = opts.withExpressionIdentifier;
- const results = [];
- const stack = [tcb];
- const sf = tcb.getSourceFile();
- while (stack.length > 0) {
- const node = stack.pop();
- if (!opts.filter(node)) {
- stack.push(...node.getChildren());
- continue;
- }
- if (withSpan !== null) {
- const comment = readSpanComment(node, sf);
- if (comment === null || withSpan.start !== comment.start || withSpan.end !== comment.end) {
- stack.push(...node.getChildren());
- continue;
- }
- }
- if (withExpressionIdentifier !== undefined &&
- !hasExpressionIdentifier(sf, node, withExpressionIdentifier)) {
- continue;
- }
- results.push(node);
- }
- return results;
- }
- function hasExpressionIdentifier(sourceFile, node, identifier) {
- return (ts.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
- if (kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
- return false;
- }
- const commentText = sourceFile.text.substring(pos + 2, end - 2);
- return commentText === `${CommentTriviaType.EXPRESSION_TYPE_IDENTIFIER}:${identifier}`;
- }) || false);
- }
- /**
- * Powers autocompletion for a specific component.
- *
- * Internally caches autocompletion results, and must be discarded if the component template or
- * surrounding TS program have changed.
- */
- class CompletionEngine {
- tcb;
- data;
- tcbPath;
- tcbIsShim;
- componentContext;
- /**
- * Cache of completions for various levels of the template, including the root template (`null`).
- * Memoizes `getTemplateContextCompletions`.
- */
- templateContextCache = new Map();
- expressionCompletionCache = new Map();
- constructor(tcb, data, tcbPath, tcbIsShim) {
- this.tcb = tcb;
- this.data = data;
- this.tcbPath = tcbPath;
- this.tcbIsShim = tcbIsShim;
- // Find the component completion expression within the TCB. This looks like: `ctx. /* ... */;`
- const globalRead = findFirstMatchingNode(this.tcb, {
- filter: ts.isPropertyAccessExpression,
- withExpressionIdentifier: ExpressionIdentifier.COMPONENT_COMPLETION,
- });
- if (globalRead !== null) {
- this.componentContext = {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- // `globalRead.name` is an empty `ts.Identifier`, so its start position immediately follows
- // the `.` in `ctx.`. TS autocompletion APIs can then be used to access completion results
- // for the component context.
- positionInFile: globalRead.name.getStart(),
- };
- }
- else {
- this.componentContext = null;
- }
- }
- /**
- * Get global completions within the given template context and AST node.
- *
- * @param context the given template context - either a `TmplAstTemplate` embedded view, or `null`
- * for the root
- * template context.
- * @param node the given AST node
- */
- getGlobalCompletions(context, node) {
- if (this.componentContext === null) {
- return null;
- }
- const templateContext = this.getTemplateContextCompletions(context);
- if (templateContext === null) {
- return null;
- }
- let nodeContext = null;
- if (node instanceof EmptyExpr$1) {
- const nodeLocation = findFirstMatchingNode(this.tcb, {
- filter: ts.isIdentifier,
- withSpan: node.sourceSpan,
- });
- if (nodeLocation !== null) {
- nodeContext = {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- positionInFile: nodeLocation.getStart(),
- };
- }
- }
- if (node instanceof PropertyRead && node.receiver instanceof ImplicitReceiver) {
- const nodeLocation = findFirstMatchingNode(this.tcb, {
- filter: ts.isPropertyAccessExpression,
- withSpan: node.sourceSpan,
- });
- if (nodeLocation) {
- nodeContext = {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- positionInFile: nodeLocation.getStart(),
- };
- }
- }
- return {
- componentContext: this.componentContext,
- templateContext,
- nodeContext,
- };
- }
- getExpressionCompletionLocation(expr) {
- if (this.expressionCompletionCache.has(expr)) {
- return this.expressionCompletionCache.get(expr);
- }
- // Completion works inside property reads and method calls.
- let tsExpr = null;
- if (expr instanceof PropertyRead || expr instanceof PropertyWrite) {
- // Non-safe navigation operations are trivial: `foo.bar` or `foo.bar()`
- tsExpr = findFirstMatchingNode(this.tcb, {
- filter: ts.isPropertyAccessExpression,
- withSpan: expr.nameSpan,
- });
- }
- else if (expr instanceof SafePropertyRead) {
- // Safe navigation operations are a little more complex, and involve a ternary. Completion
- // happens in the "true" case of the ternary.
- const ternaryExpr = findFirstMatchingNode(this.tcb, {
- filter: ts.isParenthesizedExpression,
- withSpan: expr.sourceSpan,
- });
- if (ternaryExpr === null || !ts.isConditionalExpression(ternaryExpr.expression)) {
- return null;
- }
- const whenTrue = ternaryExpr.expression.whenTrue;
- if (ts.isPropertyAccessExpression(whenTrue)) {
- tsExpr = whenTrue;
- }
- else if (ts.isCallExpression(whenTrue) &&
- ts.isPropertyAccessExpression(whenTrue.expression)) {
- tsExpr = whenTrue.expression;
- }
- }
- if (tsExpr === null) {
- return null;
- }
- const res = {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- positionInFile: tsExpr.name.getEnd(),
- };
- this.expressionCompletionCache.set(expr, res);
- return res;
- }
- getLiteralCompletionLocation(expr) {
- if (this.expressionCompletionCache.has(expr)) {
- return this.expressionCompletionCache.get(expr);
- }
- let tsExpr = null;
- if (expr instanceof TextAttribute) {
- const strNode = findFirstMatchingNode(this.tcb, {
- filter: ts.isParenthesizedExpression,
- withSpan: expr.sourceSpan,
- });
- if (strNode !== null && ts.isStringLiteral(strNode.expression)) {
- tsExpr = strNode.expression;
- }
- }
- else {
- tsExpr = findFirstMatchingNode(this.tcb, {
- filter: (n) => ts.isStringLiteral(n) || ts.isNumericLiteral(n),
- withSpan: expr.sourceSpan,
- });
- }
- if (tsExpr === null) {
- return null;
- }
- let positionInShimFile = tsExpr.getEnd();
- if (ts.isStringLiteral(tsExpr)) {
- // In the shimFile, if `tsExpr` is a string, the position should be in the quotes.
- positionInShimFile -= 1;
- }
- const res = {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- positionInFile: positionInShimFile,
- };
- this.expressionCompletionCache.set(expr, res);
- return res;
- }
- /**
- * Get global completions within the given template context - either a `TmplAstTemplate` embedded
- * view, or `null` for the root context.
- */
- getTemplateContextCompletions(context) {
- if (this.templateContextCache.has(context)) {
- return this.templateContextCache.get(context);
- }
- const templateContext = new Map();
- // The bound template already has details about the references and variables in scope in the
- // `context` template - they just need to be converted to `Completion`s.
- for (const node of this.data.boundTarget.getEntitiesInScope(context)) {
- if (node instanceof Reference$1) {
- templateContext.set(node.name, {
- kind: CompletionKind.Reference,
- node,
- });
- }
- else if (node instanceof LetDeclaration$1) {
- templateContext.set(node.name, {
- kind: CompletionKind.LetDeclaration,
- node,
- });
- }
- else {
- templateContext.set(node.name, {
- kind: CompletionKind.Variable,
- node,
- });
- }
- }
- this.templateContextCache.set(context, templateContext);
- return templateContext;
- }
- }
- const comma = ','.charCodeAt(0);
- const semicolon = ';'.charCodeAt(0);
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
- const intToChar = new Uint8Array(64); // 64 possible chars.
- const charToInt = new Uint8Array(128); // z is 122 in ASCII
- for (let i = 0; i < chars.length; i++) {
- const c = chars.charCodeAt(i);
- intToChar[i] = c;
- charToInt[c] = i;
- }
- function encodeInteger(builder, num, relative) {
- let delta = num - relative;
- delta = delta < 0 ? (-delta << 1) | 1 : delta << 1;
- do {
- let clamped = delta & 0b011111;
- delta >>>= 5;
- if (delta > 0)
- clamped |= 0b100000;
- builder.write(intToChar[clamped]);
- } while (delta > 0);
- return num;
- }
- const bufLength = 1024 * 16;
- // Provide a fallback for older environments.
- const td = typeof TextDecoder !== 'undefined'
- ? /* #__PURE__ */ new TextDecoder()
- : typeof Buffer !== 'undefined'
- ? {
- decode(buf) {
- const out = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength);
- return out.toString();
- },
- }
- : {
- decode(buf) {
- let out = '';
- for (let i = 0; i < buf.length; i++) {
- out += String.fromCharCode(buf[i]);
- }
- return out;
- },
- };
- class StringWriter {
- constructor() {
- this.pos = 0;
- this.out = '';
- this.buffer = new Uint8Array(bufLength);
- }
- write(v) {
- const { buffer } = this;
- buffer[this.pos++] = v;
- if (this.pos === bufLength) {
- this.out += td.decode(buffer);
- this.pos = 0;
- }
- }
- flush() {
- const { buffer, out, pos } = this;
- return pos > 0 ? out + td.decode(buffer.subarray(0, pos)) : out;
- }
- }
- function encode(decoded) {
- const writer = new StringWriter();
- let sourcesIndex = 0;
- let sourceLine = 0;
- let sourceColumn = 0;
- let namesIndex = 0;
- for (let i = 0; i < decoded.length; i++) {
- const line = decoded[i];
- if (i > 0)
- writer.write(semicolon);
- if (line.length === 0)
- continue;
- let genColumn = 0;
- for (let j = 0; j < line.length; j++) {
- const segment = line[j];
- if (j > 0)
- writer.write(comma);
- genColumn = encodeInteger(writer, segment[0], genColumn);
- if (segment.length === 1)
- continue;
- sourcesIndex = encodeInteger(writer, segment[1], sourcesIndex);
- sourceLine = encodeInteger(writer, segment[2], sourceLine);
- sourceColumn = encodeInteger(writer, segment[3], sourceColumn);
- if (segment.length === 4)
- continue;
- namesIndex = encodeInteger(writer, segment[4], namesIndex);
- }
- }
- return writer.flush();
- }
- class BitSet {
- constructor(arg) {
- this.bits = arg instanceof BitSet ? arg.bits.slice() : [];
- }
- add(n) {
- this.bits[n >> 5] |= 1 << (n & 31);
- }
- has(n) {
- return !!(this.bits[n >> 5] & (1 << (n & 31)));
- }
- }
- class Chunk {
- constructor(start, end, content) {
- this.start = start;
- this.end = end;
- this.original = content;
- this.intro = '';
- this.outro = '';
- this.content = content;
- this.storeName = false;
- this.edited = false;
- {
- this.previous = null;
- this.next = null;
- }
- }
- appendLeft(content) {
- this.outro += content;
- }
- appendRight(content) {
- this.intro = this.intro + content;
- }
- clone() {
- const chunk = new Chunk(this.start, this.end, this.original);
- chunk.intro = this.intro;
- chunk.outro = this.outro;
- chunk.content = this.content;
- chunk.storeName = this.storeName;
- chunk.edited = this.edited;
- return chunk;
- }
- contains(index) {
- return this.start < index && index < this.end;
- }
- eachNext(fn) {
- let chunk = this;
- while (chunk) {
- fn(chunk);
- chunk = chunk.next;
- }
- }
- eachPrevious(fn) {
- let chunk = this;
- while (chunk) {
- fn(chunk);
- chunk = chunk.previous;
- }
- }
- edit(content, storeName, contentOnly) {
- this.content = content;
- if (!contentOnly) {
- this.intro = '';
- this.outro = '';
- }
- this.storeName = storeName;
- this.edited = true;
- return this;
- }
- prependLeft(content) {
- this.outro = content + this.outro;
- }
- prependRight(content) {
- this.intro = content + this.intro;
- }
- reset() {
- this.intro = '';
- this.outro = '';
- if (this.edited) {
- this.content = this.original;
- this.storeName = false;
- this.edited = false;
- }
- }
- split(index) {
- const sliceIndex = index - this.start;
- const originalBefore = this.original.slice(0, sliceIndex);
- const originalAfter = this.original.slice(sliceIndex);
- this.original = originalBefore;
- const newChunk = new Chunk(index, this.end, originalAfter);
- newChunk.outro = this.outro;
- this.outro = '';
- this.end = index;
- if (this.edited) {
- // after split we should save the edit content record into the correct chunk
- // to make sure sourcemap correct
- // For example:
- // ' test'.trim()
- // split -> ' ' + 'test'
- // ✔️ edit -> '' + 'test'
- // ✖️ edit -> 'test' + ''
- // TODO is this block necessary?...
- newChunk.edit('', false);
- this.content = '';
- } else {
- this.content = originalBefore;
- }
- newChunk.next = this.next;
- if (newChunk.next) newChunk.next.previous = newChunk;
- newChunk.previous = this;
- this.next = newChunk;
- return newChunk;
- }
- toString() {
- return this.intro + this.content + this.outro;
- }
- trimEnd(rx) {
- this.outro = this.outro.replace(rx, '');
- if (this.outro.length) return true;
- const trimmed = this.content.replace(rx, '');
- if (trimmed.length) {
- if (trimmed !== this.content) {
- this.split(this.start + trimmed.length).edit('', undefined, true);
- if (this.edited) {
- // save the change, if it has been edited
- this.edit(trimmed, this.storeName, true);
- }
- }
- return true;
- } else {
- this.edit('', undefined, true);
- this.intro = this.intro.replace(rx, '');
- if (this.intro.length) return true;
- }
- }
- trimStart(rx) {
- this.intro = this.intro.replace(rx, '');
- if (this.intro.length) return true;
- const trimmed = this.content.replace(rx, '');
- if (trimmed.length) {
- if (trimmed !== this.content) {
- const newChunk = this.split(this.end - trimmed.length);
- if (this.edited) {
- // save the change, if it has been edited
- newChunk.edit(trimmed, this.storeName, true);
- }
- this.edit('', undefined, true);
- }
- return true;
- } else {
- this.edit('', undefined, true);
- this.outro = this.outro.replace(rx, '');
- if (this.outro.length) return true;
- }
- }
- }
- function getBtoa() {
- if (typeof globalThis !== 'undefined' && typeof globalThis.btoa === 'function') {
- return (str) => globalThis.btoa(unescape(encodeURIComponent(str)));
- } else if (typeof Buffer === 'function') {
- return (str) => Buffer.from(str, 'utf-8').toString('base64');
- } else {
- return () => {
- throw new Error('Unsupported environment: `window.btoa` or `Buffer` should be supported.');
- };
- }
- }
- const btoa = /*#__PURE__*/ getBtoa();
- class SourceMap {
- constructor(properties) {
- this.version = 3;
- this.file = properties.file;
- this.sources = properties.sources;
- this.sourcesContent = properties.sourcesContent;
- this.names = properties.names;
- this.mappings = encode(properties.mappings);
- if (typeof properties.x_google_ignoreList !== 'undefined') {
- this.x_google_ignoreList = properties.x_google_ignoreList;
- }
- if (typeof properties.debugId !== 'undefined') {
- this.debugId = properties.debugId;
- }
- }
- toString() {
- return JSON.stringify(this);
- }
- toUrl() {
- return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString());
- }
- }
- function guessIndent(code) {
- const lines = code.split('\n');
- const tabbed = lines.filter((line) => /^\t+/.test(line));
- const spaced = lines.filter((line) => /^ {2,}/.test(line));
- if (tabbed.length === 0 && spaced.length === 0) {
- return null;
- }
- // More lines tabbed than spaced? Assume tabs, and
- // default to tabs in the case of a tie (or nothing
- // to go on)
- if (tabbed.length >= spaced.length) {
- return '\t';
- }
- // Otherwise, we need to guess the multiple
- const min = spaced.reduce((previous, current) => {
- const numSpaces = /^ +/.exec(current)[0].length;
- return Math.min(numSpaces, previous);
- }, Infinity);
- return new Array(min + 1).join(' ');
- }
- function getRelativePath(from, to) {
- const fromParts = from.split(/[/\\]/);
- const toParts = to.split(/[/\\]/);
- fromParts.pop(); // get dirname
- while (fromParts[0] === toParts[0]) {
- fromParts.shift();
- toParts.shift();
- }
- if (fromParts.length) {
- let i = fromParts.length;
- while (i--) fromParts[i] = '..';
- }
- return fromParts.concat(toParts).join('/');
- }
- const toString = Object.prototype.toString;
- function isObject(thing) {
- return toString.call(thing) === '[object Object]';
- }
- function getLocator(source) {
- const originalLines = source.split('\n');
- const lineOffsets = [];
- for (let i = 0, pos = 0; i < originalLines.length; i++) {
- lineOffsets.push(pos);
- pos += originalLines[i].length + 1;
- }
- return function locate(index) {
- let i = 0;
- let j = lineOffsets.length;
- while (i < j) {
- const m = (i + j) >> 1;
- if (index < lineOffsets[m]) {
- j = m;
- } else {
- i = m + 1;
- }
- }
- const line = i - 1;
- const column = index - lineOffsets[line];
- return { line, column };
- };
- }
- const wordRegex = /\w/;
- class Mappings {
- constructor(hires) {
- this.hires = hires;
- this.generatedCodeLine = 0;
- this.generatedCodeColumn = 0;
- this.raw = [];
- this.rawSegments = this.raw[this.generatedCodeLine] = [];
- this.pending = null;
- }
- addEdit(sourceIndex, content, loc, nameIndex) {
- if (content.length) {
- const contentLengthMinusOne = content.length - 1;
- let contentLineEnd = content.indexOf('\n', 0);
- let previousContentLineEnd = -1;
- // Loop through each line in the content and add a segment, but stop if the last line is empty,
- // else code afterwards would fill one line too many
- while (contentLineEnd >= 0 && contentLengthMinusOne > contentLineEnd) {
- const segment = [this.generatedCodeColumn, sourceIndex, loc.line, loc.column];
- if (nameIndex >= 0) {
- segment.push(nameIndex);
- }
- this.rawSegments.push(segment);
- this.generatedCodeLine += 1;
- this.raw[this.generatedCodeLine] = this.rawSegments = [];
- this.generatedCodeColumn = 0;
- previousContentLineEnd = contentLineEnd;
- contentLineEnd = content.indexOf('\n', contentLineEnd + 1);
- }
- const segment = [this.generatedCodeColumn, sourceIndex, loc.line, loc.column];
- if (nameIndex >= 0) {
- segment.push(nameIndex);
- }
- this.rawSegments.push(segment);
- this.advance(content.slice(previousContentLineEnd + 1));
- } else if (this.pending) {
- this.rawSegments.push(this.pending);
- this.advance(content);
- }
- this.pending = null;
- }
- addUneditedChunk(sourceIndex, chunk, original, loc, sourcemapLocations) {
- let originalCharIndex = chunk.start;
- let first = true;
- // when iterating each char, check if it's in a word boundary
- let charInHiresBoundary = false;
- while (originalCharIndex < chunk.end) {
- if (original[originalCharIndex] === '\n') {
- loc.line += 1;
- loc.column = 0;
- this.generatedCodeLine += 1;
- this.raw[this.generatedCodeLine] = this.rawSegments = [];
- this.generatedCodeColumn = 0;
- first = true;
- charInHiresBoundary = false;
- } else {
- if (this.hires || first || sourcemapLocations.has(originalCharIndex)) {
- const segment = [this.generatedCodeColumn, sourceIndex, loc.line, loc.column];
- if (this.hires === 'boundary') {
- // in hires "boundary", group segments per word boundary than per char
- if (wordRegex.test(original[originalCharIndex])) {
- // for first char in the boundary found, start the boundary by pushing a segment
- if (!charInHiresBoundary) {
- this.rawSegments.push(segment);
- charInHiresBoundary = true;
- }
- } else {
- // for non-word char, end the boundary by pushing a segment
- this.rawSegments.push(segment);
- charInHiresBoundary = false;
- }
- } else {
- this.rawSegments.push(segment);
- }
- }
- loc.column += 1;
- this.generatedCodeColumn += 1;
- first = false;
- }
- originalCharIndex += 1;
- }
- this.pending = null;
- }
- advance(str) {
- if (!str) return;
- const lines = str.split('\n');
- if (lines.length > 1) {
- for (let i = 0; i < lines.length - 1; i++) {
- this.generatedCodeLine++;
- this.raw[this.generatedCodeLine] = this.rawSegments = [];
- }
- this.generatedCodeColumn = 0;
- }
- this.generatedCodeColumn += lines[lines.length - 1].length;
- }
- }
- const n = '\n';
- const warned = {
- insertLeft: false,
- insertRight: false,
- storeName: false,
- };
- class MagicString {
- constructor(string, options = {}) {
- const chunk = new Chunk(0, string.length, string);
- Object.defineProperties(this, {
- original: { writable: true, value: string },
- outro: { writable: true, value: '' },
- intro: { writable: true, value: '' },
- firstChunk: { writable: true, value: chunk },
- lastChunk: { writable: true, value: chunk },
- lastSearchedChunk: { writable: true, value: chunk },
- byStart: { writable: true, value: {} },
- byEnd: { writable: true, value: {} },
- filename: { writable: true, value: options.filename },
- indentExclusionRanges: { writable: true, value: options.indentExclusionRanges },
- sourcemapLocations: { writable: true, value: new BitSet() },
- storedNames: { writable: true, value: {} },
- indentStr: { writable: true, value: undefined },
- ignoreList: { writable: true, value: options.ignoreList },
- offset: { writable: true, value: options.offset || 0 },
- });
- this.byStart[0] = chunk;
- this.byEnd[string.length] = chunk;
- }
- addSourcemapLocation(char) {
- this.sourcemapLocations.add(char);
- }
- append(content) {
- if (typeof content !== 'string') throw new TypeError('outro content must be a string');
- this.outro += content;
- return this;
- }
- appendLeft(index, content) {
- index = index + this.offset;
- if (typeof content !== 'string') throw new TypeError('inserted content must be a string');
- this._split(index);
- const chunk = this.byEnd[index];
- if (chunk) {
- chunk.appendLeft(content);
- } else {
- this.intro += content;
- }
- return this;
- }
- appendRight(index, content) {
- index = index + this.offset;
- if (typeof content !== 'string') throw new TypeError('inserted content must be a string');
- this._split(index);
- const chunk = this.byStart[index];
- if (chunk) {
- chunk.appendRight(content);
- } else {
- this.outro += content;
- }
- return this;
- }
- clone() {
- const cloned = new MagicString(this.original, { filename: this.filename, offset: this.offset });
- let originalChunk = this.firstChunk;
- let clonedChunk = (cloned.firstChunk = cloned.lastSearchedChunk = originalChunk.clone());
- while (originalChunk) {
- cloned.byStart[clonedChunk.start] = clonedChunk;
- cloned.byEnd[clonedChunk.end] = clonedChunk;
- const nextOriginalChunk = originalChunk.next;
- const nextClonedChunk = nextOriginalChunk && nextOriginalChunk.clone();
- if (nextClonedChunk) {
- clonedChunk.next = nextClonedChunk;
- nextClonedChunk.previous = clonedChunk;
- clonedChunk = nextClonedChunk;
- }
- originalChunk = nextOriginalChunk;
- }
- cloned.lastChunk = clonedChunk;
- if (this.indentExclusionRanges) {
- cloned.indentExclusionRanges = this.indentExclusionRanges.slice();
- }
- cloned.sourcemapLocations = new BitSet(this.sourcemapLocations);
- cloned.intro = this.intro;
- cloned.outro = this.outro;
- return cloned;
- }
- generateDecodedMap(options) {
- options = options || {};
- const sourceIndex = 0;
- const names = Object.keys(this.storedNames);
- const mappings = new Mappings(options.hires);
- const locate = getLocator(this.original);
- if (this.intro) {
- mappings.advance(this.intro);
- }
- this.firstChunk.eachNext((chunk) => {
- const loc = locate(chunk.start);
- if (chunk.intro.length) mappings.advance(chunk.intro);
- if (chunk.edited) {
- mappings.addEdit(
- sourceIndex,
- chunk.content,
- loc,
- chunk.storeName ? names.indexOf(chunk.original) : -1,
- );
- } else {
- mappings.addUneditedChunk(sourceIndex, chunk, this.original, loc, this.sourcemapLocations);
- }
- if (chunk.outro.length) mappings.advance(chunk.outro);
- });
- return {
- file: options.file ? options.file.split(/[/\\]/).pop() : undefined,
- sources: [
- options.source ? getRelativePath(options.file || '', options.source) : options.file || '',
- ],
- sourcesContent: options.includeContent ? [this.original] : undefined,
- names,
- mappings: mappings.raw,
- x_google_ignoreList: this.ignoreList ? [sourceIndex] : undefined,
- };
- }
- generateMap(options) {
- return new SourceMap(this.generateDecodedMap(options));
- }
- _ensureindentStr() {
- if (this.indentStr === undefined) {
- this.indentStr = guessIndent(this.original);
- }
- }
- _getRawIndentString() {
- this._ensureindentStr();
- return this.indentStr;
- }
- getIndentString() {
- this._ensureindentStr();
- return this.indentStr === null ? '\t' : this.indentStr;
- }
- indent(indentStr, options) {
- const pattern = /^[^\r\n]/gm;
- if (isObject(indentStr)) {
- options = indentStr;
- indentStr = undefined;
- }
- if (indentStr === undefined) {
- this._ensureindentStr();
- indentStr = this.indentStr || '\t';
- }
- if (indentStr === '') return this; // noop
- options = options || {};
- // Process exclusion ranges
- const isExcluded = {};
- if (options.exclude) {
- const exclusions =
- typeof options.exclude[0] === 'number' ? [options.exclude] : options.exclude;
- exclusions.forEach((exclusion) => {
- for (let i = exclusion[0]; i < exclusion[1]; i += 1) {
- isExcluded[i] = true;
- }
- });
- }
- let shouldIndentNextCharacter = options.indentStart !== false;
- const replacer = (match) => {
- if (shouldIndentNextCharacter) return `${indentStr}${match}`;
- shouldIndentNextCharacter = true;
- return match;
- };
- this.intro = this.intro.replace(pattern, replacer);
- let charIndex = 0;
- let chunk = this.firstChunk;
- while (chunk) {
- const end = chunk.end;
- if (chunk.edited) {
- if (!isExcluded[charIndex]) {
- chunk.content = chunk.content.replace(pattern, replacer);
- if (chunk.content.length) {
- shouldIndentNextCharacter = chunk.content[chunk.content.length - 1] === '\n';
- }
- }
- } else {
- charIndex = chunk.start;
- while (charIndex < end) {
- if (!isExcluded[charIndex]) {
- const char = this.original[charIndex];
- if (char === '\n') {
- shouldIndentNextCharacter = true;
- } else if (char !== '\r' && shouldIndentNextCharacter) {
- shouldIndentNextCharacter = false;
- if (charIndex === chunk.start) {
- chunk.prependRight(indentStr);
- } else {
- this._splitChunk(chunk, charIndex);
- chunk = chunk.next;
- chunk.prependRight(indentStr);
- }
- }
- }
- charIndex += 1;
- }
- }
- charIndex = chunk.end;
- chunk = chunk.next;
- }
- this.outro = this.outro.replace(pattern, replacer);
- return this;
- }
- insert() {
- throw new Error(
- 'magicString.insert(...) is deprecated. Use prependRight(...) or appendLeft(...)',
- );
- }
- insertLeft(index, content) {
- if (!warned.insertLeft) {
- console.warn(
- 'magicString.insertLeft(...) is deprecated. Use magicString.appendLeft(...) instead',
- );
- warned.insertLeft = true;
- }
- return this.appendLeft(index, content);
- }
- insertRight(index, content) {
- if (!warned.insertRight) {
- console.warn(
- 'magicString.insertRight(...) is deprecated. Use magicString.prependRight(...) instead',
- );
- warned.insertRight = true;
- }
- return this.prependRight(index, content);
- }
- move(start, end, index) {
- start = start + this.offset;
- end = end + this.offset;
- index = index + this.offset;
- if (index >= start && index <= end) throw new Error('Cannot move a selection inside itself');
- this._split(start);
- this._split(end);
- this._split(index);
- const first = this.byStart[start];
- const last = this.byEnd[end];
- const oldLeft = first.previous;
- const oldRight = last.next;
- const newRight = this.byStart[index];
- if (!newRight && last === this.lastChunk) return this;
- const newLeft = newRight ? newRight.previous : this.lastChunk;
- if (oldLeft) oldLeft.next = oldRight;
- if (oldRight) oldRight.previous = oldLeft;
- if (newLeft) newLeft.next = first;
- if (newRight) newRight.previous = last;
- if (!first.previous) this.firstChunk = last.next;
- if (!last.next) {
- this.lastChunk = first.previous;
- this.lastChunk.next = null;
- }
- first.previous = newLeft;
- last.next = newRight || null;
- if (!newLeft) this.firstChunk = first;
- if (!newRight) this.lastChunk = last;
- return this;
- }
- overwrite(start, end, content, options) {
- options = options || {};
- return this.update(start, end, content, { ...options, overwrite: !options.contentOnly });
- }
- update(start, end, content, options) {
- start = start + this.offset;
- end = end + this.offset;
- if (typeof content !== 'string') throw new TypeError('replacement content must be a string');
- if (this.original.length !== 0) {
- while (start < 0) start += this.original.length;
- while (end < 0) end += this.original.length;
- }
- if (end > this.original.length) throw new Error('end is out of bounds');
- if (start === end)
- throw new Error(
- 'Cannot overwrite a zero-length range – use appendLeft or prependRight instead',
- );
- this._split(start);
- this._split(end);
- if (options === true) {
- if (!warned.storeName) {
- console.warn(
- 'The final argument to magicString.overwrite(...) should be an options object. See https://github.com/rich-harris/magic-string',
- );
- warned.storeName = true;
- }
- options = { storeName: true };
- }
- const storeName = options !== undefined ? options.storeName : false;
- const overwrite = options !== undefined ? options.overwrite : false;
- if (storeName) {
- const original = this.original.slice(start, end);
- Object.defineProperty(this.storedNames, original, {
- writable: true,
- value: true,
- enumerable: true,
- });
- }
- const first = this.byStart[start];
- const last = this.byEnd[end];
- if (first) {
- let chunk = first;
- while (chunk !== last) {
- if (chunk.next !== this.byStart[chunk.end]) {
- throw new Error('Cannot overwrite across a split point');
- }
- chunk = chunk.next;
- chunk.edit('', false);
- }
- first.edit(content, storeName, !overwrite);
- } else {
- // must be inserting at the end
- const newChunk = new Chunk(start, end, '').edit(content, storeName);
- // TODO last chunk in the array may not be the last chunk, if it's moved...
- last.next = newChunk;
- newChunk.previous = last;
- }
- return this;
- }
- prepend(content) {
- if (typeof content !== 'string') throw new TypeError('outro content must be a string');
- this.intro = content + this.intro;
- return this;
- }
- prependLeft(index, content) {
- index = index + this.offset;
- if (typeof content !== 'string') throw new TypeError('inserted content must be a string');
- this._split(index);
- const chunk = this.byEnd[index];
- if (chunk) {
- chunk.prependLeft(content);
- } else {
- this.intro = content + this.intro;
- }
- return this;
- }
- prependRight(index, content) {
- index = index + this.offset;
- if (typeof content !== 'string') throw new TypeError('inserted content must be a string');
- this._split(index);
- const chunk = this.byStart[index];
- if (chunk) {
- chunk.prependRight(content);
- } else {
- this.outro = content + this.outro;
- }
- return this;
- }
- remove(start, end) {
- start = start + this.offset;
- end = end + this.offset;
- if (this.original.length !== 0) {
- while (start < 0) start += this.original.length;
- while (end < 0) end += this.original.length;
- }
- if (start === end) return this;
- if (start < 0 || end > this.original.length) throw new Error('Character is out of bounds');
- if (start > end) throw new Error('end must be greater than start');
- this._split(start);
- this._split(end);
- let chunk = this.byStart[start];
- while (chunk) {
- chunk.intro = '';
- chunk.outro = '';
- chunk.edit('');
- chunk = end > chunk.end ? this.byStart[chunk.end] : null;
- }
- return this;
- }
- reset(start, end) {
- start = start + this.offset;
- end = end + this.offset;
- if (this.original.length !== 0) {
- while (start < 0) start += this.original.length;
- while (end < 0) end += this.original.length;
- }
- if (start === end) return this;
- if (start < 0 || end > this.original.length) throw new Error('Character is out of bounds');
- if (start > end) throw new Error('end must be greater than start');
- this._split(start);
- this._split(end);
- let chunk = this.byStart[start];
- while (chunk) {
- chunk.reset();
- chunk = end > chunk.end ? this.byStart[chunk.end] : null;
- }
- return this;
- }
- lastChar() {
- if (this.outro.length) return this.outro[this.outro.length - 1];
- let chunk = this.lastChunk;
- do {
- if (chunk.outro.length) return chunk.outro[chunk.outro.length - 1];
- if (chunk.content.length) return chunk.content[chunk.content.length - 1];
- if (chunk.intro.length) return chunk.intro[chunk.intro.length - 1];
- } while ((chunk = chunk.previous));
- if (this.intro.length) return this.intro[this.intro.length - 1];
- return '';
- }
- lastLine() {
- let lineIndex = this.outro.lastIndexOf(n);
- if (lineIndex !== -1) return this.outro.substr(lineIndex + 1);
- let lineStr = this.outro;
- let chunk = this.lastChunk;
- do {
- if (chunk.outro.length > 0) {
- lineIndex = chunk.outro.lastIndexOf(n);
- if (lineIndex !== -1) return chunk.outro.substr(lineIndex + 1) + lineStr;
- lineStr = chunk.outro + lineStr;
- }
- if (chunk.content.length > 0) {
- lineIndex = chunk.content.lastIndexOf(n);
- if (lineIndex !== -1) return chunk.content.substr(lineIndex + 1) + lineStr;
- lineStr = chunk.content + lineStr;
- }
- if (chunk.intro.length > 0) {
- lineIndex = chunk.intro.lastIndexOf(n);
- if (lineIndex !== -1) return chunk.intro.substr(lineIndex + 1) + lineStr;
- lineStr = chunk.intro + lineStr;
- }
- } while ((chunk = chunk.previous));
- lineIndex = this.intro.lastIndexOf(n);
- if (lineIndex !== -1) return this.intro.substr(lineIndex + 1) + lineStr;
- return this.intro + lineStr;
- }
- slice(start = 0, end = this.original.length - this.offset) {
- start = start + this.offset;
- end = end + this.offset;
- if (this.original.length !== 0) {
- while (start < 0) start += this.original.length;
- while (end < 0) end += this.original.length;
- }
- let result = '';
- // find start chunk
- let chunk = this.firstChunk;
- while (chunk && (chunk.start > start || chunk.end <= start)) {
- // found end chunk before start
- if (chunk.start < end && chunk.end >= end) {
- return result;
- }
- chunk = chunk.next;
- }
- if (chunk && chunk.edited && chunk.start !== start)
- throw new Error(`Cannot use replaced character ${start} as slice start anchor.`);
- const startChunk = chunk;
- while (chunk) {
- if (chunk.intro && (startChunk !== chunk || chunk.start === start)) {
- result += chunk.intro;
- }
- const containsEnd = chunk.start < end && chunk.end >= end;
- if (containsEnd && chunk.edited && chunk.end !== end)
- throw new Error(`Cannot use replaced character ${end} as slice end anchor.`);
- const sliceStart = startChunk === chunk ? start - chunk.start : 0;
- const sliceEnd = containsEnd ? chunk.content.length + end - chunk.end : chunk.content.length;
- result += chunk.content.slice(sliceStart, sliceEnd);
- if (chunk.outro && (!containsEnd || chunk.end === end)) {
- result += chunk.outro;
- }
- if (containsEnd) {
- break;
- }
- chunk = chunk.next;
- }
- return result;
- }
- // TODO deprecate this? not really very useful
- snip(start, end) {
- const clone = this.clone();
- clone.remove(0, start);
- clone.remove(end, clone.original.length);
- return clone;
- }
- _split(index) {
- if (this.byStart[index] || this.byEnd[index]) return;
- let chunk = this.lastSearchedChunk;
- const searchForward = index > chunk.end;
- while (chunk) {
- if (chunk.contains(index)) return this._splitChunk(chunk, index);
- chunk = searchForward ? this.byStart[chunk.end] : this.byEnd[chunk.start];
- }
- }
- _splitChunk(chunk, index) {
- if (chunk.edited && chunk.content.length) {
- // zero-length edited chunks are a special case (overlapping replacements)
- const loc = getLocator(this.original)(index);
- throw new Error(
- `Cannot split a chunk that has already been edited (${loc.line}:${loc.column} – "${chunk.original}")`,
- );
- }
- const newChunk = chunk.split(index);
- this.byEnd[index] = chunk;
- this.byStart[index] = newChunk;
- this.byEnd[newChunk.end] = newChunk;
- if (chunk === this.lastChunk) this.lastChunk = newChunk;
- this.lastSearchedChunk = chunk;
- return true;
- }
- toString() {
- let str = this.intro;
- let chunk = this.firstChunk;
- while (chunk) {
- str += chunk.toString();
- chunk = chunk.next;
- }
- return str + this.outro;
- }
- isEmpty() {
- let chunk = this.firstChunk;
- do {
- if (
- (chunk.intro.length && chunk.intro.trim()) ||
- (chunk.content.length && chunk.content.trim()) ||
- (chunk.outro.length && chunk.outro.trim())
- )
- return false;
- } while ((chunk = chunk.next));
- return true;
- }
- length() {
- let chunk = this.firstChunk;
- let length = 0;
- do {
- length += chunk.intro.length + chunk.content.length + chunk.outro.length;
- } while ((chunk = chunk.next));
- return length;
- }
- trimLines() {
- return this.trim('[\\r\\n]');
- }
- trim(charType) {
- return this.trimStart(charType).trimEnd(charType);
- }
- trimEndAborted(charType) {
- const rx = new RegExp((charType || '\\s') + '+$');
- this.outro = this.outro.replace(rx, '');
- if (this.outro.length) return true;
- let chunk = this.lastChunk;
- do {
- const end = chunk.end;
- const aborted = chunk.trimEnd(rx);
- // if chunk was trimmed, we have a new lastChunk
- if (chunk.end !== end) {
- if (this.lastChunk === chunk) {
- this.lastChunk = chunk.next;
- }
- this.byEnd[chunk.end] = chunk;
- this.byStart[chunk.next.start] = chunk.next;
- this.byEnd[chunk.next.end] = chunk.next;
- }
- if (aborted) return true;
- chunk = chunk.previous;
- } while (chunk);
- return false;
- }
- trimEnd(charType) {
- this.trimEndAborted(charType);
- return this;
- }
- trimStartAborted(charType) {
- const rx = new RegExp('^' + (charType || '\\s') + '+');
- this.intro = this.intro.replace(rx, '');
- if (this.intro.length) return true;
- let chunk = this.firstChunk;
- do {
- const end = chunk.end;
- const aborted = chunk.trimStart(rx);
- if (chunk.end !== end) {
- // special case...
- if (chunk === this.lastChunk) this.lastChunk = chunk.next;
- this.byEnd[chunk.end] = chunk;
- this.byStart[chunk.next.start] = chunk.next;
- this.byEnd[chunk.next.end] = chunk.next;
- }
- if (aborted) return true;
- chunk = chunk.next;
- } while (chunk);
- return false;
- }
- trimStart(charType) {
- this.trimStartAborted(charType);
- return this;
- }
- hasChanged() {
- return this.original !== this.toString();
- }
- _replaceRegexp(searchValue, replacement) {
- function getReplacement(match, str) {
- if (typeof replacement === 'string') {
- return replacement.replace(/\$(\$|&|\d+)/g, (_, i) => {
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_a_parameter
- if (i === '$') return '$';
- if (i === '&') return match[0];
- const num = +i;
- if (num < match.length) return match[+i];
- return `$${i}`;
- });
- } else {
- return replacement(...match, match.index, str, match.groups);
- }
- }
- function matchAll(re, str) {
- let match;
- const matches = [];
- while ((match = re.exec(str))) {
- matches.push(match);
- }
- return matches;
- }
- if (searchValue.global) {
- const matches = matchAll(searchValue, this.original);
- matches.forEach((match) => {
- if (match.index != null) {
- const replacement = getReplacement(match, this.original);
- if (replacement !== match[0]) {
- this.overwrite(match.index, match.index + match[0].length, replacement);
- }
- }
- });
- } else {
- const match = this.original.match(searchValue);
- if (match && match.index != null) {
- const replacement = getReplacement(match, this.original);
- if (replacement !== match[0]) {
- this.overwrite(match.index, match.index + match[0].length, replacement);
- }
- }
- }
- return this;
- }
- _replaceString(string, replacement) {
- const { original } = this;
- const index = original.indexOf(string);
- if (index !== -1) {
- this.overwrite(index, index + string.length, replacement);
- }
- return this;
- }
- replace(searchValue, replacement) {
- if (typeof searchValue === 'string') {
- return this._replaceString(searchValue, replacement);
- }
- return this._replaceRegexp(searchValue, replacement);
- }
- _replaceAllString(string, replacement) {
- const { original } = this;
- const stringLength = string.length;
- for (
- let index = original.indexOf(string);
- index !== -1;
- index = original.indexOf(string, index + stringLength)
- ) {
- const previous = original.slice(index, index + stringLength);
- if (previous !== replacement) this.overwrite(index, index + stringLength, replacement);
- }
- return this;
- }
- replaceAll(searchValue, replacement) {
- if (typeof searchValue === 'string') {
- return this._replaceAllString(searchValue, replacement);
- }
- if (!searchValue.global) {
- throw new TypeError(
- 'MagicString.prototype.replaceAll called with a non-global RegExp argument',
- );
- }
- return this._replaceRegexp(searchValue, replacement);
- }
- }
- const REGISTRY$1 = new DomElementSchemaRegistry();
- const REMOVE_XHTML_REGEX = /^:xhtml:/;
- /**
- * Checks non-Angular elements and properties against the `DomElementSchemaRegistry`, a schema
- * maintained by the Angular team via extraction from a browser IDL.
- */
- class RegistryDomSchemaChecker {
- resolver;
- _diagnostics = [];
- get diagnostics() {
- return this._diagnostics;
- }
- constructor(resolver) {
- this.resolver = resolver;
- }
- checkElement(id, element, schemas, hostIsStandalone) {
- // HTML elements inside an SVG `foreignObject` are declared in the `xhtml` namespace.
- // We need to strip it before handing it over to the registry because all HTML tag names
- // in the registry are without a namespace.
- const name = element.name.replace(REMOVE_XHTML_REGEX, '');
- if (!REGISTRY$1.hasElement(name, schemas)) {
- const mapping = this.resolver.getTemplateSourceMapping(id);
- const schemas = `'${hostIsStandalone ? '@Component' : '@NgModule'}.schemas'`;
- let errorMsg = `'${name}' is not a known element:\n`;
- errorMsg += `1. If '${name}' is an Angular component, then verify that it is ${hostIsStandalone
- ? "included in the '@Component.imports' of this component"
- : 'part of this module'}.\n`;
- if (name.indexOf('-') > -1) {
- errorMsg += `2. If '${name}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the ${schemas} of this component to suppress this message.`;
- }
- else {
- errorMsg += `2. To allow any element add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`;
- }
- const diag = makeTemplateDiagnostic(id, mapping, element.startSourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.SCHEMA_INVALID_ELEMENT), errorMsg);
- this._diagnostics.push(diag);
- }
- }
- checkProperty(id, element, name, span, schemas, hostIsStandalone) {
- if (!REGISTRY$1.hasProperty(element.name, name, schemas)) {
- const mapping = this.resolver.getTemplateSourceMapping(id);
- const decorator = hostIsStandalone ? '@Component' : '@NgModule';
- const schemas = `'${decorator}.schemas'`;
- let errorMsg = `Can't bind to '${name}' since it isn't a known property of '${element.name}'.`;
- if (element.name.startsWith('ng-')) {
- errorMsg +=
- `\n1. If '${name}' is an Angular directive, then add 'CommonModule' to the '${decorator}.imports' of this component.` +
- `\n2. To allow any property add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`;
- }
- else if (element.name.indexOf('-') > -1) {
- errorMsg +=
- `\n1. If '${element.name}' is an Angular component and it has '${name}' input, then verify that it is ${hostIsStandalone
- ? "included in the '@Component.imports' of this component"
- : 'part of this module'}.` +
- `\n2. If '${element.name}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the ${schemas} of this component to suppress this message.` +
- `\n3. To allow any property add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`;
- }
- const diag = makeTemplateDiagnostic(id, mapping, span, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.SCHEMA_INVALID_ATTRIBUTE), errorMsg);
- this._diagnostics.push(diag);
- }
- }
- }
- /**
- * An environment for a given source file that can be used to emit references.
- *
- * This can be used by the type-checking block, or constructor logic to generate
- * references to directives or other symbols or types.
- */
- class ReferenceEmitEnvironment {
- importManager;
- refEmitter;
- reflector;
- contextFile;
- constructor(importManager, refEmitter, reflector, contextFile) {
- this.importManager = importManager;
- this.refEmitter = refEmitter;
- this.reflector = reflector;
- this.contextFile = contextFile;
- }
- canReferenceType(ref, flags = exports.ImportFlags.NoAliasing |
- exports.ImportFlags.AllowTypeImports |
- exports.ImportFlags.AllowRelativeDtsImports) {
- const result = this.refEmitter.emit(ref, this.contextFile, flags);
- return result.kind === exports.ReferenceEmitKind.Success;
- }
- /**
- * Generate a `ts.TypeNode` that references the given node as a type.
- *
- * This may involve importing the node into the file if it's not declared there already.
- */
- referenceType(ref, flags = exports.ImportFlags.NoAliasing |
- exports.ImportFlags.AllowTypeImports |
- exports.ImportFlags.AllowRelativeDtsImports) {
- const ngExpr = this.refEmitter.emit(ref, this.contextFile, flags);
- assertSuccessfulReferenceEmit(ngExpr, this.contextFile, 'symbol');
- // Create an `ExpressionType` from the `Expression` and translate it via `translateType`.
- // TODO(alxhub): support references to types with generic arguments in a clean way.
- return translateType(new ExpressionType(ngExpr.expression), this.contextFile, this.reflector, this.refEmitter, this.importManager);
- }
- /**
- * Generate a `ts.Expression` that refers to the external symbol. This
- * may result in new imports being generated.
- */
- referenceExternalSymbol(moduleName, name) {
- const external = new ExternalExpr({ moduleName, name });
- return translateExpression(this.contextFile, external, this.importManager);
- }
- /**
- * Generate a `ts.TypeNode` that references a given type from the provided module.
- *
- * This will involve importing the type into the file, and will also add type parameters if
- * provided.
- */
- referenceExternalType(moduleName, name, typeParams) {
- const external = new ExternalExpr({ moduleName, name });
- return translateType(new ExpressionType(external, TypeModifier.None, typeParams), this.contextFile, this.reflector, this.refEmitter, this.importManager);
- }
- /**
- * Generates a `ts.TypeNode` representing a type that is being referenced from a different place
- * in the program. Any type references inside the transplanted type will be rewritten so that
- * they can be imported in the context file.
- */
- referenceTransplantedType(type) {
- return translateType(type, this.contextFile, this.reflector, this.refEmitter, this.importManager);
- }
- }
- /**
- * A `Set` of `ts.SyntaxKind`s of `ts.Expression` which are safe to wrap in a `ts.AsExpression`
- * without needing to be wrapped in parentheses.
- *
- * For example, `foo.bar()` is a `ts.CallExpression`, and can be safely cast to `any` with
- * `foo.bar() as any`. however, `foo !== bar` is a `ts.BinaryExpression`, and attempting to cast
- * without the parentheses yields the expression `foo !== bar as any`. This is semantically
- * equivalent to `foo !== (bar as any)`, which is not what was intended. Thus,
- * `ts.BinaryExpression`s need to be wrapped in parentheses before casting.
- */
- //
- const SAFE_TO_CAST_WITHOUT_PARENS = new Set([
- // Expressions which are already parenthesized can be cast without further wrapping.
- ts.SyntaxKind.ParenthesizedExpression,
- // Expressions which form a single lexical unit leave no room for precedence issues with the cast.
- ts.SyntaxKind.Identifier,
- ts.SyntaxKind.CallExpression,
- ts.SyntaxKind.NonNullExpression,
- ts.SyntaxKind.ElementAccessExpression,
- ts.SyntaxKind.PropertyAccessExpression,
- ts.SyntaxKind.ArrayLiteralExpression,
- ts.SyntaxKind.ObjectLiteralExpression,
- // The same goes for various literals.
- ts.SyntaxKind.StringLiteral,
- ts.SyntaxKind.NumericLiteral,
- ts.SyntaxKind.TrueKeyword,
- ts.SyntaxKind.FalseKeyword,
- ts.SyntaxKind.NullKeyword,
- ts.SyntaxKind.UndefinedKeyword,
- ]);
- function tsCastToAny(expr) {
- // Wrap `expr` in parentheses if needed (see `SAFE_TO_CAST_WITHOUT_PARENS` above).
- if (!SAFE_TO_CAST_WITHOUT_PARENS.has(expr.kind)) {
- expr = ts.factory.createParenthesizedExpression(expr);
- }
- // The outer expression is always wrapped in parentheses.
- return ts.factory.createParenthesizedExpression(ts.factory.createAsExpression(expr, ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)));
- }
- /**
- * Create an expression which instantiates an element by its HTML tagName.
- *
- * Thanks to narrowing of `document.createElement()`, this expression will have its type inferred
- * based on the tag name, including for custom elements that have appropriate .d.ts definitions.
- */
- function tsCreateElement(tagName) {
- const createElement = ts.factory.createPropertyAccessExpression(
- /* expression */ ts.factory.createIdentifier('document'), 'createElement');
- return ts.factory.createCallExpression(
- /* expression */ createElement,
- /* typeArguments */ undefined,
- /* argumentsArray */ [ts.factory.createStringLiteral(tagName)]);
- }
- /**
- * Create a `ts.VariableStatement` which declares a variable without explicit initialization.
- *
- * The initializer `null!` is used to bypass strict variable initialization checks.
- *
- * Unlike with `tsCreateVariable`, the type of the variable is explicitly specified.
- */
- function tsDeclareVariable(id, type) {
- // When we create a variable like `var _t1: boolean = null!`, TypeScript actually infers `_t1`
- // to be `never`, instead of a `boolean`. To work around it, we cast the value
- // in the initializer, e.g. `var _t1 = null! as boolean;`.
- addExpressionIdentifier(type, ExpressionIdentifier.VARIABLE_AS_EXPRESSION);
- const initializer = ts.factory.createAsExpression(ts.factory.createNonNullExpression(ts.factory.createNull()), type);
- const decl = ts.factory.createVariableDeclaration(
- /* name */ id,
- /* exclamationToken */ undefined,
- /* type */ undefined,
- /* initializer */ initializer);
- return ts.factory.createVariableStatement(
- /* modifiers */ undefined,
- /* declarationList */ [decl]);
- }
- /**
- * Creates a `ts.TypeQueryNode` for a coerced input.
- *
- * For example: `typeof MatInput.ngAcceptInputType_value`, where MatInput is `typeName` and `value`
- * is the `coercedInputName`.
- *
- * @param typeName The `EntityName` of the Directive where the static coerced input is defined.
- * @param coercedInputName The field name of the coerced input.
- */
- function tsCreateTypeQueryForCoercedInput(typeName, coercedInputName) {
- return ts.factory.createTypeQueryNode(ts.factory.createQualifiedName(typeName, `ngAcceptInputType_${coercedInputName}`));
- }
- /**
- * Create a `ts.VariableStatement` that initializes a variable with a given expression.
- *
- * Unlike with `tsDeclareVariable`, the type of the variable is inferred from the initializer
- * expression.
- */
- function tsCreateVariable(id, initializer, flags = null) {
- const decl = ts.factory.createVariableDeclaration(
- /* name */ id,
- /* exclamationToken */ undefined,
- /* type */ undefined,
- /* initializer */ initializer);
- return ts.factory.createVariableStatement(
- /* modifiers */ undefined,
- /* declarationList */ flags === null
- ? [decl]
- : ts.factory.createVariableDeclarationList([decl], flags));
- }
- /**
- * Construct a `ts.CallExpression` that calls a method on a receiver.
- */
- function tsCallMethod(receiver, methodName, args = []) {
- const methodAccess = ts.factory.createPropertyAccessExpression(receiver, methodName);
- return ts.factory.createCallExpression(
- /* expression */ methodAccess,
- /* typeArguments */ undefined,
- /* argumentsArray */ args);
- }
- function isAccessExpression(node) {
- return ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node);
- }
- /**
- * Creates a TypeScript node representing a numeric value.
- */
- function tsNumericExpression(value) {
- // As of TypeScript 5.3 negative numbers are represented as `prefixUnaryOperator` and passing a
- // negative number (even as a string) into `createNumericLiteral` will result in an error.
- if (value < 0) {
- const operand = ts.factory.createNumericLiteral(Math.abs(value));
- return ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, operand);
- }
- return ts.factory.createNumericLiteral(value);
- }
- /**
- * See `TypeEmitter` for more information on the emitting process.
- */
- class TypeParameterEmitter {
- typeParameters;
- reflector;
- constructor(typeParameters, reflector) {
- this.typeParameters = typeParameters;
- this.reflector = reflector;
- }
- /**
- * Determines whether the type parameters can be emitted. If this returns true, then a call to
- * `emit` is known to succeed. Vice versa, if false is returned then `emit` should not be
- * called, as it would fail.
- */
- canEmit(canEmitReference) {
- if (this.typeParameters === undefined) {
- return true;
- }
- return this.typeParameters.every((typeParam) => {
- return (this.canEmitType(typeParam.constraint, canEmitReference) &&
- this.canEmitType(typeParam.default, canEmitReference));
- });
- }
- canEmitType(type, canEmitReference) {
- if (type === undefined) {
- return true;
- }
- return canEmitType(type, (typeReference) => {
- const reference = this.resolveTypeReference(typeReference);
- if (reference === null) {
- return false;
- }
- if (reference instanceof Reference) {
- return canEmitReference(reference);
- }
- return true;
- });
- }
- /**
- * Emits the type parameters using the provided emitter function for `Reference`s.
- */
- emit(emitReference) {
- if (this.typeParameters === undefined) {
- return undefined;
- }
- const emitter = new TypeEmitter((type) => this.translateTypeReference(type, emitReference));
- return this.typeParameters.map((typeParam) => {
- const constraint = typeParam.constraint !== undefined ? emitter.emitType(typeParam.constraint) : undefined;
- const defaultType = typeParam.default !== undefined ? emitter.emitType(typeParam.default) : undefined;
- return ts.factory.updateTypeParameterDeclaration(typeParam, typeParam.modifiers, typeParam.name, constraint, defaultType);
- });
- }
- resolveTypeReference(type) {
- const target = ts.isIdentifier(type.typeName) ? type.typeName : type.typeName.right;
- const declaration = this.reflector.getDeclarationOfIdentifier(target);
- // If no declaration could be resolved or does not have a `ts.Declaration`, the type cannot be
- // resolved.
- if (declaration === null || declaration.node === null) {
- return null;
- }
- // If the declaration corresponds with a local type parameter, the type reference can be used
- // as is.
- if (this.isLocalTypeParameter(declaration.node)) {
- return type;
- }
- let owningModule = null;
- if (typeof declaration.viaModule === 'string') {
- owningModule = {
- specifier: declaration.viaModule,
- resolutionContext: type.getSourceFile().fileName,
- };
- }
- return new Reference(declaration.node, declaration.viaModule === AmbientImport ? AmbientImport : owningModule);
- }
- translateTypeReference(type, emitReference) {
- const reference = this.resolveTypeReference(type);
- if (!(reference instanceof Reference)) {
- return reference;
- }
- const typeNode = emitReference(reference);
- if (typeNode === null) {
- return null;
- }
- if (!ts.isTypeReferenceNode(typeNode)) {
- throw new Error(`Expected TypeReferenceNode for emitted reference, got ${ts.SyntaxKind[typeNode.kind]}.`);
- }
- return typeNode;
- }
- isLocalTypeParameter(decl) {
- // Checking for local type parameters only occurs during resolution of type parameters, so it is
- // guaranteed that type parameters are present.
- return this.typeParameters.some((param) => param === decl);
- }
- }
- /**
- * External modules/identifiers that always should exist for type check
- * block files.
- *
- * Importing the modules in preparation helps ensuring a stable import graph
- * that would not degrade TypeScript's incremental program structure re-use.
- *
- * Note: For inline type check blocks, or type constructors, we cannot add preparation
- * imports, but ideally the required modules are already imported and can be re-used
- * to not incur a structural TypeScript program re-use discarding.
- */
- const TCB_FILE_IMPORT_GRAPH_PREPARE_IDENTIFIERS = [
- // Imports may be added for signal input checking. We wouldn't want to change the
- // import graph for incremental compilations when suddenly a signal input is used,
- // or removed.
- Identifiers.InputSignalBrandWriteType,
- ];
- /**
- * Indicates whether a particular component requires an inline type check block.
- *
- * This is not a boolean state as inlining might only be required to get the best possible
- * type-checking, but the component could theoretically still be checked without it.
- */
- var TcbInliningRequirement;
- (function (TcbInliningRequirement) {
- /**
- * There is no way to type check this component without inlining.
- */
- TcbInliningRequirement[TcbInliningRequirement["MustInline"] = 0] = "MustInline";
- /**
- * Inlining should be used due to the component's generic bounds, but a non-inlining fallback
- * method can be used if that's not possible.
- */
- TcbInliningRequirement[TcbInliningRequirement["ShouldInlineForGenericBounds"] = 1] = "ShouldInlineForGenericBounds";
- /**
- * There is no requirement for this component's TCB to be inlined.
- */
- TcbInliningRequirement[TcbInliningRequirement["None"] = 2] = "None";
- })(TcbInliningRequirement || (TcbInliningRequirement = {}));
- function requiresInlineTypeCheckBlock(ref, env, usedPipes, reflector) {
- // In order to qualify for a declared TCB (not inline) two conditions must be met:
- // 1) the class must be suitable to be referenced from `env` (e.g. it must be exported)
- // 2) it must not have contextual generic type bounds
- if (!env.canReferenceType(ref)) {
- // Condition 1 is false, the class is not exported.
- return TcbInliningRequirement.MustInline;
- }
- else if (!checkIfGenericTypeBoundsCanBeEmitted(ref.node, reflector, env)) {
- // Condition 2 is false, the class has constrained generic types. It should be checked with an
- // inline TCB if possible, but can potentially use fallbacks to avoid inlining if not.
- return TcbInliningRequirement.ShouldInlineForGenericBounds;
- }
- else if (usedPipes.some((pipeRef) => !env.canReferenceType(pipeRef))) {
- // If one of the pipes used by the component is not exported, a non-inline TCB will not be able
- // to import it, so this requires an inline TCB.
- return TcbInliningRequirement.MustInline;
- }
- else {
- return TcbInliningRequirement.None;
- }
- }
- /** Maps a shim position back to a source code location. */
- function getSourceMapping(shimSf, position, resolver, isDiagnosticRequest) {
- const node = getTokenAtPosition(shimSf, position);
- const sourceLocation = findSourceLocation(node, shimSf, isDiagnosticRequest);
- if (sourceLocation === null) {
- return null;
- }
- const mapping = resolver.getTemplateSourceMapping(sourceLocation.id);
- const span = resolver.toTemplateParseSourceSpan(sourceLocation.id, sourceLocation.span);
- if (span === null) {
- return null;
- }
- // TODO(atscott): Consider adding a context span by walking up from `node` until we get a
- // different span.
- return { sourceLocation, sourceMapping: mapping, span };
- }
- function findTypeCheckBlock(file, id, isDiagnosticRequest) {
- for (const stmt of file.statements) {
- if (ts.isFunctionDeclaration(stmt) && getTemplateId(stmt, file, isDiagnosticRequest) === id) {
- return stmt;
- }
- }
- return null;
- }
- /**
- * Traverses up the AST starting from the given node to extract the source location from comments
- * that have been emitted into the TCB. If the node does not exist within a TCB, or if an ignore
- * marker comment is found up the tree (and this is part of a diagnostic request), this function
- * returns null.
- */
- function findSourceLocation(node, sourceFile, isDiagnosticsRequest) {
- // Search for comments until the TCB's function declaration is encountered.
- while (node !== undefined && !ts.isFunctionDeclaration(node)) {
- if (hasIgnoreForDiagnosticsMarker(node, sourceFile) && isDiagnosticsRequest) {
- // There's an ignore marker on this node, so the diagnostic should not be reported.
- return null;
- }
- const span = readSpanComment(node, sourceFile);
- if (span !== null) {
- // Once the positional information has been extracted, search further up the TCB to extract
- // the unique id that is attached with the TCB's function declaration.
- const id = getTemplateId(node, sourceFile, isDiagnosticsRequest);
- if (id === null) {
- return null;
- }
- return { id, span };
- }
- node = node.parent;
- }
- return null;
- }
- function getTemplateId(node, sourceFile, isDiagnosticRequest) {
- // Walk up to the function declaration of the TCB, the file information is attached there.
- while (!ts.isFunctionDeclaration(node)) {
- if (hasIgnoreForDiagnosticsMarker(node, sourceFile) && isDiagnosticRequest) {
- // There's an ignore marker on this node, so the diagnostic should not be reported.
- return null;
- }
- node = node.parent;
- // Bail once we have reached the root.
- if (node === undefined) {
- return null;
- }
- }
- const start = node.getFullStart();
- return (ts.forEachLeadingCommentRange(sourceFile.text, start, (pos, end, kind) => {
- if (kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
- return null;
- }
- const commentText = sourceFile.text.substring(pos + 2, end - 2);
- return commentText;
- }) || null);
- }
- /**
- * Ensure imports for certain external modules that should always
- * exist are generated. These are ensured to exist to avoid frequent
- * import graph changes whenever e.g. a signal input is introduced in user code.
- */
- function ensureTypeCheckFilePreparationImports(env) {
- for (const identifier of TCB_FILE_IMPORT_GRAPH_PREPARE_IDENTIFIERS) {
- env.importManager.addImport({
- exportModuleSpecifier: identifier.moduleName,
- exportSymbolName: identifier.name,
- requestedFile: env.contextFile,
- });
- }
- }
- function checkIfGenericTypeBoundsCanBeEmitted(node, reflector, env) {
- // Generic type parameters are considered context free if they can be emitted into any context.
- const emitter = new TypeParameterEmitter(node.typeParameters, reflector);
- return emitter.canEmit((ref) => env.canReferenceType(ref));
- }
- function generateTypeCtorDeclarationFn(env, meta, nodeTypeRef, typeParams) {
- const rawTypeArgs = typeParams !== undefined ? generateGenericArgs(typeParams) : undefined;
- const rawType = ts.factory.createTypeReferenceNode(nodeTypeRef, rawTypeArgs);
- const initParam = constructTypeCtorParameter(env, meta, rawType);
- const typeParameters = typeParametersWithDefaultTypes(typeParams);
- if (meta.body) {
- const fnType = ts.factory.createFunctionTypeNode(
- /* typeParameters */ typeParameters,
- /* parameters */ [initParam],
- /* type */ rawType);
- const decl = ts.factory.createVariableDeclaration(
- /* name */ meta.fnName,
- /* exclamationToken */ undefined,
- /* type */ fnType,
- /* body */ ts.factory.createNonNullExpression(ts.factory.createNull()));
- const declList = ts.factory.createVariableDeclarationList([decl], ts.NodeFlags.Const);
- return ts.factory.createVariableStatement(
- /* modifiers */ undefined,
- /* declarationList */ declList);
- }
- else {
- return ts.factory.createFunctionDeclaration(
- /* modifiers */ [ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword)],
- /* asteriskToken */ undefined,
- /* name */ meta.fnName,
- /* typeParameters */ typeParameters,
- /* parameters */ [initParam],
- /* type */ rawType,
- /* body */ undefined);
- }
- }
- /**
- * Generate an inline type constructor for the given class and metadata.
- *
- * An inline type constructor is a specially shaped TypeScript static method, intended to be placed
- * within a directive class itself, that permits type inference of any generic type parameters of
- * the class from the types of expressions bound to inputs or outputs, and the types of elements
- * that match queries performed by the directive. It also catches any errors in the types of these
- * expressions. This method is never called at runtime, but is used in type-check blocks to
- * construct directive types.
- *
- * An inline type constructor for NgFor looks like:
- *
- * static ngTypeCtor<T>(init: Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>):
- * NgForOf<T>;
- *
- * A typical constructor would be:
- *
- * NgForOf.ngTypeCtor(init: {
- * ngForOf: ['foo', 'bar'],
- * ngForTrackBy: null as any,
- * ngForTemplate: null as any,
- * }); // Infers a type of NgForOf<string>.
- *
- * Any inputs declared on the type for which no property binding is present are assigned a value of
- * type `any`, to avoid producing any type errors for unset inputs.
- *
- * Inline type constructors are used when the type being created has bounded generic types which
- * make writing a declared type constructor (via `generateTypeCtorDeclarationFn`) difficult or
- * impossible.
- *
- * @param node the `ClassDeclaration<ts.ClassDeclaration>` for which a type constructor will be
- * generated.
- * @param meta additional metadata required to generate the type constructor.
- * @returns a `ts.MethodDeclaration` for the type constructor.
- */
- function generateInlineTypeCtor(env, node, meta) {
- // Build rawType, a `ts.TypeNode` of the class with its generic parameters passed through from
- // the definition without any type bounds. For example, if the class is
- // `FooDirective<T extends Bar>`, its rawType would be `FooDirective<T>`.
- const rawTypeArgs = node.typeParameters !== undefined ? generateGenericArgs(node.typeParameters) : undefined;
- const rawType = ts.factory.createTypeReferenceNode(node.name, rawTypeArgs);
- const initParam = constructTypeCtorParameter(env, meta, rawType);
- // If this constructor is being generated into a .ts file, then it needs a fake body. The body
- // is set to a return of `null!`. If the type constructor is being generated into a .d.ts file,
- // it needs no body.
- let body = undefined;
- if (meta.body) {
- body = ts.factory.createBlock([
- ts.factory.createReturnStatement(ts.factory.createNonNullExpression(ts.factory.createNull())),
- ]);
- }
- // Create the type constructor method declaration.
- return ts.factory.createMethodDeclaration(
- /* modifiers */ [ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)],
- /* asteriskToken */ undefined,
- /* name */ meta.fnName,
- /* questionToken */ undefined,
- /* typeParameters */ typeParametersWithDefaultTypes(node.typeParameters),
- /* parameters */ [initParam],
- /* type */ rawType,
- /* body */ body);
- }
- function constructTypeCtorParameter(env, meta, rawType) {
- // initType is the type of 'init', the single argument to the type constructor method.
- // If the Directive has any inputs, its initType will be:
- //
- // Pick<rawType, 'inputA'|'inputB'>
- //
- // Pick here is used to select only those fields from which the generic type parameters of the
- // directive will be inferred.
- //
- // In the special case there are no inputs, initType is set to {}.
- let initType = null;
- const plainKeys = [];
- const coercedKeys = [];
- const signalInputKeys = [];
- for (const { classPropertyName, transform, isSignal } of meta.fields.inputs) {
- if (isSignal) {
- signalInputKeys.push(ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(classPropertyName)));
- }
- else if (!meta.coercedInputFields.has(classPropertyName)) {
- plainKeys.push(ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(classPropertyName)));
- }
- else {
- const coercionType = transform != null
- ? transform.type.node
- : tsCreateTypeQueryForCoercedInput(rawType.typeName, classPropertyName);
- coercedKeys.push(ts.factory.createPropertySignature(
- /* modifiers */ undefined,
- /* name */ classPropertyName,
- /* questionToken */ undefined,
- /* type */ coercionType));
- }
- }
- if (plainKeys.length > 0) {
- // Construct a union of all the field names.
- const keyTypeUnion = ts.factory.createUnionTypeNode(plainKeys);
- // Construct the Pick<rawType, keyTypeUnion>.
- initType = ts.factory.createTypeReferenceNode('Pick', [rawType, keyTypeUnion]);
- }
- if (coercedKeys.length > 0) {
- const coercedLiteral = ts.factory.createTypeLiteralNode(coercedKeys);
- initType =
- initType !== null
- ? ts.factory.createIntersectionTypeNode([initType, coercedLiteral])
- : coercedLiteral;
- }
- if (signalInputKeys.length > 0) {
- const keyTypeUnion = ts.factory.createUnionTypeNode(signalInputKeys);
- // Construct the UnwrapDirectiveSignalInputs<rawType, keyTypeUnion>.
- const unwrapDirectiveSignalInputsExpr = env.referenceExternalType(Identifiers.UnwrapDirectiveSignalInputs.moduleName, Identifiers.UnwrapDirectiveSignalInputs.name, [
- // TODO:
- new ExpressionType(new WrappedNodeExpr(rawType)),
- new ExpressionType(new WrappedNodeExpr(keyTypeUnion)),
- ]);
- initType =
- initType !== null
- ? ts.factory.createIntersectionTypeNode([initType, unwrapDirectiveSignalInputsExpr])
- : unwrapDirectiveSignalInputsExpr;
- }
- if (initType === null) {
- // Special case - no inputs, outputs, or other fields which could influence the result type.
- initType = ts.factory.createTypeLiteralNode([]);
- }
- // Create the 'init' parameter itself.
- return ts.factory.createParameterDeclaration(
- /* modifiers */ undefined,
- /* dotDotDotToken */ undefined,
- /* name */ 'init',
- /* questionToken */ undefined,
- /* type */ initType,
- /* initializer */ undefined);
- }
- function generateGenericArgs(params) {
- return params.map((param) => ts.factory.createTypeReferenceNode(param.name, undefined));
- }
- function requiresInlineTypeCtor(node, host, env) {
- // The class requires an inline type constructor if it has generic type bounds that can not be
- // emitted into the provided type-check environment.
- return !checkIfGenericTypeBoundsCanBeEmitted(node, host, env);
- }
- /**
- * Add a default `= any` to type parameters that don't have a default value already.
- *
- * TypeScript uses the default type of a type parameter whenever inference of that parameter
- * fails. This can happen when inferring a complex type from 'any'. For example, if `NgFor`'s
- * inference is done with the TCB code:
- *
- * ```ts
- * class NgFor<T> {
- * ngForOf: T[];
- * }
- *
- * declare function ctor<T>(o: Pick<NgFor<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>):
- * NgFor<T>;
- * ```
- *
- * An invocation looks like:
- *
- * ```ts
- * var _t1 = ctor({ngForOf: [1, 2], ngForTrackBy: null as any, ngForTemplate: null as any});
- * ```
- *
- * This correctly infers the type `NgFor<number>` for `_t1`, since `T` is inferred from the
- * assignment of type `number[]` to `ngForOf`'s type `T[]`. However, if `any` is passed instead:
- *
- * ```ts
- * var _t2 = ctor({ngForOf: [1, 2] as any, ngForTrackBy: null as any, ngForTemplate: null as
- * any});
- * ```
- *
- * then inference for `T` fails (it cannot be inferred from `T[] = any`). In this case, `T`
- * takes the type `{}`, and so `_t2` is inferred as `NgFor<{}>`. This is obviously wrong.
- *
- * Adding a default type to the generic declaration in the constructor solves this problem, as
- * the default type will be used in the event that inference fails.
- *
- * ```ts
- * declare function ctor<T = any>(o: Pick<NgFor<T>, 'ngForOf'>): NgFor<T>;
- *
- * var _t3 = ctor({ngForOf: [1, 2] as any});
- * ```
- *
- * This correctly infers `T` as `any`, and therefore `_t3` as `NgFor<any>`.
- */
- function typeParametersWithDefaultTypes(params) {
- if (params === undefined) {
- return undefined;
- }
- return params.map((param) => {
- if (param.default === undefined) {
- return ts.factory.updateTypeParameterDeclaration(param, param.modifiers, param.name, param.constraint, ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
- }
- else {
- return param;
- }
- });
- }
- /**
- * A context which hosts one or more Type Check Blocks (TCBs).
- *
- * An `Environment` supports the generation of TCBs by tracking necessary imports, declarations of
- * type constructors, and other statements beyond the type-checking code within the TCB itself.
- * Through method calls on `Environment`, the TCB generator can request `ts.Expression`s which
- * reference declarations in the `Environment` for these artifacts`.
- *
- * `Environment` can be used in a standalone fashion, or can be extended to support more specialized
- * usage.
- */
- class Environment extends ReferenceEmitEnvironment {
- config;
- nextIds = {
- pipeInst: 1,
- typeCtor: 1,
- };
- typeCtors = new Map();
- typeCtorStatements = [];
- pipeInsts = new Map();
- pipeInstStatements = [];
- constructor(config, importManager, refEmitter, reflector, contextFile) {
- super(importManager, refEmitter, reflector, contextFile);
- this.config = config;
- }
- /**
- * Get an expression referring to a type constructor for the given directive.
- *
- * Depending on the shape of the directive itself, this could be either a reference to a declared
- * type constructor, or to an inline type constructor.
- */
- typeCtorFor(dir) {
- const dirRef = dir.ref;
- const node = dirRef.node;
- if (this.typeCtors.has(node)) {
- return this.typeCtors.get(node);
- }
- if (requiresInlineTypeCtor(node, this.reflector, this)) {
- // The constructor has already been created inline, we just need to construct a reference to
- // it.
- const ref = this.reference(dirRef);
- const typeCtorExpr = ts.factory.createPropertyAccessExpression(ref, 'ngTypeCtor');
- this.typeCtors.set(node, typeCtorExpr);
- return typeCtorExpr;
- }
- else {
- const fnName = `_ctor${this.nextIds.typeCtor++}`;
- const nodeTypeRef = this.referenceType(dirRef);
- if (!ts.isTypeReferenceNode(nodeTypeRef)) {
- throw new Error(`Expected TypeReferenceNode from reference to ${dirRef.debugName}`);
- }
- const meta = {
- fnName,
- body: true,
- fields: {
- inputs: dir.inputs,
- // TODO: support queries
- queries: dir.queries,
- },
- coercedInputFields: dir.coercedInputFields,
- };
- const typeParams = this.emitTypeParameters(node);
- const typeCtor = generateTypeCtorDeclarationFn(this, meta, nodeTypeRef.typeName, typeParams);
- this.typeCtorStatements.push(typeCtor);
- const fnId = ts.factory.createIdentifier(fnName);
- this.typeCtors.set(node, fnId);
- return fnId;
- }
- }
- /*
- * Get an expression referring to an instance of the given pipe.
- */
- pipeInst(ref) {
- if (this.pipeInsts.has(ref.node)) {
- return this.pipeInsts.get(ref.node);
- }
- const pipeType = this.referenceType(ref);
- const pipeInstId = ts.factory.createIdentifier(`_pipe${this.nextIds.pipeInst++}`);
- this.pipeInstStatements.push(tsDeclareVariable(pipeInstId, pipeType));
- this.pipeInsts.set(ref.node, pipeInstId);
- return pipeInstId;
- }
- /**
- * Generate a `ts.Expression` that references the given node.
- *
- * This may involve importing the node into the file if it's not declared there already.
- */
- reference(ref) {
- // Disable aliasing for imports generated in a template type-checking context, as there is no
- // guarantee that any alias re-exports exist in the .d.ts files. It's safe to use direct imports
- // in these cases as there is no strict dependency checking during the template type-checking
- // pass.
- const ngExpr = this.refEmitter.emit(ref, this.contextFile, exports.ImportFlags.NoAliasing);
- assertSuccessfulReferenceEmit(ngExpr, this.contextFile, 'class');
- // Use `translateExpression` to convert the `Expression` into a `ts.Expression`.
- return translateExpression(this.contextFile, ngExpr.expression, this.importManager);
- }
- emitTypeParameters(declaration) {
- const emitter = new TypeParameterEmitter(declaration.typeParameters, this.reflector);
- return emitter.emit((ref) => this.referenceType(ref));
- }
- getPreludeStatements() {
- return [...this.pipeInstStatements, ...this.typeCtorStatements];
- }
- }
- class OutOfBandDiagnosticRecorderImpl {
- resolver;
- _diagnostics = [];
- /**
- * Tracks which `BindingPipe` nodes have already been recorded as invalid, so only one diagnostic
- * is ever produced per node.
- */
- recordedPipes = new Set();
- constructor(resolver) {
- this.resolver = resolver;
- }
- get diagnostics() {
- return this._diagnostics;
- }
- missingReferenceTarget(id, ref) {
- const mapping = this.resolver.getTemplateSourceMapping(id);
- const value = ref.value.trim();
- const errorMsg = `No directive found with exportAs '${value}'.`;
- this._diagnostics.push(makeTemplateDiagnostic(id, mapping, ref.valueSpan || ref.sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.MISSING_REFERENCE_TARGET), errorMsg));
- }
- missingPipe(id, ast) {
- if (this.recordedPipes.has(ast)) {
- return;
- }
- const mapping = this.resolver.getTemplateSourceMapping(id);
- const errorMsg = `No pipe found with name '${ast.name}'.`;
- const sourceSpan = this.resolver.toTemplateParseSourceSpan(id, ast.nameSpan);
- if (sourceSpan === null) {
- throw new Error(`Assertion failure: no SourceLocation found for usage of pipe '${ast.name}'.`);
- }
- this._diagnostics.push(makeTemplateDiagnostic(id, mapping, sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.MISSING_PIPE), errorMsg));
- this.recordedPipes.add(ast);
- }
- deferredPipeUsedEagerly(id, ast) {
- if (this.recordedPipes.has(ast)) {
- return;
- }
- const mapping = this.resolver.getTemplateSourceMapping(id);
- const errorMsg = `Pipe '${ast.name}' was imported via \`@Component.deferredImports\`, ` +
- `but was used outside of a \`@defer\` block in a template. To fix this, either ` +
- `use the '${ast.name}' pipe inside of a \`@defer\` block or import this dependency ` +
- `using the \`@Component.imports\` field.`;
- const sourceSpan = this.resolver.toTemplateParseSourceSpan(id, ast.nameSpan);
- if (sourceSpan === null) {
- throw new Error(`Assertion failure: no SourceLocation found for usage of pipe '${ast.name}'.`);
- }
- this._diagnostics.push(makeTemplateDiagnostic(id, mapping, sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.DEFERRED_PIPE_USED_EAGERLY), errorMsg));
- this.recordedPipes.add(ast);
- }
- deferredComponentUsedEagerly(id, element) {
- const mapping = this.resolver.getTemplateSourceMapping(id);
- const errorMsg = `Element '${element.name}' contains a component or a directive that ` +
- `was imported via \`@Component.deferredImports\`, but the element itself is located ` +
- `outside of a \`@defer\` block in a template. To fix this, either ` +
- `use the '${element.name}' element inside of a \`@defer\` block or ` +
- `import referenced component/directive dependency using the \`@Component.imports\` field.`;
- const { start, end } = element.startSourceSpan;
- const absoluteSourceSpan = new AbsoluteSourceSpan(start.offset, end.offset);
- const sourceSpan = this.resolver.toTemplateParseSourceSpan(id, absoluteSourceSpan);
- if (sourceSpan === null) {
- throw new Error(`Assertion failure: no SourceLocation found for usage of pipe '${element.name}'.`);
- }
- this._diagnostics.push(makeTemplateDiagnostic(id, mapping, sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.DEFERRED_DIRECTIVE_USED_EAGERLY), errorMsg));
- }
- duplicateTemplateVar(id, variable, firstDecl) {
- const mapping = this.resolver.getTemplateSourceMapping(id);
- const errorMsg = `Cannot redeclare variable '${variable.name}' as it was previously declared elsewhere for the same template.`;
- // The allocation of the error here is pretty useless for variables declared in microsyntax,
- // since the sourceSpan refers to the entire microsyntax property, not a span for the specific
- // variable in question.
- //
- // TODO(alxhub): allocate to a tighter span once one is available.
- this._diagnostics.push(makeTemplateDiagnostic(id, mapping, variable.sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.DUPLICATE_VARIABLE_DECLARATION), errorMsg, [
- {
- text: `The variable '${firstDecl.name}' was first declared here.`,
- start: firstDecl.sourceSpan.start.offset,
- end: firstDecl.sourceSpan.end.offset,
- sourceFile: mapping.node.getSourceFile(),
- },
- ]));
- }
- requiresInlineTcb(id, node) {
- this._diagnostics.push(makeInlineDiagnostic(id, exports.ErrorCode.INLINE_TCB_REQUIRED, node.name, `This component requires inline template type-checking, which is not supported by the current environment.`));
- }
- requiresInlineTypeConstructors(id, node, directives) {
- let message;
- if (directives.length > 1) {
- message = `This component uses directives which require inline type constructors, which are not supported by the current environment.`;
- }
- else {
- message = `This component uses a directive which requires an inline type constructor, which is not supported by the current environment.`;
- }
- this._diagnostics.push(makeInlineDiagnostic(id, exports.ErrorCode.INLINE_TYPE_CTOR_REQUIRED, node.name, message, directives.map((dir) => makeRelatedInformation(dir.name, `Requires an inline type constructor.`))));
- }
- suboptimalTypeInference(id, variables) {
- const mapping = this.resolver.getTemplateSourceMapping(id);
- // Select one of the template variables that's most suitable for reporting the diagnostic. Any
- // variable will do, but prefer one bound to the context's $implicit if present.
- let diagnosticVar = null;
- for (const variable of variables) {
- if (diagnosticVar === null || variable.value === '' || variable.value === '$implicit') {
- diagnosticVar = variable;
- }
- }
- if (diagnosticVar === null) {
- // There is no variable on which to report the diagnostic.
- return;
- }
- let varIdentification = `'${diagnosticVar.name}'`;
- if (variables.length === 2) {
- varIdentification += ` (and 1 other)`;
- }
- else if (variables.length > 2) {
- varIdentification += ` (and ${variables.length - 1} others)`;
- }
- const message = `This structural directive supports advanced type inference, but the current compiler configuration prevents its usage. The variable ${varIdentification} will have type 'any' as a result.\n\nConsider enabling the 'strictTemplates' option in your tsconfig.json for better type inference within this template.`;
- this._diagnostics.push(makeTemplateDiagnostic(id, mapping, diagnosticVar.keySpan, ts.DiagnosticCategory.Suggestion, ngErrorCode(exports.ErrorCode.SUGGEST_SUBOPTIMAL_TYPE_INFERENCE), message));
- }
- splitTwoWayBinding(id, input, output, inputConsumer, outputConsumer) {
- const mapping = this.resolver.getTemplateSourceMapping(id);
- const errorMsg = `The property and event halves of the two-way binding '${input.name}' are not bound to the same target.
- Find more at https://angular.dev/guide/templates/two-way-binding#how-two-way-binding-works`;
- const relatedMessages = [];
- relatedMessages.push({
- text: `The property half of the binding is to the '${inputConsumer.name.text}' component.`,
- start: inputConsumer.name.getStart(),
- end: inputConsumer.name.getEnd(),
- sourceFile: inputConsumer.name.getSourceFile(),
- });
- if (outputConsumer instanceof Element$1) {
- let message = `The event half of the binding is to a native event called '${input.name}' on the <${outputConsumer.name}> DOM element.`;
- if (!mapping.node.getSourceFile().isDeclarationFile) {
- message += `\n \n Are you missing an output declaration called '${output.name}'?`;
- }
- relatedMessages.push({
- text: message,
- start: outputConsumer.sourceSpan.start.offset + 1,
- end: outputConsumer.sourceSpan.start.offset + outputConsumer.name.length + 1,
- sourceFile: mapping.node.getSourceFile(),
- });
- }
- else {
- relatedMessages.push({
- text: `The event half of the binding is to the '${outputConsumer.name.text}' component.`,
- start: outputConsumer.name.getStart(),
- end: outputConsumer.name.getEnd(),
- sourceFile: outputConsumer.name.getSourceFile(),
- });
- }
- this._diagnostics.push(makeTemplateDiagnostic(id, mapping, input.keySpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.SPLIT_TWO_WAY_BINDING), errorMsg, relatedMessages));
- }
- missingRequiredInputs(id, element, directiveName, isComponent, inputAliases) {
- const message = `Required input${inputAliases.length === 1 ? '' : 's'} ${inputAliases
- .map((n) => `'${n}'`)
- .join(', ')} from ${isComponent ? 'component' : 'directive'} ${directiveName} must be specified.`;
- this._diagnostics.push(makeTemplateDiagnostic(id, this.resolver.getTemplateSourceMapping(id), element.startSourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.MISSING_REQUIRED_INPUTS), message));
- }
- illegalForLoopTrackAccess(id, block, access) {
- const sourceSpan = this.resolver.toTemplateParseSourceSpan(id, access.sourceSpan);
- if (sourceSpan === null) {
- throw new Error(`Assertion failure: no SourceLocation found for property read.`);
- }
- const messageVars = [block.item, ...block.contextVariables.filter((v) => v.value === '$index')]
- .map((v) => `'${v.name}'`)
- .join(', ');
- const message = `Cannot access '${access.name}' inside of a track expression. ` +
- `Only ${messageVars} and properties on the containing component are available to this expression.`;
- this._diagnostics.push(makeTemplateDiagnostic(id, this.resolver.getTemplateSourceMapping(id), sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.ILLEGAL_FOR_LOOP_TRACK_ACCESS), message));
- }
- inaccessibleDeferredTriggerElement(id, trigger) {
- let message;
- if (trigger.reference === null) {
- message =
- `Trigger cannot find reference. Make sure that the @defer block has a ` +
- `@placeholder with at least one root element node.`;
- }
- else {
- message =
- `Trigger cannot find reference "${trigger.reference}".\nCheck that an element with #${trigger.reference} exists in the same template and it's accessible from the ` +
- `@defer block.\nDeferred blocks can only access triggers in same view, a parent ` +
- `embedded view or the root view of the @placeholder block.`;
- }
- this._diagnostics.push(makeTemplateDiagnostic(id, this.resolver.getTemplateSourceMapping(id), trigger.sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.INACCESSIBLE_DEFERRED_TRIGGER_ELEMENT), message));
- }
- controlFlowPreventingContentProjection(id, category, projectionNode, componentName, slotSelector, controlFlowNode, preservesWhitespaces) {
- const blockName = controlFlowNode.nameSpan.toString().trim();
- const lines = [
- `Node matches the "${slotSelector}" slot of the "${componentName}" component, but will not be projected into the specific slot because the surrounding ${blockName} has more than one node at its root. To project the node in the right slot, you can:\n`,
- `1. Wrap the content of the ${blockName} block in an <ng-container/> that matches the "${slotSelector}" selector.`,
- `2. Split the content of the ${blockName} block across multiple ${blockName} blocks such that each one only has a single projectable node at its root.`,
- `3. Remove all content from the ${blockName} block, except for the node being projected.`,
- ];
- if (preservesWhitespaces) {
- lines.push('Note: the host component has `preserveWhitespaces: true` which may ' +
- 'cause whitespace to affect content projection.');
- }
- lines.push('', 'This check can be disabled using the `extendedDiagnostics.checks.' +
- 'controlFlowPreventingContentProjection = "suppress" compiler option.`');
- this._diagnostics.push(makeTemplateDiagnostic(id, this.resolver.getTemplateSourceMapping(id), projectionNode.startSourceSpan, category, ngErrorCode(exports.ErrorCode.CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION), lines.join('\n')));
- }
- illegalWriteToLetDeclaration(id, node, target) {
- const sourceSpan = this.resolver.toTemplateParseSourceSpan(id, node.sourceSpan);
- if (sourceSpan === null) {
- throw new Error(`Assertion failure: no SourceLocation found for property write.`);
- }
- this._diagnostics.push(makeTemplateDiagnostic(id, this.resolver.getTemplateSourceMapping(id), sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.ILLEGAL_LET_WRITE), `Cannot assign to @let declaration '${target.name}'.`));
- }
- letUsedBeforeDefinition(id, node, target) {
- const sourceSpan = this.resolver.toTemplateParseSourceSpan(id, node.sourceSpan);
- if (sourceSpan === null) {
- throw new Error(`Assertion failure: no SourceLocation found for property read.`);
- }
- this._diagnostics.push(makeTemplateDiagnostic(id, this.resolver.getTemplateSourceMapping(id), sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.LET_USED_BEFORE_DEFINITION), `Cannot read @let declaration '${target.name}' before it has been defined.`));
- }
- conflictingDeclaration(id, decl) {
- const mapping = this.resolver.getTemplateSourceMapping(id);
- const errorMsg = `Cannot declare @let called '${decl.name}' as there is another symbol in the template with the same name.`;
- this._diagnostics.push(makeTemplateDiagnostic(id, mapping, decl.sourceSpan, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.CONFLICTING_LET_DECLARATION), errorMsg));
- }
- }
- function makeInlineDiagnostic(id, code, node, messageText, relatedInformation) {
- return {
- ...makeDiagnostic(code, node, messageText, relatedInformation),
- sourceFile: node.getSourceFile(),
- typeCheckId: id,
- };
- }
- /**
- * A `ShimGenerator` which adds type-checking files to the `ts.Program`.
- *
- * This is a requirement for performant template type-checking, as TypeScript will only reuse
- * information in the main program when creating the type-checking program if the set of files in
- * each are exactly the same. Thus, the main program also needs the synthetic type-checking files.
- */
- class TypeCheckShimGenerator {
- extensionPrefix = 'ngtypecheck';
- shouldEmit = false;
- generateShimForFile(sf, genFilePath, priorShimSf) {
- if (priorShimSf !== null) {
- // If this shim existed in the previous program, reuse it now. It might not be correct, but
- // reusing it in the main program allows the shape of its imports to potentially remain the
- // same and TS can then use the fastest path for incremental program creation. Later during
- // the type-checking phase it's going to either be reused, or replaced anyways. Thus there's
- // no harm in reuse here even if it's out of date.
- return priorShimSf;
- }
- return ts.createSourceFile(genFilePath, 'export const USED_FOR_NG_TYPE_CHECKING = true;', ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
- }
- static shimFor(fileName) {
- return absoluteFrom(fileName.replace(/\.tsx?$/, '.ngtypecheck.ts'));
- }
- }
- /**
- * Wraps the node in parenthesis such that inserted span comments become attached to the proper
- * node. This is an alias for `ts.factory.createParenthesizedExpression` with the benefit that it
- * signifies that the inserted parenthesis are for diagnostic purposes, not for correctness of the
- * rendered TCB code.
- *
- * Note that it is important that nodes and its attached comment are not wrapped into parenthesis
- * by default, as it prevents correct translation of e.g. diagnostics produced for incorrect method
- * arguments. Such diagnostics would then be produced for the parenthesised node whereas the
- * positional comment would be located within that node, resulting in a mismatch.
- */
- function wrapForDiagnostics(expr) {
- return ts.factory.createParenthesizedExpression(expr);
- }
- /**
- * Wraps the node in parenthesis such that inserted span comments become attached to the proper
- * node. This is an alias for `ts.factory.createParenthesizedExpression` with the benefit that it
- * signifies that the inserted parenthesis are for use by the type checker, not for correctness of
- * the rendered TCB code.
- */
- function wrapForTypeChecker(expr) {
- return ts.factory.createParenthesizedExpression(expr);
- }
- /**
- * Adds a synthetic comment to the expression that represents the parse span of the provided node.
- * This comment can later be retrieved as trivia of a node to recover original source locations.
- */
- function addParseSpanInfo(node, span) {
- let commentText;
- if (span instanceof AbsoluteSourceSpan) {
- commentText = `${span.start},${span.end}`;
- }
- else {
- commentText = `${span.start.offset},${span.end.offset}`;
- }
- ts.addSyntheticTrailingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, commentText,
- /* hasTrailingNewLine */ false);
- }
- /**
- * Adds a synthetic comment to the function declaration that contains the type checking ID
- * of the class declaration.
- */
- function addTypeCheckId(tcb, id) {
- ts.addSyntheticLeadingComment(tcb, ts.SyntaxKind.MultiLineCommentTrivia, id, true);
- }
- /**
- * Determines if the diagnostic should be reported. Some diagnostics are produced because of the
- * way TCBs are generated; those diagnostics should not be reported as type check errors of the
- * template.
- */
- function shouldReportDiagnostic(diagnostic) {
- const { code } = diagnostic;
- if (code === 6133 /* $var is declared but its value is never read. */) {
- return false;
- }
- else if (code === 6199 /* All variables are unused. */) {
- return false;
- }
- else if (code === 2695 /* Left side of comma operator is unused and has no side effects. */) {
- return false;
- }
- else if (code === 7006 /* Parameter '$event' implicitly has an 'any' type. */) {
- return false;
- }
- return true;
- }
- /**
- * Attempts to translate a TypeScript diagnostic produced during template type-checking to their
- * location of origin, based on the comments that are emitted in the TCB code.
- *
- * If the diagnostic could not be translated, `null` is returned to indicate that the diagnostic
- * should not be reported at all. This prevents diagnostics from non-TCB code in a user's source
- * file from being reported as type-check errors.
- */
- function translateDiagnostic(diagnostic, resolver) {
- if (diagnostic.file === undefined || diagnostic.start === undefined) {
- return null;
- }
- const fullMapping = getSourceMapping(diagnostic.file, diagnostic.start, resolver,
- /*isDiagnosticsRequest*/ true);
- if (fullMapping === null) {
- return null;
- }
- const { sourceLocation, sourceMapping: templateSourceMapping, span } = fullMapping;
- return makeTemplateDiagnostic(sourceLocation.id, templateSourceMapping, span, diagnostic.category, diagnostic.code, diagnostic.messageText);
- }
- /**
- * Expression that is cast to any. Currently represented as `0 as any`.
- *
- * Historically this expression was using `null as any`, but a newly-added check in TypeScript 5.6
- * (https://devblogs.microsoft.com/typescript/announcing-typescript-5-6-beta/#disallowed-nullish-and-truthy-checks)
- * started flagging it as always being nullish. Other options that were considered:
- * - `NaN as any` or `Infinity as any` - not used, because they don't work if the `noLib` compiler
- * option is enabled. Also they require more characters.
- * - Some flavor of function call, like `isNan(0) as any` - requires even more characters than the
- * NaN option and has the same issue with `noLib`.
- */
- const ANY_EXPRESSION = ts.factory.createAsExpression(ts.factory.createNumericLiteral('0'), ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
- const UNDEFINED = ts.factory.createIdentifier('undefined');
- const UNARY_OPS = new Map([
- ['+', ts.SyntaxKind.PlusToken],
- ['-', ts.SyntaxKind.MinusToken],
- ]);
- const BINARY_OPS = new Map([
- ['+', ts.SyntaxKind.PlusToken],
- ['-', ts.SyntaxKind.MinusToken],
- ['<', ts.SyntaxKind.LessThanToken],
- ['>', ts.SyntaxKind.GreaterThanToken],
- ['<=', ts.SyntaxKind.LessThanEqualsToken],
- ['>=', ts.SyntaxKind.GreaterThanEqualsToken],
- ['==', ts.SyntaxKind.EqualsEqualsToken],
- ['===', ts.SyntaxKind.EqualsEqualsEqualsToken],
- ['*', ts.SyntaxKind.AsteriskToken],
- ['/', ts.SyntaxKind.SlashToken],
- ['%', ts.SyntaxKind.PercentToken],
- ['!=', ts.SyntaxKind.ExclamationEqualsToken],
- ['!==', ts.SyntaxKind.ExclamationEqualsEqualsToken],
- ['||', ts.SyntaxKind.BarBarToken],
- ['&&', ts.SyntaxKind.AmpersandAmpersandToken],
- ['&', ts.SyntaxKind.AmpersandToken],
- ['|', ts.SyntaxKind.BarToken],
- ['??', ts.SyntaxKind.QuestionQuestionToken],
- ]);
- /**
- * Convert an `AST` to TypeScript code directly, without going through an intermediate `Expression`
- * AST.
- */
- function astToTypescript(ast, maybeResolve, config) {
- const translator = new AstTranslator(maybeResolve, config);
- return translator.translate(ast);
- }
- class AstTranslator {
- maybeResolve;
- config;
- constructor(maybeResolve, config) {
- this.maybeResolve = maybeResolve;
- this.config = config;
- }
- translate(ast) {
- // Skip over an `ASTWithSource` as its `visit` method calls directly into its ast's `visit`,
- // which would prevent any custom resolution through `maybeResolve` for that node.
- if (ast instanceof ASTWithSource) {
- ast = ast.ast;
- }
- // The `EmptyExpr` doesn't have a dedicated method on `AstVisitor`, so it's special cased here.
- if (ast instanceof EmptyExpr$1) {
- const res = ts.factory.createIdentifier('undefined');
- addParseSpanInfo(res, ast.sourceSpan);
- return res;
- }
- // First attempt to let any custom resolution logic provide a translation for the given node.
- const resolved = this.maybeResolve(ast);
- if (resolved !== null) {
- return resolved;
- }
- return ast.visit(this);
- }
- visitUnary(ast) {
- const expr = this.translate(ast.expr);
- const op = UNARY_OPS.get(ast.operator);
- if (op === undefined) {
- throw new Error(`Unsupported Unary.operator: ${ast.operator}`);
- }
- const node = wrapForDiagnostics(ts.factory.createPrefixUnaryExpression(op, expr));
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitBinary(ast) {
- const lhs = wrapForDiagnostics(this.translate(ast.left));
- const rhs = wrapForDiagnostics(this.translate(ast.right));
- const op = BINARY_OPS.get(ast.operation);
- if (op === undefined) {
- throw new Error(`Unsupported Binary.operation: ${ast.operation}`);
- }
- const node = ts.factory.createBinaryExpression(lhs, op, rhs);
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitChain(ast) {
- const elements = ast.expressions.map((expr) => this.translate(expr));
- const node = wrapForDiagnostics(ts.factory.createCommaListExpression(elements));
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitConditional(ast) {
- const condExpr = this.translate(ast.condition);
- const trueExpr = this.translate(ast.trueExp);
- // Wrap `falseExpr` in parens so that the trailing parse span info is not attributed to the
- // whole conditional.
- // In the following example, the last source span comment (5,6) could be seen as the
- // trailing comment for _either_ the whole conditional expression _or_ just the `falseExpr` that
- // is immediately before it:
- // `conditional /*1,2*/ ? trueExpr /*3,4*/ : falseExpr /*5,6*/`
- // This should be instead be `conditional /*1,2*/ ? trueExpr /*3,4*/ : (falseExpr /*5,6*/)`
- const falseExpr = wrapForTypeChecker(this.translate(ast.falseExp));
- const node = ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression(condExpr, undefined, trueExpr, undefined, falseExpr));
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitImplicitReceiver(ast) {
- throw new Error('Method not implemented.');
- }
- visitThisReceiver(ast) {
- throw new Error('Method not implemented.');
- }
- visitInterpolation(ast) {
- // Build up a chain of binary + operations to simulate the string concatenation of the
- // interpolation's expressions. The chain is started using an actual string literal to ensure
- // the type is inferred as 'string'.
- return ast.expressions.reduce((lhs, ast) => ts.factory.createBinaryExpression(lhs, ts.SyntaxKind.PlusToken, wrapForTypeChecker(this.translate(ast))), ts.factory.createStringLiteral(''));
- }
- visitKeyedRead(ast) {
- const receiver = wrapForDiagnostics(this.translate(ast.receiver));
- const key = this.translate(ast.key);
- const node = ts.factory.createElementAccessExpression(receiver, key);
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitKeyedWrite(ast) {
- const receiver = wrapForDiagnostics(this.translate(ast.receiver));
- const left = ts.factory.createElementAccessExpression(receiver, this.translate(ast.key));
- // TODO(joost): annotate `left` with the span of the element access, which is not currently
- // available on `ast`.
- const right = wrapForTypeChecker(this.translate(ast.value));
- const node = wrapForDiagnostics(ts.factory.createBinaryExpression(left, ts.SyntaxKind.EqualsToken, right));
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitLiteralArray(ast) {
- const elements = ast.expressions.map((expr) => this.translate(expr));
- const literal = ts.factory.createArrayLiteralExpression(elements);
- // If strictLiteralTypes is disabled, array literals are cast to `any`.
- const node = this.config.strictLiteralTypes ? literal : tsCastToAny(literal);
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitLiteralMap(ast) {
- const properties = ast.keys.map(({ key }, idx) => {
- const value = this.translate(ast.values[idx]);
- return ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(key), value);
- });
- const literal = ts.factory.createObjectLiteralExpression(properties, true);
- // If strictLiteralTypes is disabled, object literals are cast to `any`.
- const node = this.config.strictLiteralTypes ? literal : tsCastToAny(literal);
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitLiteralPrimitive(ast) {
- let node;
- if (ast.value === undefined) {
- node = ts.factory.createIdentifier('undefined');
- }
- else if (ast.value === null) {
- node = ts.factory.createNull();
- }
- else if (typeof ast.value === 'string') {
- node = ts.factory.createStringLiteral(ast.value);
- }
- else if (typeof ast.value === 'number') {
- node = tsNumericExpression(ast.value);
- }
- else if (typeof ast.value === 'boolean') {
- node = ast.value ? ts.factory.createTrue() : ts.factory.createFalse();
- }
- else {
- throw Error(`Unsupported AST value of type ${typeof ast.value}`);
- }
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitNonNullAssert(ast) {
- const expr = wrapForDiagnostics(this.translate(ast.expression));
- const node = ts.factory.createNonNullExpression(expr);
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitPipe(ast) {
- throw new Error('Method not implemented.');
- }
- visitPrefixNot(ast) {
- const expression = wrapForDiagnostics(this.translate(ast.expression));
- const node = ts.factory.createLogicalNot(expression);
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitTypeofExpression(ast) {
- const expression = wrapForDiagnostics(this.translate(ast.expression));
- const node = ts.factory.createTypeOfExpression(expression);
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitPropertyRead(ast) {
- // This is a normal property read - convert the receiver to an expression and emit the correct
- // TypeScript expression to read the property.
- const receiver = wrapForDiagnostics(this.translate(ast.receiver));
- const name = ts.factory.createPropertyAccessExpression(receiver, ast.name);
- addParseSpanInfo(name, ast.nameSpan);
- const node = wrapForDiagnostics(name);
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitPropertyWrite(ast) {
- const receiver = wrapForDiagnostics(this.translate(ast.receiver));
- const left = ts.factory.createPropertyAccessExpression(receiver, ast.name);
- addParseSpanInfo(left, ast.nameSpan);
- // TypeScript reports assignment errors on the entire lvalue expression. Annotate the lvalue of
- // the assignment with the sourceSpan, which includes receivers, rather than nameSpan for
- // consistency of the diagnostic location.
- // a.b.c = 1
- // ^^^^^^^^^ sourceSpan
- // ^ nameSpan
- const leftWithPath = wrapForDiagnostics(left);
- addParseSpanInfo(leftWithPath, ast.sourceSpan);
- // The right needs to be wrapped in parens as well or we cannot accurately match its
- // span to just the RHS. For example, the span in `e = $event /*0,10*/` is ambiguous.
- // It could refer to either the whole binary expression or just the RHS.
- // We should instead generate `e = ($event /*0,10*/)` so we know the span 0,10 matches RHS.
- const right = wrapForTypeChecker(this.translate(ast.value));
- const node = wrapForDiagnostics(ts.factory.createBinaryExpression(leftWithPath, ts.SyntaxKind.EqualsToken, right));
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitSafePropertyRead(ast) {
- let node;
- const receiver = wrapForDiagnostics(this.translate(ast.receiver));
- // The form of safe property reads depends on whether strictness is in use.
- if (this.config.strictSafeNavigationTypes) {
- // Basically, the return here is either the type of the complete expression with a null-safe
- // property read, or `undefined`. So a ternary is used to create an "or" type:
- // "a?.b" becomes (0 as any ? a!.b : undefined)
- // The type of this expression is (typeof a!.b) | undefined, which is exactly as desired.
- const expr = ts.factory.createPropertyAccessExpression(ts.factory.createNonNullExpression(receiver), ast.name);
- addParseSpanInfo(expr, ast.nameSpan);
- node = ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression(ANY_EXPRESSION, undefined, expr, undefined, UNDEFINED));
- }
- else if (VeSafeLhsInferenceBugDetector.veWillInferAnyFor(ast)) {
- // Emulate a View Engine bug where 'any' is inferred for the left-hand side of the safe
- // navigation operation. With this bug, the type of the left-hand side is regarded as any.
- // Therefore, the left-hand side only needs repeating in the output (to validate it), and then
- // 'any' is used for the rest of the expression. This is done using a comma operator:
- // "a?.b" becomes (a as any).b, which will of course have type 'any'.
- node = ts.factory.createPropertyAccessExpression(tsCastToAny(receiver), ast.name);
- }
- else {
- // The View Engine bug isn't active, so check the entire type of the expression, but the final
- // result is still inferred as `any`.
- // "a?.b" becomes (a!.b as any)
- const expr = ts.factory.createPropertyAccessExpression(ts.factory.createNonNullExpression(receiver), ast.name);
- addParseSpanInfo(expr, ast.nameSpan);
- node = tsCastToAny(expr);
- }
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitSafeKeyedRead(ast) {
- const receiver = wrapForDiagnostics(this.translate(ast.receiver));
- const key = this.translate(ast.key);
- let node;
- // The form of safe property reads depends on whether strictness is in use.
- if (this.config.strictSafeNavigationTypes) {
- // "a?.[...]" becomes (0 as any ? a![...] : undefined)
- const expr = ts.factory.createElementAccessExpression(ts.factory.createNonNullExpression(receiver), key);
- addParseSpanInfo(expr, ast.sourceSpan);
- node = ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression(ANY_EXPRESSION, undefined, expr, undefined, UNDEFINED));
- }
- else if (VeSafeLhsInferenceBugDetector.veWillInferAnyFor(ast)) {
- // "a?.[...]" becomes (a as any)[...]
- node = ts.factory.createElementAccessExpression(tsCastToAny(receiver), key);
- }
- else {
- // "a?.[...]" becomes (a!.[...] as any)
- const expr = ts.factory.createElementAccessExpression(ts.factory.createNonNullExpression(receiver), key);
- addParseSpanInfo(expr, ast.sourceSpan);
- node = tsCastToAny(expr);
- }
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitCall(ast) {
- const args = ast.args.map((expr) => this.translate(expr));
- let expr;
- const receiver = ast.receiver;
- // For calls that have a property read as receiver, we have to special-case their emit to avoid
- // inserting superfluous parenthesis as they prevent TypeScript from applying a narrowing effect
- // if the method acts as a type guard.
- if (receiver instanceof PropertyRead) {
- const resolved = this.maybeResolve(receiver);
- if (resolved !== null) {
- expr = resolved;
- }
- else {
- const propertyReceiver = wrapForDiagnostics(this.translate(receiver.receiver));
- expr = ts.factory.createPropertyAccessExpression(propertyReceiver, receiver.name);
- addParseSpanInfo(expr, receiver.nameSpan);
- }
- }
- else {
- expr = this.translate(receiver);
- }
- let node;
- // Safe property/keyed reads will produce a ternary whose value is nullable.
- // We have to generate a similar ternary around the call.
- if (ast.receiver instanceof SafePropertyRead || ast.receiver instanceof SafeKeyedRead) {
- node = this.convertToSafeCall(ast, expr, args);
- }
- else {
- node = ts.factory.createCallExpression(expr, undefined, args);
- }
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitSafeCall(ast) {
- const args = ast.args.map((expr) => this.translate(expr));
- const expr = wrapForDiagnostics(this.translate(ast.receiver));
- const node = this.convertToSafeCall(ast, expr, args);
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- visitTemplateLiteral(ast) {
- const length = ast.elements.length;
- const head = ast.elements[0];
- let result;
- if (length === 1) {
- result = ts.factory.createNoSubstitutionTemplateLiteral(head.text);
- }
- else {
- const spans = [];
- const tailIndex = length - 1;
- for (let i = 1; i < tailIndex; i++) {
- const middle = ts.factory.createTemplateMiddle(ast.elements[i].text);
- spans.push(ts.factory.createTemplateSpan(this.translate(ast.expressions[i - 1]), middle));
- }
- const resolvedExpression = this.translate(ast.expressions[tailIndex - 1]);
- const templateTail = ts.factory.createTemplateTail(ast.elements[tailIndex].text);
- spans.push(ts.factory.createTemplateSpan(resolvedExpression, templateTail));
- result = ts.factory.createTemplateExpression(ts.factory.createTemplateHead(head.text), spans);
- }
- return result;
- }
- visitTemplateLiteralElement(ast, context) {
- throw new Error('Method not implemented');
- }
- convertToSafeCall(ast, expr, args) {
- if (this.config.strictSafeNavigationTypes) {
- // "a?.method(...)" becomes (0 as any ? a!.method(...) : undefined)
- const call = ts.factory.createCallExpression(ts.factory.createNonNullExpression(expr), undefined, args);
- return ts.factory.createParenthesizedExpression(ts.factory.createConditionalExpression(ANY_EXPRESSION, undefined, call, undefined, UNDEFINED));
- }
- if (VeSafeLhsInferenceBugDetector.veWillInferAnyFor(ast)) {
- // "a?.method(...)" becomes (a as any).method(...)
- return ts.factory.createCallExpression(tsCastToAny(expr), undefined, args);
- }
- // "a?.method(...)" becomes (a!.method(...) as any)
- return tsCastToAny(ts.factory.createCallExpression(ts.factory.createNonNullExpression(expr), undefined, args));
- }
- }
- /**
- * Checks whether View Engine will infer a type of 'any' for the left-hand side of a safe navigation
- * operation.
- *
- * In View Engine's template type-checker, certain receivers of safe navigation operations will
- * cause a temporary variable to be allocated as part of the checking expression, to save the value
- * of the receiver and use it more than once in the expression. This temporary variable has type
- * 'any'. In practice, this means certain receivers cause View Engine to not check the full
- * expression, and other receivers will receive more complete checking.
- *
- * For compatibility, this logic is adapted from View Engine's expression_converter.ts so that the
- * Ivy checker can emulate this bug when needed.
- */
- class VeSafeLhsInferenceBugDetector {
- static SINGLETON = new VeSafeLhsInferenceBugDetector();
- static veWillInferAnyFor(ast) {
- const visitor = VeSafeLhsInferenceBugDetector.SINGLETON;
- return ast instanceof Call ? ast.visit(visitor) : ast.receiver.visit(visitor);
- }
- visitUnary(ast) {
- return ast.expr.visit(this);
- }
- visitBinary(ast) {
- return ast.left.visit(this) || ast.right.visit(this);
- }
- visitChain(ast) {
- return false;
- }
- visitConditional(ast) {
- return ast.condition.visit(this) || ast.trueExp.visit(this) || ast.falseExp.visit(this);
- }
- visitCall(ast) {
- return true;
- }
- visitSafeCall(ast) {
- return false;
- }
- visitImplicitReceiver(ast) {
- return false;
- }
- visitThisReceiver(ast) {
- return false;
- }
- visitInterpolation(ast) {
- return ast.expressions.some((exp) => exp.visit(this));
- }
- visitKeyedRead(ast) {
- return false;
- }
- visitKeyedWrite(ast) {
- return false;
- }
- visitLiteralArray(ast) {
- return true;
- }
- visitLiteralMap(ast) {
- return true;
- }
- visitLiteralPrimitive(ast) {
- return false;
- }
- visitPipe(ast) {
- return true;
- }
- visitPrefixNot(ast) {
- return ast.expression.visit(this);
- }
- visitTypeofExpression(ast) {
- return ast.expression.visit(this);
- }
- visitNonNullAssert(ast) {
- return ast.expression.visit(this);
- }
- visitPropertyRead(ast) {
- return false;
- }
- visitPropertyWrite(ast) {
- return false;
- }
- visitSafePropertyRead(ast) {
- return false;
- }
- visitSafeKeyedRead(ast) {
- return false;
- }
- visitTemplateLiteral(ast, context) {
- return false;
- }
- visitTemplateLiteralElement(ast, context) {
- return false;
- }
- }
- /**
- * Controls how generics for the component context class will be handled during TCB generation.
- */
- var TcbGenericContextBehavior;
- (function (TcbGenericContextBehavior) {
- /**
- * References to generic parameter bounds will be emitted via the `TypeParameterEmitter`.
- *
- * The caller must verify that all parameter bounds are emittable in order to use this mode.
- */
- TcbGenericContextBehavior[TcbGenericContextBehavior["UseEmitter"] = 0] = "UseEmitter";
- /**
- * Generic parameter declarations will be copied directly from the `ts.ClassDeclaration` of the
- * component class.
- *
- * The caller must only use the generated TCB code in a context where such copies will still be
- * valid, such as an inline type check block.
- */
- TcbGenericContextBehavior[TcbGenericContextBehavior["CopyClassNodes"] = 1] = "CopyClassNodes";
- /**
- * Any generic parameters for the component context class will be set to `any`.
- *
- * Produces a less useful type, but is always safe to use.
- */
- TcbGenericContextBehavior[TcbGenericContextBehavior["FallbackToAny"] = 2] = "FallbackToAny";
- })(TcbGenericContextBehavior || (TcbGenericContextBehavior = {}));
- /**
- * Given a `ts.ClassDeclaration` for a component, and metadata regarding that component, compose a
- * "type check block" function.
- *
- * When passed through TypeScript's TypeChecker, type errors that arise within the type check block
- * function indicate issues in the template itself.
- *
- * As a side effect of generating a TCB for the component, `ts.Diagnostic`s may also be produced
- * directly for issues within the template which are identified during generation. These issues are
- * recorded in either the `domSchemaChecker` (which checks usage of DOM elements and bindings) as
- * well as the `oobRecorder` (which records errors when the type-checking code generator is unable
- * to sufficiently understand a template).
- *
- * @param env an `Environment` into which type-checking code will be generated.
- * @param ref a `Reference` to the component class which should be type-checked.
- * @param name a `ts.Identifier` to use for the generated `ts.FunctionDeclaration`.
- * @param meta metadata about the component's template and the function being generated.
- * @param domSchemaChecker used to check and record errors regarding improper usage of DOM elements
- * and bindings.
- * @param oobRecorder used to record errors regarding template elements which could not be correctly
- * translated into types during TCB generation.
- * @param genericContextBehavior controls how generic parameters (especially parameters with generic
- * bounds) will be referenced from the generated TCB code.
- */
- function generateTypeCheckBlock(env, ref, name, meta, domSchemaChecker, oobRecorder, genericContextBehavior) {
- const tcb = new Context(env, domSchemaChecker, oobRecorder, meta.id, meta.boundTarget, meta.pipes, meta.schemas, meta.isStandalone, meta.preserveWhitespaces);
- const scope = Scope.forNodes(tcb, null, null, tcb.boundTarget.target.template, /* guard */ null);
- const ctxRawType = env.referenceType(ref);
- if (!ts.isTypeReferenceNode(ctxRawType)) {
- throw new Error(`Expected TypeReferenceNode when referencing the ctx param for ${ref.debugName}`);
- }
- let typeParameters = undefined;
- let typeArguments = undefined;
- if (ref.node.typeParameters !== undefined) {
- if (!env.config.useContextGenericType) {
- genericContextBehavior = TcbGenericContextBehavior.FallbackToAny;
- }
- switch (genericContextBehavior) {
- case TcbGenericContextBehavior.UseEmitter:
- // Guaranteed to emit type parameters since we checked that the class has them above.
- typeParameters = new TypeParameterEmitter(ref.node.typeParameters, env.reflector).emit((typeRef) => env.referenceType(typeRef));
- typeArguments = typeParameters.map((param) => ts.factory.createTypeReferenceNode(param.name));
- break;
- case TcbGenericContextBehavior.CopyClassNodes:
- typeParameters = [...ref.node.typeParameters];
- typeArguments = typeParameters.map((param) => ts.factory.createTypeReferenceNode(param.name));
- break;
- case TcbGenericContextBehavior.FallbackToAny:
- typeArguments = ref.node.typeParameters.map(() => ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
- break;
- }
- }
- const paramList = [tcbThisParam(ctxRawType.typeName, typeArguments)];
- const scopeStatements = scope.render();
- const innerBody = ts.factory.createBlock([...env.getPreludeStatements(), ...scopeStatements]);
- // Wrap the body in an "if (true)" expression. This is unnecessary but has the effect of causing
- // the `ts.Printer` to format the type-check block nicely.
- const body = ts.factory.createBlock([
- ts.factory.createIfStatement(ts.factory.createTrue(), innerBody, undefined),
- ]);
- const fnDecl = ts.factory.createFunctionDeclaration(
- /* modifiers */ undefined,
- /* asteriskToken */ undefined,
- /* name */ name,
- /* typeParameters */ env.config.useContextGenericType ? typeParameters : undefined,
- /* parameters */ paramList,
- /* type */ undefined,
- /* body */ body);
- addTypeCheckId(fnDecl, meta.id);
- return fnDecl;
- }
- /**
- * A code generation operation that's involved in the construction of a Type Check Block.
- *
- * The generation of a TCB is non-linear. Bindings within a template may result in the need to
- * construct certain types earlier than they otherwise would be constructed. That is, if the
- * generation of a TCB for a template is broken down into specific operations (constructing a
- * directive, extracting a variable from a let- operation, etc), then it's possible for operations
- * earlier in the sequence to depend on operations which occur later in the sequence.
- *
- * `TcbOp` abstracts the different types of operations which are required to convert a template into
- * a TCB. This allows for two phases of processing for the template, where 1) a linear sequence of
- * `TcbOp`s is generated, and then 2) these operations are executed, not necessarily in linear
- * order.
- *
- * Each `TcbOp` may insert statements into the body of the TCB, and also optionally return a
- * `ts.Expression` which can be used to reference the operation's result.
- */
- class TcbOp {
- /**
- * Replacement value or operation used while this `TcbOp` is executing (i.e. to resolve circular
- * references during its execution).
- *
- * This is usually a `null!` expression (which asks TS to infer an appropriate type), but another
- * `TcbOp` can be returned in cases where additional code generation is necessary to deal with
- * circular references.
- */
- circularFallback() {
- return INFER_TYPE_FOR_CIRCULAR_OP_EXPR;
- }
- }
- /**
- * A `TcbOp` which creates an expression for a native DOM element (or web component) from a
- * `TmplAstElement`.
- *
- * Executing this operation returns a reference to the element variable.
- */
- class TcbElementOp extends TcbOp {
- tcb;
- scope;
- element;
- constructor(tcb, scope, element) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.element = element;
- }
- get optional() {
- // The statement generated by this operation is only used for type-inference of the DOM
- // element's type and won't report diagnostics by itself, so the operation is marked as optional
- // to avoid generating statements for DOM elements that are never referenced.
- return true;
- }
- execute() {
- const id = this.tcb.allocateId();
- // Add the declaration of the element using document.createElement.
- const initializer = tsCreateElement(this.element.name);
- addParseSpanInfo(initializer, this.element.startSourceSpan || this.element.sourceSpan);
- this.scope.addStatement(tsCreateVariable(id, initializer));
- return id;
- }
- }
- /**
- * A `TcbOp` which creates an expression for particular let- `TmplAstVariable` on a
- * `TmplAstTemplate`'s context.
- *
- * Executing this operation returns a reference to the variable variable (lol).
- */
- class TcbTemplateVariableOp extends TcbOp {
- tcb;
- scope;
- template;
- variable;
- constructor(tcb, scope, template, variable) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.template = template;
- this.variable = variable;
- }
- get optional() {
- return false;
- }
- execute() {
- // Look for a context variable for the template.
- const ctx = this.scope.resolve(this.template);
- // Allocate an identifier for the TmplAstVariable, and initialize it to a read of the variable
- // on the template context.
- const id = this.tcb.allocateId();
- const initializer = ts.factory.createPropertyAccessExpression(
- /* expression */ ctx,
- /* name */ this.variable.value || '$implicit');
- addParseSpanInfo(id, this.variable.keySpan);
- // Declare the variable, and return its identifier.
- let variable;
- if (this.variable.valueSpan !== undefined) {
- addParseSpanInfo(initializer, this.variable.valueSpan);
- variable = tsCreateVariable(id, wrapForTypeChecker(initializer));
- }
- else {
- variable = tsCreateVariable(id, initializer);
- }
- addParseSpanInfo(variable.declarationList.declarations[0], this.variable.sourceSpan);
- this.scope.addStatement(variable);
- return id;
- }
- }
- /**
- * A `TcbOp` which generates a variable for a `TmplAstTemplate`'s context.
- *
- * Executing this operation returns a reference to the template's context variable.
- */
- class TcbTemplateContextOp extends TcbOp {
- tcb;
- scope;
- constructor(tcb, scope) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- }
- // The declaration of the context variable is only needed when the context is actually referenced.
- optional = true;
- execute() {
- // Allocate a template ctx variable and declare it with an 'any' type. The type of this variable
- // may be narrowed as a result of template guard conditions.
- const ctx = this.tcb.allocateId();
- const type = ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
- this.scope.addStatement(tsDeclareVariable(ctx, type));
- return ctx;
- }
- }
- /**
- * A `TcbOp` which generates a constant for a `TmplAstLetDeclaration`.
- *
- * Executing this operation returns a reference to the `@let` declaration.
- */
- class TcbLetDeclarationOp extends TcbOp {
- tcb;
- scope;
- node;
- constructor(tcb, scope, node) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.node = node;
- }
- /**
- * `@let` declarations are mandatory, because their expressions
- * should be checked even if they aren't referenced anywhere.
- */
- optional = false;
- execute() {
- const id = this.tcb.allocateId();
- addParseSpanInfo(id, this.node.nameSpan);
- const value = tcbExpression(this.node.value, this.tcb, this.scope);
- // Value needs to be wrapped, because spans for the expressions inside of it can
- // be picked up incorrectly as belonging to the full variable declaration.
- const varStatement = tsCreateVariable(id, wrapForTypeChecker(value), ts.NodeFlags.Const);
- addParseSpanInfo(varStatement.declarationList.declarations[0], this.node.sourceSpan);
- this.scope.addStatement(varStatement);
- return id;
- }
- }
- /**
- * A `TcbOp` which descends into a `TmplAstTemplate`'s children and generates type-checking code for
- * them.
- *
- * This operation wraps the children's type-checking code in an `if` block, which may include one
- * or more type guard conditions that narrow types within the template body.
- */
- class TcbTemplateBodyOp extends TcbOp {
- tcb;
- scope;
- template;
- constructor(tcb, scope, template) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.template = template;
- }
- get optional() {
- return false;
- }
- execute() {
- // An `if` will be constructed, within which the template's children will be type checked. The
- // `if` is used for two reasons: it creates a new syntactic scope, isolating variables declared
- // in the template's TCB from the outer context, and it allows any directives on the templates
- // to perform type narrowing of either expressions or the template's context.
- //
- // The guard is the `if` block's condition. It's usually set to `true` but directives that exist
- // on the template can trigger extra guard expressions that serve to narrow types within the
- // `if`. `guard` is calculated by starting with `true` and adding other conditions as needed.
- // Collect these into `guards` by processing the directives.
- const directiveGuards = [];
- const directives = this.tcb.boundTarget.getDirectivesOfNode(this.template);
- if (directives !== null) {
- for (const dir of directives) {
- const dirInstId = this.scope.resolve(this.template, dir);
- const dirId = this.tcb.env.reference(dir.ref);
- // There are two kinds of guards. Template guards (ngTemplateGuards) allow type narrowing of
- // the expression passed to an @Input of the directive. Scan the directive to see if it has
- // any template guards, and generate them if needed.
- dir.ngTemplateGuards.forEach((guard) => {
- // For each template guard function on the directive, look for a binding to that input.
- const boundInput = this.template.inputs.find((i) => i.name === guard.inputName) ||
- this.template.templateAttrs.find((i) => i instanceof BoundAttribute && i.name === guard.inputName);
- if (boundInput !== undefined) {
- // If there is such a binding, generate an expression for it.
- const expr = tcbExpression(boundInput.value, this.tcb, this.scope);
- // The expression has already been checked in the type constructor invocation, so
- // it should be ignored when used within a template guard.
- markIgnoreDiagnostics(expr);
- if (guard.type === 'binding') {
- // Use the binding expression itself as guard.
- directiveGuards.push(expr);
- }
- else {
- // Call the guard function on the directive with the directive instance and that
- // expression.
- const guardInvoke = tsCallMethod(dirId, `ngTemplateGuard_${guard.inputName}`, [
- dirInstId,
- expr,
- ]);
- addParseSpanInfo(guardInvoke, boundInput.value.sourceSpan);
- directiveGuards.push(guardInvoke);
- }
- }
- });
- // The second kind of guard is a template context guard. This guard narrows the template
- // rendering context variable `ctx`.
- if (dir.hasNgTemplateContextGuard) {
- if (this.tcb.env.config.applyTemplateContextGuards) {
- const ctx = this.scope.resolve(this.template);
- const guardInvoke = tsCallMethod(dirId, 'ngTemplateContextGuard', [dirInstId, ctx]);
- addParseSpanInfo(guardInvoke, this.template.sourceSpan);
- directiveGuards.push(guardInvoke);
- }
- else if (this.template.variables.length > 0 &&
- this.tcb.env.config.suggestionsForSuboptimalTypeInference) {
- // The compiler could have inferred a better type for the variables in this template,
- // but was prevented from doing so by the type-checking configuration. Issue a warning
- // diagnostic.
- this.tcb.oobRecorder.suboptimalTypeInference(this.tcb.id, this.template.variables);
- }
- }
- }
- }
- // By default the guard is simply `true`.
- let guard = null;
- // If there are any guards from directives, use them instead.
- if (directiveGuards.length > 0) {
- // Pop the first value and use it as the initializer to reduce(). This way, a single guard
- // will be used on its own, but two or more will be combined into binary AND expressions.
- guard = directiveGuards.reduce((expr, dirGuard) => ts.factory.createBinaryExpression(expr, ts.SyntaxKind.AmpersandAmpersandToken, dirGuard), directiveGuards.pop());
- }
- // Create a new Scope for the template. This constructs the list of operations for the template
- // children, as well as tracks bindings within the template.
- const tmplScope = Scope.forNodes(this.tcb, this.scope, this.template, this.template.children, guard);
- // Render the template's `Scope` into its statements.
- const statements = tmplScope.render();
- if (statements.length === 0) {
- // As an optimization, don't generate the scope's block if it has no statements. This is
- // beneficial for templates that contain for example `<span *ngIf="first"></span>`, in which
- // case there's no need to render the `NgIf` guard expression. This seems like a minor
- // improvement, however it reduces the number of flow-node antecedents that TypeScript needs
- // to keep into account for such cases, resulting in an overall reduction of
- // type-checking time.
- return null;
- }
- let tmplBlock = ts.factory.createBlock(statements);
- if (guard !== null) {
- // The scope has a guard that needs to be applied, so wrap the template block into an `if`
- // statement containing the guard expression.
- tmplBlock = ts.factory.createIfStatement(
- /* expression */ guard,
- /* thenStatement */ tmplBlock);
- }
- this.scope.addStatement(tmplBlock);
- return null;
- }
- }
- /**
- * A `TcbOp` which renders an Angular expression (e.g. `{{foo() && bar.baz}}`).
- *
- * Executing this operation returns nothing.
- */
- class TcbExpressionOp extends TcbOp {
- tcb;
- scope;
- expression;
- constructor(tcb, scope, expression) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.expression = expression;
- }
- get optional() {
- return false;
- }
- execute() {
- const expr = tcbExpression(this.expression, this.tcb, this.scope);
- this.scope.addStatement(ts.factory.createExpressionStatement(expr));
- return null;
- }
- }
- /**
- * A `TcbOp` which constructs an instance of a directive. For generic directives, generic
- * parameters are set to `any` type.
- */
- class TcbDirectiveTypeOpBase extends TcbOp {
- tcb;
- scope;
- node;
- dir;
- constructor(tcb, scope, node, dir) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.node = node;
- this.dir = dir;
- }
- get optional() {
- // The statement generated by this operation is only used to declare the directive's type and
- // won't report diagnostics by itself, so the operation is marked as optional to avoid
- // generating declarations for directives that don't have any inputs/outputs.
- return true;
- }
- execute() {
- const dirRef = this.dir.ref;
- const rawType = this.tcb.env.referenceType(this.dir.ref);
- let type;
- if (this.dir.isGeneric === false || dirRef.node.typeParameters === undefined) {
- type = rawType;
- }
- else {
- if (!ts.isTypeReferenceNode(rawType)) {
- throw new Error(`Expected TypeReferenceNode when referencing the type for ${this.dir.ref.debugName}`);
- }
- const typeArguments = dirRef.node.typeParameters.map(() => ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
- type = ts.factory.createTypeReferenceNode(rawType.typeName, typeArguments);
- }
- const id = this.tcb.allocateId();
- addExpressionIdentifier(id, ExpressionIdentifier.DIRECTIVE);
- addParseSpanInfo(id, this.node.startSourceSpan || this.node.sourceSpan);
- this.scope.addStatement(tsDeclareVariable(id, type));
- return id;
- }
- }
- /**
- * A `TcbOp` which constructs an instance of a non-generic directive _without_ setting any of its
- * inputs. Inputs are later set in the `TcbDirectiveInputsOp`. Type checking was found to be
- * faster when done in this way as opposed to `TcbDirectiveCtorOp` which is only necessary when the
- * directive is generic.
- *
- * Executing this operation returns a reference to the directive instance variable with its inferred
- * type.
- */
- class TcbNonGenericDirectiveTypeOp extends TcbDirectiveTypeOpBase {
- /**
- * Creates a variable declaration for this op's directive of the argument type. Returns the id of
- * the newly created variable.
- */
- execute() {
- const dirRef = this.dir.ref;
- if (this.dir.isGeneric) {
- throw new Error(`Assertion Error: expected ${dirRef.debugName} not to be generic.`);
- }
- return super.execute();
- }
- }
- /**
- * A `TcbOp` which constructs an instance of a generic directive with its generic parameters set
- * to `any` type. This op is like `TcbDirectiveTypeOp`, except that generic parameters are set to
- * `any` type. This is used for situations where we want to avoid inlining.
- *
- * Executing this operation returns a reference to the directive instance variable with its generic
- * type parameters set to `any`.
- */
- class TcbGenericDirectiveTypeWithAnyParamsOp extends TcbDirectiveTypeOpBase {
- execute() {
- const dirRef = this.dir.ref;
- if (dirRef.node.typeParameters === undefined) {
- throw new Error(`Assertion Error: expected typeParameters when creating a declaration for ${dirRef.debugName}`);
- }
- return super.execute();
- }
- }
- /**
- * A `TcbOp` which creates a variable for a local ref in a template.
- * The initializer for the variable is the variable expression for the directive, template, or
- * element the ref refers to. When the reference is used in the template, those TCB statements will
- * access this variable as well. For example:
- * ```ts
- * var _t1 = document.createElement('div');
- * var _t2 = _t1;
- * _t2.value
- * ```
- * This operation supports more fluent lookups for the `TemplateTypeChecker` when getting a symbol
- * for a reference. In most cases, this isn't essential; that is, the information for the symbol
- * could be gathered without this operation using the `BoundTarget`. However, for the case of
- * ng-template references, we will need this reference variable to not only provide a location in
- * the shim file, but also to narrow the variable to the correct `TemplateRef<T>` type rather than
- * `TemplateRef<any>` (this work is still TODO).
- *
- * Executing this operation returns a reference to the directive instance variable with its inferred
- * type.
- */
- class TcbReferenceOp extends TcbOp {
- tcb;
- scope;
- node;
- host;
- target;
- constructor(tcb, scope, node, host, target) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.node = node;
- this.host = host;
- this.target = target;
- }
- // The statement generated by this operation is only used to for the Type Checker
- // so it can map a reference variable in the template directly to a node in the TCB.
- optional = true;
- execute() {
- const id = this.tcb.allocateId();
- let initializer = this.target instanceof Template || this.target instanceof Element$1
- ? this.scope.resolve(this.target)
- : this.scope.resolve(this.host, this.target);
- // The reference is either to an element, an <ng-template> node, or to a directive on an
- // element or template.
- if ((this.target instanceof Element$1 && !this.tcb.env.config.checkTypeOfDomReferences) ||
- !this.tcb.env.config.checkTypeOfNonDomReferences) {
- // References to DOM nodes are pinned to 'any' when `checkTypeOfDomReferences` is `false`.
- // References to `TemplateRef`s and directives are pinned to 'any' when
- // `checkTypeOfNonDomReferences` is `false`.
- initializer = ts.factory.createAsExpression(initializer, ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
- }
- else if (this.target instanceof Template) {
- // Direct references to an <ng-template> node simply require a value of type
- // `TemplateRef<any>`. To get this, an expression of the form
- // `(_t1 as any as TemplateRef<any>)` is constructed.
- initializer = ts.factory.createAsExpression(initializer, ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
- initializer = ts.factory.createAsExpression(initializer, this.tcb.env.referenceExternalType('@angular/core', 'TemplateRef', [DYNAMIC_TYPE]));
- initializer = ts.factory.createParenthesizedExpression(initializer);
- }
- addParseSpanInfo(initializer, this.node.sourceSpan);
- addParseSpanInfo(id, this.node.keySpan);
- this.scope.addStatement(tsCreateVariable(id, initializer));
- return id;
- }
- }
- /**
- * A `TcbOp` which is used when the target of a reference is missing. This operation generates a
- * variable of type any for usages of the invalid reference to resolve to. The invalid reference
- * itself is recorded out-of-band.
- */
- class TcbInvalidReferenceOp extends TcbOp {
- tcb;
- scope;
- constructor(tcb, scope) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- }
- // The declaration of a missing reference is only needed when the reference is resolved.
- optional = true;
- execute() {
- const id = this.tcb.allocateId();
- this.scope.addStatement(tsCreateVariable(id, ANY_EXPRESSION));
- return id;
- }
- }
- /**
- * A `TcbOp` which constructs an instance of a directive with types inferred from its inputs. The
- * inputs themselves are not checked here; checking of inputs is achieved in `TcbDirectiveInputsOp`.
- * Any errors reported in this statement are ignored, as the type constructor call is only present
- * for type-inference.
- *
- * When a Directive is generic, it is required that the TCB generates the instance using this method
- * in order to infer the type information correctly.
- *
- * Executing this operation returns a reference to the directive instance variable with its inferred
- * type.
- */
- class TcbDirectiveCtorOp extends TcbOp {
- tcb;
- scope;
- node;
- dir;
- constructor(tcb, scope, node, dir) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.node = node;
- this.dir = dir;
- }
- get optional() {
- // The statement generated by this operation is only used to infer the directive's type and
- // won't report diagnostics by itself, so the operation is marked as optional.
- return true;
- }
- execute() {
- const id = this.tcb.allocateId();
- addExpressionIdentifier(id, ExpressionIdentifier.DIRECTIVE);
- addParseSpanInfo(id, this.node.startSourceSpan || this.node.sourceSpan);
- const genericInputs = new Map();
- const boundAttrs = getBoundAttributes(this.dir, this.node);
- for (const attr of boundAttrs) {
- // Skip text attributes if configured to do so.
- if (!this.tcb.env.config.checkTypeOfAttributes &&
- attr.attribute instanceof TextAttribute) {
- continue;
- }
- for (const { fieldName, isTwoWayBinding } of attr.inputs) {
- // Skip the field if an attribute has already been bound to it; we can't have a duplicate
- // key in the type constructor call.
- if (genericInputs.has(fieldName)) {
- continue;
- }
- const expression = translateInput(attr.attribute, this.tcb, this.scope);
- genericInputs.set(fieldName, {
- type: 'binding',
- field: fieldName,
- expression,
- sourceSpan: attr.attribute.sourceSpan,
- isTwoWayBinding,
- });
- }
- }
- // Add unset directive inputs for each of the remaining unset fields.
- for (const { classPropertyName } of this.dir.inputs) {
- if (!genericInputs.has(classPropertyName)) {
- genericInputs.set(classPropertyName, { type: 'unset', field: classPropertyName });
- }
- }
- // Call the type constructor of the directive to infer a type, and assign the directive
- // instance.
- const typeCtor = tcbCallTypeCtor(this.dir, this.tcb, Array.from(genericInputs.values()));
- markIgnoreDiagnostics(typeCtor);
- this.scope.addStatement(tsCreateVariable(id, typeCtor));
- return id;
- }
- circularFallback() {
- return new TcbDirectiveCtorCircularFallbackOp(this.tcb, this.scope, this.node, this.dir);
- }
- }
- /**
- * A `TcbOp` which generates code to check input bindings on an element that correspond with the
- * members of a directive.
- *
- * Executing this operation returns nothing.
- */
- class TcbDirectiveInputsOp extends TcbOp {
- tcb;
- scope;
- node;
- dir;
- constructor(tcb, scope, node, dir) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.node = node;
- this.dir = dir;
- }
- get optional() {
- return false;
- }
- execute() {
- let dirId = null;
- // TODO(joost): report duplicate properties
- const boundAttrs = getBoundAttributes(this.dir, this.node);
- const seenRequiredInputs = new Set();
- for (const attr of boundAttrs) {
- // For bound inputs, the property is assigned the binding expression.
- const expr = widenBinding(translateInput(attr.attribute, this.tcb, this.scope), this.tcb);
- let assignment = wrapForDiagnostics(expr);
- for (const { fieldName, required, transformType, isSignal, isTwoWayBinding } of attr.inputs) {
- let target;
- if (required) {
- seenRequiredInputs.add(fieldName);
- }
- // Note: There is no special logic for transforms/coercion with signal inputs.
- // For signal inputs, a `transformType` will never be set as we do not capture
- // the transform in the compiler metadata. Signal inputs incorporate their
- // transform write type into their member type, and we extract it below when
- // setting the `WriteT` of such `InputSignalWithTransform<_, WriteT>`.
- if (this.dir.coercedInputFields.has(fieldName)) {
- let type;
- if (transformType !== null) {
- type = this.tcb.env.referenceTransplantedType(new TransplantedType(transformType));
- }
- else {
- // The input has a coercion declaration which should be used instead of assigning the
- // expression into the input field directly. To achieve this, a variable is declared
- // with a type of `typeof Directive.ngAcceptInputType_fieldName` which is then used as
- // target of the assignment.
- const dirTypeRef = this.tcb.env.referenceType(this.dir.ref);
- if (!ts.isTypeReferenceNode(dirTypeRef)) {
- throw new Error(`Expected TypeReferenceNode from reference to ${this.dir.ref.debugName}`);
- }
- type = tsCreateTypeQueryForCoercedInput(dirTypeRef.typeName, fieldName);
- }
- const id = this.tcb.allocateId();
- this.scope.addStatement(tsDeclareVariable(id, type));
- target = id;
- }
- else if (this.dir.undeclaredInputFields.has(fieldName)) {
- // If no coercion declaration is present nor is the field declared (i.e. the input is
- // declared in a `@Directive` or `@Component` decorator's `inputs` property) there is no
- // assignment target available, so this field is skipped.
- continue;
- }
- else if (!this.tcb.env.config.honorAccessModifiersForInputBindings &&
- this.dir.restrictedInputFields.has(fieldName)) {
- // If strict checking of access modifiers is disabled and the field is restricted
- // (i.e. private/protected/readonly), generate an assignment into a temporary variable
- // that has the type of the field. This achieves type-checking but circumvents the access
- // modifiers.
- if (dirId === null) {
- dirId = this.scope.resolve(this.node, this.dir);
- }
- const id = this.tcb.allocateId();
- const dirTypeRef = this.tcb.env.referenceType(this.dir.ref);
- if (!ts.isTypeReferenceNode(dirTypeRef)) {
- throw new Error(`Expected TypeReferenceNode from reference to ${this.dir.ref.debugName}`);
- }
- const type = ts.factory.createIndexedAccessTypeNode(ts.factory.createTypeQueryNode(dirId), ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(fieldName)));
- const temp = tsDeclareVariable(id, type);
- this.scope.addStatement(temp);
- target = id;
- }
- else {
- if (dirId === null) {
- dirId = this.scope.resolve(this.node, this.dir);
- }
- // To get errors assign directly to the fields on the instance, using property access
- // when possible. String literal fields may not be valid JS identifiers so we use
- // literal element access instead for those cases.
- target = this.dir.stringLiteralInputFields.has(fieldName)
- ? ts.factory.createElementAccessExpression(dirId, ts.factory.createStringLiteral(fieldName))
- : ts.factory.createPropertyAccessExpression(dirId, ts.factory.createIdentifier(fieldName));
- }
- // For signal inputs, we unwrap the target `InputSignal`. Note that
- // we intentionally do the following things:
- // 1. keep the direct access to `dir.[field]` so that modifiers are honored.
- // 2. follow the existing pattern where multiple targets assign a single expression.
- // This is a significant requirement for language service auto-completion.
- if (isSignal) {
- const inputSignalBrandWriteSymbol = this.tcb.env.referenceExternalSymbol(Identifiers.InputSignalBrandWriteType.moduleName, Identifiers.InputSignalBrandWriteType.name);
- if (!ts.isIdentifier(inputSignalBrandWriteSymbol) &&
- !ts.isPropertyAccessExpression(inputSignalBrandWriteSymbol)) {
- throw new Error(`Expected identifier or property access for reference to ${Identifiers.InputSignalBrandWriteType.name}`);
- }
- target = ts.factory.createElementAccessExpression(target, inputSignalBrandWriteSymbol);
- }
- if (attr.attribute.keySpan !== undefined) {
- addParseSpanInfo(target, attr.attribute.keySpan);
- }
- // Two-way bindings accept `T | WritableSignal<T>` so we have to unwrap the value.
- if (isTwoWayBinding && this.tcb.env.config.allowSignalsInTwoWayBindings) {
- assignment = unwrapWritableSignal(assignment, this.tcb);
- }
- // Finally the assignment is extended by assigning it into the target expression.
- assignment = ts.factory.createBinaryExpression(target, ts.SyntaxKind.EqualsToken, assignment);
- }
- addParseSpanInfo(assignment, attr.attribute.sourceSpan);
- // Ignore diagnostics for text attributes if configured to do so.
- if (!this.tcb.env.config.checkTypeOfAttributes &&
- attr.attribute instanceof TextAttribute) {
- markIgnoreDiagnostics(assignment);
- }
- this.scope.addStatement(ts.factory.createExpressionStatement(assignment));
- }
- this.checkRequiredInputs(seenRequiredInputs);
- return null;
- }
- checkRequiredInputs(seenRequiredInputs) {
- const missing = [];
- for (const input of this.dir.inputs) {
- if (input.required && !seenRequiredInputs.has(input.classPropertyName)) {
- missing.push(input.bindingPropertyName);
- }
- }
- if (missing.length > 0) {
- this.tcb.oobRecorder.missingRequiredInputs(this.tcb.id, this.node, this.dir.name, this.dir.isComponent, missing);
- }
- }
- }
- /**
- * A `TcbOp` which is used to generate a fallback expression if the inference of a directive type
- * via `TcbDirectiveCtorOp` requires a reference to its own type. This can happen using a template
- * reference:
- *
- * ```html
- * <some-cmp #ref [prop]="ref.foo"></some-cmp>
- * ```
- *
- * In this case, `TcbDirectiveCtorCircularFallbackOp` will add a second inference of the directive
- * type to the type-check block, this time calling the directive's type constructor without any
- * input expressions. This infers the widest possible supertype for the directive, which is used to
- * resolve any recursive references required to infer the real type.
- */
- class TcbDirectiveCtorCircularFallbackOp extends TcbOp {
- tcb;
- scope;
- node;
- dir;
- constructor(tcb, scope, node, dir) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.node = node;
- this.dir = dir;
- }
- get optional() {
- return false;
- }
- execute() {
- const id = this.tcb.allocateId();
- const typeCtor = this.tcb.env.typeCtorFor(this.dir);
- const circularPlaceholder = ts.factory.createCallExpression(typeCtor,
- /* typeArguments */ undefined, [ts.factory.createNonNullExpression(ts.factory.createNull())]);
- this.scope.addStatement(tsCreateVariable(id, circularPlaceholder));
- return id;
- }
- }
- /**
- * A `TcbOp` which feeds elements and unclaimed properties to the `DomSchemaChecker`.
- *
- * The DOM schema is not checked via TCB code generation. Instead, the `DomSchemaChecker` ingests
- * elements and property bindings and accumulates synthetic `ts.Diagnostic`s out-of-band. These are
- * later merged with the diagnostics generated from the TCB.
- *
- * For convenience, the TCB iteration of the template is used to drive the `DomSchemaChecker` via
- * the `TcbDomSchemaCheckerOp`.
- */
- class TcbDomSchemaCheckerOp extends TcbOp {
- tcb;
- element;
- checkElement;
- claimedInputs;
- constructor(tcb, element, checkElement, claimedInputs) {
- super();
- this.tcb = tcb;
- this.element = element;
- this.checkElement = checkElement;
- this.claimedInputs = claimedInputs;
- }
- get optional() {
- return false;
- }
- execute() {
- if (this.checkElement) {
- this.tcb.domSchemaChecker.checkElement(this.tcb.id, this.element, this.tcb.schemas, this.tcb.hostIsStandalone);
- }
- // TODO(alxhub): this could be more efficient.
- for (const binding of this.element.inputs) {
- const isPropertyBinding = binding.type === exports.BindingType.Property || binding.type === exports.BindingType.TwoWay;
- if (isPropertyBinding && this.claimedInputs.has(binding.name)) {
- // Skip this binding as it was claimed by a directive.
- continue;
- }
- if (isPropertyBinding && binding.name !== 'style' && binding.name !== 'class') {
- // A direct binding to a property.
- const propertyName = ATTR_TO_PROP.get(binding.name) ?? binding.name;
- this.tcb.domSchemaChecker.checkProperty(this.tcb.id, this.element, propertyName, binding.sourceSpan, this.tcb.schemas, this.tcb.hostIsStandalone);
- }
- }
- return null;
- }
- }
- /**
- * A `TcbOp` that finds and flags control flow nodes that interfere with content projection.
- *
- * Context:
- * Control flow blocks try to emulate the content projection behavior of `*ngIf` and `*ngFor`
- * in order to reduce breakages when moving from one syntax to the other (see #52414), however the
- * approach only works if there's only one element at the root of the control flow expression.
- * This means that a stray sibling node (e.g. text) can prevent an element from being projected
- * into the right slot. The purpose of the `TcbOp` is to find any places where a node at the root
- * of a control flow expression *would have been projected* into a specific slot, if the control
- * flow node didn't exist.
- */
- class TcbControlFlowContentProjectionOp extends TcbOp {
- tcb;
- element;
- ngContentSelectors;
- componentName;
- category;
- constructor(tcb, element, ngContentSelectors, componentName) {
- super();
- this.tcb = tcb;
- this.element = element;
- this.ngContentSelectors = ngContentSelectors;
- this.componentName = componentName;
- // We only need to account for `error` and `warning` since
- // this check won't be enabled for `suppress`.
- this.category =
- tcb.env.config.controlFlowPreventingContentProjection === 'error'
- ? ts.DiagnosticCategory.Error
- : ts.DiagnosticCategory.Warning;
- }
- optional = false;
- execute() {
- const controlFlowToCheck = this.findPotentialControlFlowNodes();
- if (controlFlowToCheck.length > 0) {
- const matcher = new SelectorMatcher();
- for (const selector of this.ngContentSelectors) {
- // `*` is a special selector for the catch-all slot.
- if (selector !== '*') {
- matcher.addSelectables(CssSelector.parse(selector), selector);
- }
- }
- for (const root of controlFlowToCheck) {
- for (const child of root.children) {
- if (child instanceof Element$1 || child instanceof Template) {
- matcher.match(createCssSelectorFromNode(child), (_, originalSelector) => {
- this.tcb.oobRecorder.controlFlowPreventingContentProjection(this.tcb.id, this.category, child, this.componentName, originalSelector, root, this.tcb.hostPreserveWhitespaces);
- });
- }
- }
- }
- }
- return null;
- }
- findPotentialControlFlowNodes() {
- const result = [];
- for (const child of this.element.children) {
- if (child instanceof ForLoopBlock) {
- if (this.shouldCheck(child)) {
- result.push(child);
- }
- if (child.empty !== null && this.shouldCheck(child.empty)) {
- result.push(child.empty);
- }
- }
- else if (child instanceof IfBlock) {
- for (const branch of child.branches) {
- if (this.shouldCheck(branch)) {
- result.push(branch);
- }
- }
- }
- else if (child instanceof SwitchBlock) {
- for (const current of child.cases) {
- if (this.shouldCheck(current)) {
- result.push(current);
- }
- }
- }
- }
- return result;
- }
- shouldCheck(node) {
- // Skip nodes with less than two children since it's impossible
- // for them to run into the issue that we're checking for.
- if (node.children.length < 2) {
- return false;
- }
- let hasSeenRootNode = false;
- // Check the number of root nodes while skipping empty text where relevant.
- for (const child of node.children) {
- // Normally `preserveWhitspaces` would have been accounted for during parsing, however
- // in `ngtsc/annotations/component/src/resources.ts#parseExtractedTemplate` we enable
- // `preserveWhitespaces` to preserve the accuracy of source maps diagnostics. This means
- // that we have to account for it here since the presence of text nodes affects the
- // content projection behavior.
- if (!(child instanceof Text$3) ||
- this.tcb.hostPreserveWhitespaces ||
- child.value.trim().length > 0) {
- // Content projection will be affected if there's more than one root node.
- if (hasSeenRootNode) {
- return true;
- }
- hasSeenRootNode = true;
- }
- }
- return false;
- }
- }
- /**
- * Mapping between attributes names that don't correspond to their element property names.
- * Note: this mapping has to be kept in sync with the equally named mapping in the runtime.
- */
- const ATTR_TO_PROP = new Map(Object.entries({
- 'class': 'className',
- 'for': 'htmlFor',
- 'formaction': 'formAction',
- 'innerHtml': 'innerHTML',
- 'readonly': 'readOnly',
- 'tabindex': 'tabIndex',
- }));
- /**
- * A `TcbOp` which generates code to check "unclaimed inputs" - bindings on an element which were
- * not attributed to any directive or component, and are instead processed against the HTML element
- * itself.
- *
- * Currently, only the expressions of these bindings are checked. The targets of the bindings are
- * checked against the DOM schema via a `TcbDomSchemaCheckerOp`.
- *
- * Executing this operation returns nothing.
- */
- class TcbUnclaimedInputsOp extends TcbOp {
- tcb;
- scope;
- element;
- claimedInputs;
- constructor(tcb, scope, element, claimedInputs) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.element = element;
- this.claimedInputs = claimedInputs;
- }
- get optional() {
- return false;
- }
- execute() {
- // `this.inputs` contains only those bindings not matched by any directive. These bindings go to
- // the element itself.
- let elId = null;
- // TODO(alxhub): this could be more efficient.
- for (const binding of this.element.inputs) {
- const isPropertyBinding = binding.type === exports.BindingType.Property || binding.type === exports.BindingType.TwoWay;
- if (isPropertyBinding && this.claimedInputs.has(binding.name)) {
- // Skip this binding as it was claimed by a directive.
- continue;
- }
- const expr = widenBinding(tcbExpression(binding.value, this.tcb, this.scope), this.tcb);
- if (this.tcb.env.config.checkTypeOfDomBindings && isPropertyBinding) {
- if (binding.name !== 'style' && binding.name !== 'class') {
- if (elId === null) {
- elId = this.scope.resolve(this.element);
- }
- // A direct binding to a property.
- const propertyName = ATTR_TO_PROP.get(binding.name) ?? binding.name;
- const prop = ts.factory.createElementAccessExpression(elId, ts.factory.createStringLiteral(propertyName));
- const stmt = ts.factory.createBinaryExpression(prop, ts.SyntaxKind.EqualsToken, wrapForDiagnostics(expr));
- addParseSpanInfo(stmt, binding.sourceSpan);
- this.scope.addStatement(ts.factory.createExpressionStatement(stmt));
- }
- else {
- this.scope.addStatement(ts.factory.createExpressionStatement(expr));
- }
- }
- else {
- // A binding to an animation, attribute, class or style. For now, only validate the right-
- // hand side of the expression.
- // TODO: properly check class and style bindings.
- this.scope.addStatement(ts.factory.createExpressionStatement(expr));
- }
- }
- return null;
- }
- }
- /**
- * A `TcbOp` which generates code to check event bindings on an element that correspond with the
- * outputs of a directive.
- *
- * Executing this operation returns nothing.
- */
- class TcbDirectiveOutputsOp extends TcbOp {
- tcb;
- scope;
- node;
- dir;
- constructor(tcb, scope, node, dir) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.node = node;
- this.dir = dir;
- }
- get optional() {
- return false;
- }
- execute() {
- let dirId = null;
- const outputs = this.dir.outputs;
- for (const output of this.node.outputs) {
- if (output.type === exports.ParsedEventType.Animation ||
- !outputs.hasBindingPropertyName(output.name)) {
- continue;
- }
- if (this.tcb.env.config.checkTypeOfOutputEvents && output.name.endsWith('Change')) {
- const inputName = output.name.slice(0, -6);
- checkSplitTwoWayBinding(inputName, output, this.node.inputs, this.tcb);
- }
- // TODO(alxhub): consider supporting multiple fields with the same property name for outputs.
- const field = outputs.getByBindingPropertyName(output.name)[0].classPropertyName;
- if (dirId === null) {
- dirId = this.scope.resolve(this.node, this.dir);
- }
- const outputField = ts.factory.createElementAccessExpression(dirId, ts.factory.createStringLiteral(field));
- addParseSpanInfo(outputField, output.keySpan);
- if (this.tcb.env.config.checkTypeOfOutputEvents) {
- // For strict checking of directive events, generate a call to the `subscribe` method
- // on the directive's output field to let type information flow into the handler function's
- // `$event` parameter.
- const handler = tcbCreateEventHandler(output, this.tcb, this.scope, 0 /* EventParamType.Infer */);
- const subscribeFn = ts.factory.createPropertyAccessExpression(outputField, 'subscribe');
- const call = ts.factory.createCallExpression(subscribeFn, /* typeArguments */ undefined, [
- handler,
- ]);
- addParseSpanInfo(call, output.sourceSpan);
- this.scope.addStatement(ts.factory.createExpressionStatement(call));
- }
- else {
- // If strict checking of directive events is disabled:
- //
- // * We still generate the access to the output field as a statement in the TCB so consumers
- // of the `TemplateTypeChecker` can still find the node for the class member for the
- // output.
- // * Emit a handler function where the `$event` parameter has an explicit `any` type.
- this.scope.addStatement(ts.factory.createExpressionStatement(outputField));
- const handler = tcbCreateEventHandler(output, this.tcb, this.scope, 1 /* EventParamType.Any */);
- this.scope.addStatement(ts.factory.createExpressionStatement(handler));
- }
- }
- return null;
- }
- }
- /**
- * A `TcbOp` which generates code to check "unclaimed outputs" - event bindings on an element which
- * were not attributed to any directive or component, and are instead processed against the HTML
- * element itself.
- *
- * Executing this operation returns nothing.
- */
- class TcbUnclaimedOutputsOp extends TcbOp {
- tcb;
- scope;
- element;
- claimedOutputs;
- constructor(tcb, scope, element, claimedOutputs) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.element = element;
- this.claimedOutputs = claimedOutputs;
- }
- get optional() {
- return false;
- }
- execute() {
- let elId = null;
- // TODO(alxhub): this could be more efficient.
- for (const output of this.element.outputs) {
- if (this.claimedOutputs.has(output.name)) {
- // Skip this event handler as it was claimed by a directive.
- continue;
- }
- if (this.tcb.env.config.checkTypeOfOutputEvents && output.name.endsWith('Change')) {
- const inputName = output.name.slice(0, -6);
- if (checkSplitTwoWayBinding(inputName, output, this.element.inputs, this.tcb)) {
- // Skip this event handler as the error was already handled.
- continue;
- }
- }
- if (output.type === exports.ParsedEventType.Animation) {
- // Animation output bindings always have an `$event` parameter of type `AnimationEvent`.
- const eventType = this.tcb.env.config.checkTypeOfAnimationEvents
- ? this.tcb.env.referenceExternalType('@angular/animations', 'AnimationEvent')
- : 1 /* EventParamType.Any */;
- const handler = tcbCreateEventHandler(output, this.tcb, this.scope, eventType);
- this.scope.addStatement(ts.factory.createExpressionStatement(handler));
- }
- else if (this.tcb.env.config.checkTypeOfDomEvents) {
- // If strict checking of DOM events is enabled, generate a call to `addEventListener` on
- // the element instance so that TypeScript's type inference for
- // `HTMLElement.addEventListener` using `HTMLElementEventMap` to infer an accurate type for
- // `$event` depending on the event name. For unknown event names, TypeScript resorts to the
- // base `Event` type.
- const handler = tcbCreateEventHandler(output, this.tcb, this.scope, 0 /* EventParamType.Infer */);
- if (elId === null) {
- elId = this.scope.resolve(this.element);
- }
- const propertyAccess = ts.factory.createPropertyAccessExpression(elId, 'addEventListener');
- addParseSpanInfo(propertyAccess, output.keySpan);
- const call = ts.factory.createCallExpression(
- /* expression */ propertyAccess,
- /* typeArguments */ undefined,
- /* arguments */ [ts.factory.createStringLiteral(output.name), handler]);
- addParseSpanInfo(call, output.sourceSpan);
- this.scope.addStatement(ts.factory.createExpressionStatement(call));
- }
- else {
- // If strict checking of DOM inputs is disabled, emit a handler function where the `$event`
- // parameter has an explicit `any` type.
- const handler = tcbCreateEventHandler(output, this.tcb, this.scope, 1 /* EventParamType.Any */);
- this.scope.addStatement(ts.factory.createExpressionStatement(handler));
- }
- }
- return null;
- }
- }
- /**
- * A `TcbOp` which generates a completion point for the component context.
- *
- * This completion point looks like `this. ;` in the TCB output, and does not produce diagnostics.
- * TypeScript autocompletion APIs can be used at this completion point (after the '.') to produce
- * autocompletion results of properties and methods from the template's component context.
- */
- class TcbComponentContextCompletionOp extends TcbOp {
- scope;
- constructor(scope) {
- super();
- this.scope = scope;
- }
- optional = false;
- execute() {
- const ctx = ts.factory.createThis();
- const ctxDot = ts.factory.createPropertyAccessExpression(ctx, '');
- markIgnoreDiagnostics(ctxDot);
- addExpressionIdentifier(ctxDot, ExpressionIdentifier.COMPONENT_COMPLETION);
- this.scope.addStatement(ts.factory.createExpressionStatement(ctxDot));
- return null;
- }
- }
- /**
- * A `TcbOp` which renders a variable defined inside of block syntax (e.g. `@if (expr; as var) {}`).
- *
- * Executing this operation returns the identifier which can be used to refer to the variable.
- */
- class TcbBlockVariableOp extends TcbOp {
- tcb;
- scope;
- initializer;
- variable;
- constructor(tcb, scope, initializer, variable) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.initializer = initializer;
- this.variable = variable;
- }
- get optional() {
- return false;
- }
- execute() {
- const id = this.tcb.allocateId();
- addParseSpanInfo(id, this.variable.keySpan);
- const variable = tsCreateVariable(id, wrapForTypeChecker(this.initializer));
- addParseSpanInfo(variable.declarationList.declarations[0], this.variable.sourceSpan);
- this.scope.addStatement(variable);
- return id;
- }
- }
- /**
- * A `TcbOp` which renders a variable that is implicitly available within a block (e.g. `$count`
- * in a `@for` block).
- *
- * Executing this operation returns the identifier which can be used to refer to the variable.
- */
- class TcbBlockImplicitVariableOp extends TcbOp {
- tcb;
- scope;
- type;
- variable;
- constructor(tcb, scope, type, variable) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.type = type;
- this.variable = variable;
- }
- optional = true;
- execute() {
- const id = this.tcb.allocateId();
- addParseSpanInfo(id, this.variable.keySpan);
- const variable = tsDeclareVariable(id, this.type);
- addParseSpanInfo(variable.declarationList.declarations[0], this.variable.sourceSpan);
- this.scope.addStatement(variable);
- return id;
- }
- }
- /**
- * A `TcbOp` which renders an `if` template block as a TypeScript `if` statement.
- *
- * Executing this operation returns nothing.
- */
- class TcbIfOp extends TcbOp {
- tcb;
- scope;
- block;
- expressionScopes = new Map();
- constructor(tcb, scope, block) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.block = block;
- }
- get optional() {
- return false;
- }
- execute() {
- const root = this.generateBranch(0);
- root && this.scope.addStatement(root);
- return null;
- }
- generateBranch(index) {
- const branch = this.block.branches[index];
- if (!branch) {
- return undefined;
- }
- // If the expression is null, it means that it's an `else` statement.
- if (branch.expression === null) {
- const branchScope = this.getBranchScope(this.scope, branch, index);
- return ts.factory.createBlock(branchScope.render());
- }
- // We process the expression first in the parent scope, but create a scope around the block
- // that the body will inherit from. We do this, because we need to declare a separate variable
- // for the case where the expression has an alias _and_ because we need the processed
- // expression when generating the guard for the body.
- const outerScope = Scope.forNodes(this.tcb, this.scope, branch, [], null);
- outerScope.render().forEach((stmt) => this.scope.addStatement(stmt));
- this.expressionScopes.set(branch, outerScope);
- let expression = tcbExpression(branch.expression, this.tcb, this.scope);
- if (branch.expressionAlias !== null) {
- expression = ts.factory.createBinaryExpression(ts.factory.createParenthesizedExpression(expression), ts.SyntaxKind.AmpersandAmpersandToken, outerScope.resolve(branch.expressionAlias));
- }
- const bodyScope = this.getBranchScope(outerScope, branch, index);
- return ts.factory.createIfStatement(expression, ts.factory.createBlock(bodyScope.render()), this.generateBranch(index + 1));
- }
- getBranchScope(parentScope, branch, index) {
- const checkBody = this.tcb.env.config.checkControlFlowBodies;
- return Scope.forNodes(this.tcb, parentScope, null, checkBody ? branch.children : [], checkBody ? this.generateBranchGuard(index) : null);
- }
- generateBranchGuard(index) {
- let guard = null;
- // Since event listeners are inside callbacks, type narrowing doesn't apply to them anymore.
- // To recreate the behavior, we generate an expression that negates all the values of the
- // branches _before_ the current one, and then we add the current branch's expression on top.
- // For example `@if (expr === 1) {} @else if (expr === 2) {} @else if (expr === 3)`, the guard
- // for the last expression will be `!(expr === 1) && !(expr === 2) && expr === 3`.
- for (let i = 0; i <= index; i++) {
- const branch = this.block.branches[i];
- // Skip over branches without an expression.
- if (branch.expression === null) {
- continue;
- }
- // This shouldn't happen since all the state is handled
- // internally, but we have the check just in case.
- if (!this.expressionScopes.has(branch)) {
- throw new Error(`Could not determine expression scope of branch at index ${i}`);
- }
- const expressionScope = this.expressionScopes.get(branch);
- let expression;
- // We need to recreate the expression and mark it to be ignored for diagnostics,
- // because it was already checked as a part of the block's condition and we don't
- // want it to produce a duplicate diagnostic.
- expression = tcbExpression(branch.expression, this.tcb, expressionScope);
- if (branch.expressionAlias !== null) {
- expression = ts.factory.createBinaryExpression(ts.factory.createParenthesizedExpression(expression), ts.SyntaxKind.AmpersandAmpersandToken, expressionScope.resolve(branch.expressionAlias));
- }
- markIgnoreDiagnostics(expression);
- // The expressions of the preceding branches have to be negated
- // (e.g. `expr` becomes `!(expr)`) when comparing in the guard, except
- // for the branch's own expression which is preserved as is.
- const comparisonExpression = i === index
- ? expression
- : ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, ts.factory.createParenthesizedExpression(expression));
- // Finally add the expression to the guard with an && operator.
- guard =
- guard === null
- ? comparisonExpression
- : ts.factory.createBinaryExpression(guard, ts.SyntaxKind.AmpersandAmpersandToken, comparisonExpression);
- }
- return guard;
- }
- }
- /**
- * A `TcbOp` which renders a `switch` block as a TypeScript `switch` statement.
- *
- * Executing this operation returns nothing.
- */
- class TcbSwitchOp extends TcbOp {
- tcb;
- scope;
- block;
- constructor(tcb, scope, block) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.block = block;
- }
- get optional() {
- return false;
- }
- execute() {
- const switchExpression = tcbExpression(this.block.expression, this.tcb, this.scope);
- const clauses = this.block.cases.map((current) => {
- const checkBody = this.tcb.env.config.checkControlFlowBodies;
- const clauseScope = Scope.forNodes(this.tcb, this.scope, null, checkBody ? current.children : [], checkBody ? this.generateGuard(current, switchExpression) : null);
- const statements = [...clauseScope.render(), ts.factory.createBreakStatement()];
- return current.expression === null
- ? ts.factory.createDefaultClause(statements)
- : ts.factory.createCaseClause(tcbExpression(current.expression, this.tcb, clauseScope), statements);
- });
- this.scope.addStatement(ts.factory.createSwitchStatement(switchExpression, ts.factory.createCaseBlock(clauses)));
- return null;
- }
- generateGuard(node, switchValue) {
- // For non-default cases, the guard needs to compare against the case value, e.g.
- // `switchExpression === caseExpression`.
- if (node.expression !== null) {
- // The expression needs to be ignored for diagnostics since it has been checked already.
- const expression = tcbExpression(node.expression, this.tcb, this.scope);
- markIgnoreDiagnostics(expression);
- return ts.factory.createBinaryExpression(switchValue, ts.SyntaxKind.EqualsEqualsEqualsToken, expression);
- }
- // To fully narrow the type in the default case, we need to generate an expression that negates
- // the values of all of the other expressions. For example:
- // @switch (expr) {
- // @case (1) {}
- // @case (2) {}
- // @default {}
- // }
- // Will produce the guard `expr !== 1 && expr !== 2`.
- let guard = null;
- for (const current of this.block.cases) {
- if (current.expression === null) {
- continue;
- }
- // The expression needs to be ignored for diagnostics since it has been checked already.
- const expression = tcbExpression(current.expression, this.tcb, this.scope);
- markIgnoreDiagnostics(expression);
- const comparison = ts.factory.createBinaryExpression(switchValue, ts.SyntaxKind.ExclamationEqualsEqualsToken, expression);
- if (guard === null) {
- guard = comparison;
- }
- else {
- guard = ts.factory.createBinaryExpression(guard, ts.SyntaxKind.AmpersandAmpersandToken, comparison);
- }
- }
- return guard;
- }
- }
- /**
- * A `TcbOp` which renders a `for` block as a TypeScript `for...of` loop.
- *
- * Executing this operation returns nothing.
- */
- class TcbForOfOp extends TcbOp {
- tcb;
- scope;
- block;
- constructor(tcb, scope, block) {
- super();
- this.tcb = tcb;
- this.scope = scope;
- this.block = block;
- }
- get optional() {
- return false;
- }
- execute() {
- const loopScope = Scope.forNodes(this.tcb, this.scope, this.block, this.tcb.env.config.checkControlFlowBodies ? this.block.children : [], null);
- const initializerId = loopScope.resolve(this.block.item);
- if (!ts.isIdentifier(initializerId)) {
- throw new Error(`Could not resolve for loop variable ${this.block.item.name} to an identifier`);
- }
- const initializer = ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(initializerId)], ts.NodeFlags.Const);
- addParseSpanInfo(initializer, this.block.item.keySpan);
- // It's common to have a for loop over a nullable value (e.g. produced by the `async` pipe).
- // Add a non-null expression to allow such values to be assigned.
- const expression = ts.factory.createNonNullExpression(tcbExpression(this.block.expression, this.tcb, this.scope));
- const trackTranslator = new TcbForLoopTrackTranslator(this.tcb, loopScope, this.block);
- const trackExpression = trackTranslator.translate(this.block.trackBy);
- const statements = [
- ...loopScope.render(),
- ts.factory.createExpressionStatement(trackExpression),
- ];
- this.scope.addStatement(ts.factory.createForOfStatement(undefined, initializer, expression, ts.factory.createBlock(statements)));
- return null;
- }
- }
- /**
- * Value used to break a circular reference between `TcbOp`s.
- *
- * This value is returned whenever `TcbOp`s have a circular dependency. The expression is a non-null
- * assertion of the null value (in TypeScript, the expression `null!`). This construction will infer
- * the least narrow type for whatever it's assigned to.
- */
- const INFER_TYPE_FOR_CIRCULAR_OP_EXPR = ts.factory.createNonNullExpression(ts.factory.createNull());
- /**
- * Overall generation context for the type check block.
- *
- * `Context` handles operations during code generation which are global with respect to the whole
- * block. It's responsible for variable name allocation and management of any imports needed. It
- * also contains the template metadata itself.
- */
- class Context {
- env;
- domSchemaChecker;
- oobRecorder;
- id;
- boundTarget;
- pipes;
- schemas;
- hostIsStandalone;
- hostPreserveWhitespaces;
- nextId = 1;
- constructor(env, domSchemaChecker, oobRecorder, id, boundTarget, pipes, schemas, hostIsStandalone, hostPreserveWhitespaces) {
- this.env = env;
- this.domSchemaChecker = domSchemaChecker;
- this.oobRecorder = oobRecorder;
- this.id = id;
- this.boundTarget = boundTarget;
- this.pipes = pipes;
- this.schemas = schemas;
- this.hostIsStandalone = hostIsStandalone;
- this.hostPreserveWhitespaces = hostPreserveWhitespaces;
- }
- /**
- * Allocate a new variable name for use within the `Context`.
- *
- * Currently this uses a monotonically increasing counter, but in the future the variable name
- * might change depending on the type of data being stored.
- */
- allocateId() {
- return ts.factory.createIdentifier(`_t${this.nextId++}`);
- }
- getPipeByName(name) {
- if (this.pipes === null || !this.pipes.has(name)) {
- return null;
- }
- return this.pipes.get(name);
- }
- }
- /**
- * Local scope within the type check block for a particular template.
- *
- * The top-level template and each nested `<ng-template>` have their own `Scope`, which exist in a
- * hierarchy. The structure of this hierarchy mirrors the syntactic scopes in the generated type
- * check block, where each nested template is encased in an `if` structure.
- *
- * As a template's `TcbOp`s are executed in a given `Scope`, statements are added via
- * `addStatement()`. When this processing is complete, the `Scope` can be turned into a `ts.Block`
- * via `renderToBlock()`.
- *
- * If a `TcbOp` requires the output of another, it can call `resolve()`.
- */
- class Scope {
- tcb;
- parent;
- guard;
- /**
- * A queue of operations which need to be performed to generate the TCB code for this scope.
- *
- * This array can contain either a `TcbOp` which has yet to be executed, or a `ts.Expression|null`
- * representing the memoized result of executing the operation. As operations are executed, their
- * results are written into the `opQueue`, overwriting the original operation.
- *
- * If an operation is in the process of being executed, it is temporarily overwritten here with
- * `INFER_TYPE_FOR_CIRCULAR_OP_EXPR`. This way, if a cycle is encountered where an operation
- * depends transitively on its own result, the inner operation will infer the least narrow type
- * that fits instead. This has the same semantics as TypeScript itself when types are referenced
- * circularly.
- */
- opQueue = [];
- /**
- * A map of `TmplAstElement`s to the index of their `TcbElementOp` in the `opQueue`
- */
- elementOpMap = new Map();
- /**
- * A map of maps which tracks the index of `TcbDirectiveCtorOp`s in the `opQueue` for each
- * directive on a `TmplAstElement` or `TmplAstTemplate` node.
- */
- directiveOpMap = new Map();
- /**
- * A map of `TmplAstReference`s to the index of their `TcbReferenceOp` in the `opQueue`
- */
- referenceOpMap = new Map();
- /**
- * Map of immediately nested <ng-template>s (within this `Scope`) represented by `TmplAstTemplate`
- * nodes to the index of their `TcbTemplateContextOp`s in the `opQueue`.
- */
- templateCtxOpMap = new Map();
- /**
- * Map of variables declared on the template that created this `Scope` (represented by
- * `TmplAstVariable` nodes) to the index of their `TcbVariableOp`s in the `opQueue`, or to
- * pre-resolved variable identifiers.
- */
- varMap = new Map();
- /**
- * A map of the names of `TmplAstLetDeclaration`s to the index of their op in the `opQueue`.
- *
- * Assumes that there won't be duplicated `@let` declarations within the same scope.
- */
- letDeclOpMap = new Map();
- /**
- * Statements for this template.
- *
- * Executing the `TcbOp`s in the `opQueue` populates this array.
- */
- statements = [];
- /**
- * Names of the for loop context variables and their types.
- */
- static forLoopContextVariableTypes = new Map([
- ['$first', ts.SyntaxKind.BooleanKeyword],
- ['$last', ts.SyntaxKind.BooleanKeyword],
- ['$even', ts.SyntaxKind.BooleanKeyword],
- ['$odd', ts.SyntaxKind.BooleanKeyword],
- ['$index', ts.SyntaxKind.NumberKeyword],
- ['$count', ts.SyntaxKind.NumberKeyword],
- ]);
- constructor(tcb, parent = null, guard = null) {
- this.tcb = tcb;
- this.parent = parent;
- this.guard = guard;
- }
- /**
- * Constructs a `Scope` given either a `TmplAstTemplate` or a list of `TmplAstNode`s.
- *
- * @param tcb the overall context of TCB generation.
- * @param parentScope the `Scope` of the parent template (if any) or `null` if this is the root
- * `Scope`.
- * @param scopedNode Node that provides the scope around the child nodes (e.g. a
- * `TmplAstTemplate` node exposing variables to its children).
- * @param children Child nodes that should be appended to the TCB.
- * @param guard an expression that is applied to this scope for type narrowing purposes.
- */
- static forNodes(tcb, parentScope, scopedNode, children, guard) {
- const scope = new Scope(tcb, parentScope, guard);
- if (parentScope === null && tcb.env.config.enableTemplateTypeChecker) {
- // Add an autocompletion point for the component context.
- scope.opQueue.push(new TcbComponentContextCompletionOp(scope));
- }
- // If given an actual `TmplAstTemplate` instance, then process any additional information it
- // has.
- if (scopedNode instanceof Template) {
- // The template's variable declarations need to be added as `TcbVariableOp`s.
- const varMap = new Map();
- for (const v of scopedNode.variables) {
- // Validate that variables on the `TmplAstTemplate` are only declared once.
- if (!varMap.has(v.name)) {
- varMap.set(v.name, v);
- }
- else {
- const firstDecl = varMap.get(v.name);
- tcb.oobRecorder.duplicateTemplateVar(tcb.id, v, firstDecl);
- }
- this.registerVariable(scope, v, new TcbTemplateVariableOp(tcb, scope, scopedNode, v));
- }
- }
- else if (scopedNode instanceof IfBlockBranch) {
- const { expression, expressionAlias } = scopedNode;
- if (expression !== null && expressionAlias !== null) {
- this.registerVariable(scope, expressionAlias, new TcbBlockVariableOp(tcb, scope, tcbExpression(expression, tcb, scope), expressionAlias));
- }
- }
- else if (scopedNode instanceof ForLoopBlock) {
- // Register the variable for the loop so it can be resolved by
- // children. It'll be declared once the loop is created.
- const loopInitializer = tcb.allocateId();
- addParseSpanInfo(loopInitializer, scopedNode.item.sourceSpan);
- scope.varMap.set(scopedNode.item, loopInitializer);
- for (const variable of scopedNode.contextVariables) {
- if (!this.forLoopContextVariableTypes.has(variable.value)) {
- throw new Error(`Unrecognized for loop context variable ${variable.name}`);
- }
- const type = ts.factory.createKeywordTypeNode(this.forLoopContextVariableTypes.get(variable.value));
- this.registerVariable(scope, variable, new TcbBlockImplicitVariableOp(tcb, scope, type, variable));
- }
- }
- for (const node of children) {
- scope.appendNode(node);
- }
- // Once everything is registered, we need to check if there are `@let`
- // declarations that conflict with other local symbols defined after them.
- for (const variable of scope.varMap.keys()) {
- Scope.checkConflictingLet(scope, variable);
- }
- for (const ref of scope.referenceOpMap.keys()) {
- Scope.checkConflictingLet(scope, ref);
- }
- return scope;
- }
- /** Registers a local variable with a scope. */
- static registerVariable(scope, variable, op) {
- const opIndex = scope.opQueue.push(op) - 1;
- scope.varMap.set(variable, opIndex);
- }
- /**
- * Look up a `ts.Expression` representing the value of some operation in the current `Scope`,
- * including any parent scope(s). This method always returns a mutable clone of the
- * `ts.Expression` with the comments cleared.
- *
- * @param node a `TmplAstNode` of the operation in question. The lookup performed will depend on
- * the type of this node:
- *
- * Assuming `directive` is not present, then `resolve` will return:
- *
- * * `TmplAstElement` - retrieve the expression for the element DOM node
- * * `TmplAstTemplate` - retrieve the template context variable
- * * `TmplAstVariable` - retrieve a template let- variable
- * * `TmplAstLetDeclaration` - retrieve a template `@let` declaration
- * * `TmplAstReference` - retrieve variable created for the local ref
- *
- * @param directive if present, a directive type on a `TmplAstElement` or `TmplAstTemplate` to
- * look up instead of the default for an element or template node.
- */
- resolve(node, directive) {
- // Attempt to resolve the operation locally.
- const res = this.resolveLocal(node, directive);
- if (res !== null) {
- // We want to get a clone of the resolved expression and clear the trailing comments
- // so they don't continue to appear in every place the expression is used.
- // As an example, this would otherwise produce:
- // var _t1 /**T:DIR*/ /*1,2*/ = _ctor1();
- // _t1 /**T:DIR*/ /*1,2*/.input = 'value';
- //
- // In addition, returning a clone prevents the consumer of `Scope#resolve` from
- // attaching comments at the declaration site.
- let clone;
- if (ts.isIdentifier(res)) {
- clone = ts.factory.createIdentifier(res.text);
- }
- else if (ts.isNonNullExpression(res)) {
- clone = ts.factory.createNonNullExpression(res.expression);
- }
- else {
- throw new Error(`Could not resolve ${node} to an Identifier or a NonNullExpression`);
- }
- ts.setOriginalNode(clone, res);
- clone.parent = clone.parent;
- return ts.setSyntheticTrailingComments(clone, []);
- }
- else if (this.parent !== null) {
- // Check with the parent.
- return this.parent.resolve(node, directive);
- }
- else {
- throw new Error(`Could not resolve ${node} / ${directive}`);
- }
- }
- /**
- * Add a statement to this scope.
- */
- addStatement(stmt) {
- this.statements.push(stmt);
- }
- /**
- * Get the statements.
- */
- render() {
- for (let i = 0; i < this.opQueue.length; i++) {
- // Optional statements cannot be skipped when we are generating the TCB for use
- // by the TemplateTypeChecker.
- const skipOptional = !this.tcb.env.config.enableTemplateTypeChecker;
- this.executeOp(i, skipOptional);
- }
- return this.statements;
- }
- /**
- * Returns an expression of all template guards that apply to this scope, including those of
- * parent scopes. If no guards have been applied, null is returned.
- */
- guards() {
- let parentGuards = null;
- if (this.parent !== null) {
- // Start with the guards from the parent scope, if present.
- parentGuards = this.parent.guards();
- }
- if (this.guard === null) {
- // This scope does not have a guard, so return the parent's guards as is.
- return parentGuards;
- }
- else if (parentGuards === null) {
- // There's no guards from the parent scope, so this scope's guard represents all available
- // guards.
- return this.guard;
- }
- else {
- // Both the parent scope and this scope provide a guard, so create a combination of the two.
- // It is important that the parent guard is used as left operand, given that it may provide
- // narrowing that is required for this scope's guard to be valid.
- return ts.factory.createBinaryExpression(parentGuards, ts.SyntaxKind.AmpersandAmpersandToken, this.guard);
- }
- }
- /** Returns whether a template symbol is defined locally within the current scope. */
- isLocal(node) {
- if (node instanceof Variable) {
- return this.varMap.has(node);
- }
- if (node instanceof LetDeclaration$1) {
- return this.letDeclOpMap.has(node.name);
- }
- return this.referenceOpMap.has(node);
- }
- resolveLocal(ref, directive) {
- if (ref instanceof Reference$1 && this.referenceOpMap.has(ref)) {
- return this.resolveOp(this.referenceOpMap.get(ref));
- }
- else if (ref instanceof LetDeclaration$1 && this.letDeclOpMap.has(ref.name)) {
- return this.resolveOp(this.letDeclOpMap.get(ref.name).opIndex);
- }
- else if (ref instanceof Variable && this.varMap.has(ref)) {
- // Resolving a context variable for this template.
- // Execute the `TcbVariableOp` associated with the `TmplAstVariable`.
- const opIndexOrNode = this.varMap.get(ref);
- return typeof opIndexOrNode === 'number' ? this.resolveOp(opIndexOrNode) : opIndexOrNode;
- }
- else if (ref instanceof Template &&
- directive === undefined &&
- this.templateCtxOpMap.has(ref)) {
- // Resolving the context of the given sub-template.
- // Execute the `TcbTemplateContextOp` for the template.
- return this.resolveOp(this.templateCtxOpMap.get(ref));
- }
- else if ((ref instanceof Element$1 || ref instanceof Template) &&
- directive !== undefined &&
- this.directiveOpMap.has(ref)) {
- // Resolving a directive on an element or sub-template.
- const dirMap = this.directiveOpMap.get(ref);
- if (dirMap.has(directive)) {
- return this.resolveOp(dirMap.get(directive));
- }
- else {
- return null;
- }
- }
- else if (ref instanceof Element$1 && this.elementOpMap.has(ref)) {
- // Resolving the DOM node of an element in this template.
- return this.resolveOp(this.elementOpMap.get(ref));
- }
- else {
- return null;
- }
- }
- /**
- * Like `executeOp`, but assert that the operation actually returned `ts.Expression`.
- */
- resolveOp(opIndex) {
- const res = this.executeOp(opIndex, /* skipOptional */ false);
- if (res === null) {
- throw new Error(`Error resolving operation, got null`);
- }
- return res;
- }
- /**
- * Execute a particular `TcbOp` in the `opQueue`.
- *
- * This method replaces the operation in the `opQueue` with the result of execution (once done)
- * and also protects against a circular dependency from the operation to itself by temporarily
- * setting the operation's result to a special expression.
- */
- executeOp(opIndex, skipOptional) {
- const op = this.opQueue[opIndex];
- if (!(op instanceof TcbOp)) {
- return op;
- }
- if (skipOptional && op.optional) {
- return null;
- }
- // Set the result of the operation in the queue to its circular fallback. If executing this
- // operation results in a circular dependency, this will prevent an infinite loop and allow for
- // the resolution of such cycles.
- this.opQueue[opIndex] = op.circularFallback();
- const res = op.execute();
- // Once the operation has finished executing, it's safe to cache the real result.
- this.opQueue[opIndex] = res;
- return res;
- }
- appendNode(node) {
- if (node instanceof Element$1) {
- const opIndex = this.opQueue.push(new TcbElementOp(this.tcb, this, node)) - 1;
- this.elementOpMap.set(node, opIndex);
- if (this.tcb.env.config.controlFlowPreventingContentProjection !== 'suppress') {
- this.appendContentProjectionCheckOp(node);
- }
- this.appendDirectivesAndInputsOfNode(node);
- this.appendOutputsOfNode(node);
- this.appendChildren(node);
- this.checkAndAppendReferencesOfNode(node);
- }
- else if (node instanceof Template) {
- // Template children are rendered in a child scope.
- this.appendDirectivesAndInputsOfNode(node);
- this.appendOutputsOfNode(node);
- const ctxIndex = this.opQueue.push(new TcbTemplateContextOp(this.tcb, this)) - 1;
- this.templateCtxOpMap.set(node, ctxIndex);
- if (this.tcb.env.config.checkTemplateBodies) {
- this.opQueue.push(new TcbTemplateBodyOp(this.tcb, this, node));
- }
- else if (this.tcb.env.config.alwaysCheckSchemaInTemplateBodies) {
- this.appendDeepSchemaChecks(node.children);
- }
- this.checkAndAppendReferencesOfNode(node);
- }
- else if (node instanceof DeferredBlock) {
- this.appendDeferredBlock(node);
- }
- else if (node instanceof IfBlock) {
- this.opQueue.push(new TcbIfOp(this.tcb, this, node));
- }
- else if (node instanceof SwitchBlock) {
- this.opQueue.push(new TcbSwitchOp(this.tcb, this, node));
- }
- else if (node instanceof ForLoopBlock) {
- this.opQueue.push(new TcbForOfOp(this.tcb, this, node));
- node.empty && this.tcb.env.config.checkControlFlowBodies && this.appendChildren(node.empty);
- }
- else if (node instanceof BoundText) {
- this.opQueue.push(new TcbExpressionOp(this.tcb, this, node.value));
- }
- else if (node instanceof Icu$1) {
- this.appendIcuExpressions(node);
- }
- else if (node instanceof Content) {
- this.appendChildren(node);
- }
- else if (node instanceof LetDeclaration$1) {
- const opIndex = this.opQueue.push(new TcbLetDeclarationOp(this.tcb, this, node)) - 1;
- if (this.isLocal(node)) {
- this.tcb.oobRecorder.conflictingDeclaration(this.tcb.id, node);
- }
- else {
- this.letDeclOpMap.set(node.name, { opIndex, node });
- }
- }
- }
- appendChildren(node) {
- for (const child of node.children) {
- this.appendNode(child);
- }
- }
- checkAndAppendReferencesOfNode(node) {
- for (const ref of node.references) {
- const target = this.tcb.boundTarget.getReferenceTarget(ref);
- let ctxIndex;
- if (target === null) {
- // The reference is invalid if it doesn't have a target, so report it as an error.
- this.tcb.oobRecorder.missingReferenceTarget(this.tcb.id, ref);
- // Any usages of the invalid reference will be resolved to a variable of type any.
- ctxIndex = this.opQueue.push(new TcbInvalidReferenceOp(this.tcb, this)) - 1;
- }
- else if (target instanceof Template || target instanceof Element$1) {
- ctxIndex = this.opQueue.push(new TcbReferenceOp(this.tcb, this, ref, node, target)) - 1;
- }
- else {
- ctxIndex =
- this.opQueue.push(new TcbReferenceOp(this.tcb, this, ref, node, target.directive)) - 1;
- }
- this.referenceOpMap.set(ref, ctxIndex);
- }
- }
- appendDirectivesAndInputsOfNode(node) {
- // Collect all the inputs on the element.
- const claimedInputs = new Set();
- const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
- if (directives === null || directives.length === 0) {
- // If there are no directives, then all inputs are unclaimed inputs, so queue an operation
- // to add them if needed.
- if (node instanceof Element$1) {
- this.opQueue.push(new TcbUnclaimedInputsOp(this.tcb, this, node, claimedInputs));
- this.opQueue.push(new TcbDomSchemaCheckerOp(this.tcb, node, /* checkElement */ true, claimedInputs));
- }
- return;
- }
- else {
- if (node instanceof Element$1) {
- const isDeferred = this.tcb.boundTarget.isDeferred(node);
- if (!isDeferred && directives.some((dirMeta) => dirMeta.isExplicitlyDeferred)) {
- // This node has directives/components that were defer-loaded (included into
- // `@Component.deferredImports`), but the node itself was used outside of a
- // `@defer` block, which is the error.
- this.tcb.oobRecorder.deferredComponentUsedEagerly(this.tcb.id, node);
- }
- }
- }
- const dirMap = new Map();
- for (const dir of directives) {
- let directiveOp;
- const host = this.tcb.env.reflector;
- const dirRef = dir.ref;
- if (!dir.isGeneric) {
- // The most common case is that when a directive is not generic, we use the normal
- // `TcbNonDirectiveTypeOp`.
- directiveOp = new TcbNonGenericDirectiveTypeOp(this.tcb, this, node, dir);
- }
- else if (!requiresInlineTypeCtor(dirRef.node, host, this.tcb.env) ||
- this.tcb.env.config.useInlineTypeConstructors) {
- // For generic directives, we use a type constructor to infer types. If a directive requires
- // an inline type constructor, then inlining must be available to use the
- // `TcbDirectiveCtorOp`. If not we, we fallback to using `any` – see below.
- directiveOp = new TcbDirectiveCtorOp(this.tcb, this, node, dir);
- }
- else {
- // If inlining is not available, then we give up on inferring the generic params, and use
- // `any` type for the directive's generic parameters.
- directiveOp = new TcbGenericDirectiveTypeWithAnyParamsOp(this.tcb, this, node, dir);
- }
- const dirIndex = this.opQueue.push(directiveOp) - 1;
- dirMap.set(dir, dirIndex);
- this.opQueue.push(new TcbDirectiveInputsOp(this.tcb, this, node, dir));
- }
- this.directiveOpMap.set(node, dirMap);
- // After expanding the directives, we might need to queue an operation to check any unclaimed
- // inputs.
- if (node instanceof Element$1) {
- // Go through the directives and remove any inputs that it claims from `elementInputs`.
- for (const dir of directives) {
- for (const propertyName of dir.inputs.propertyNames) {
- claimedInputs.add(propertyName);
- }
- }
- this.opQueue.push(new TcbUnclaimedInputsOp(this.tcb, this, node, claimedInputs));
- // If there are no directives which match this element, then it's a "plain" DOM element (or a
- // web component), and should be checked against the DOM schema. If any directives match,
- // we must assume that the element could be custom (either a component, or a directive like
- // <router-outlet>) and shouldn't validate the element name itself.
- const checkElement = directives.length === 0;
- this.opQueue.push(new TcbDomSchemaCheckerOp(this.tcb, node, checkElement, claimedInputs));
- }
- }
- appendOutputsOfNode(node) {
- // Collect all the outputs on the element.
- const claimedOutputs = new Set();
- const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
- if (directives === null || directives.length === 0) {
- // If there are no directives, then all outputs are unclaimed outputs, so queue an operation
- // to add them if needed.
- if (node instanceof Element$1) {
- this.opQueue.push(new TcbUnclaimedOutputsOp(this.tcb, this, node, claimedOutputs));
- }
- return;
- }
- // Queue operations for all directives to check the relevant outputs for a directive.
- for (const dir of directives) {
- this.opQueue.push(new TcbDirectiveOutputsOp(this.tcb, this, node, dir));
- }
- // After expanding the directives, we might need to queue an operation to check any unclaimed
- // outputs.
- if (node instanceof Element$1) {
- // Go through the directives and register any outputs that it claims in `claimedOutputs`.
- for (const dir of directives) {
- for (const outputProperty of dir.outputs.propertyNames) {
- claimedOutputs.add(outputProperty);
- }
- }
- this.opQueue.push(new TcbUnclaimedOutputsOp(this.tcb, this, node, claimedOutputs));
- }
- }
- appendDeepSchemaChecks(nodes) {
- for (const node of nodes) {
- if (!(node instanceof Element$1 || node instanceof Template)) {
- continue;
- }
- if (node instanceof Element$1) {
- const claimedInputs = new Set();
- const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
- let hasDirectives;
- if (directives === null || directives.length === 0) {
- hasDirectives = false;
- }
- else {
- hasDirectives = true;
- for (const dir of directives) {
- for (const propertyName of dir.inputs.propertyNames) {
- claimedInputs.add(propertyName);
- }
- }
- }
- this.opQueue.push(new TcbDomSchemaCheckerOp(this.tcb, node, !hasDirectives, claimedInputs));
- }
- this.appendDeepSchemaChecks(node.children);
- }
- }
- appendIcuExpressions(node) {
- for (const variable of Object.values(node.vars)) {
- this.opQueue.push(new TcbExpressionOp(this.tcb, this, variable.value));
- }
- for (const placeholder of Object.values(node.placeholders)) {
- if (placeholder instanceof BoundText) {
- this.opQueue.push(new TcbExpressionOp(this.tcb, this, placeholder.value));
- }
- }
- }
- appendContentProjectionCheckOp(root) {
- const meta = this.tcb.boundTarget.getDirectivesOfNode(root)?.find((meta) => meta.isComponent) || null;
- if (meta !== null && meta.ngContentSelectors !== null && meta.ngContentSelectors.length > 0) {
- const selectors = meta.ngContentSelectors;
- // We don't need to generate anything for components that don't have projection
- // slots, or they only have one catch-all slot (represented by `*`).
- if (selectors.length > 1 || (selectors.length === 1 && selectors[0] !== '*')) {
- this.opQueue.push(new TcbControlFlowContentProjectionOp(this.tcb, root, selectors, meta.name));
- }
- }
- }
- appendDeferredBlock(block) {
- this.appendDeferredTriggers(block, block.triggers);
- this.appendDeferredTriggers(block, block.prefetchTriggers);
- // Only the `when` hydration trigger needs to be checked.
- if (block.hydrateTriggers.when) {
- this.opQueue.push(new TcbExpressionOp(this.tcb, this, block.hydrateTriggers.when.value));
- }
- this.appendChildren(block);
- if (block.placeholder !== null) {
- this.appendChildren(block.placeholder);
- }
- if (block.loading !== null) {
- this.appendChildren(block.loading);
- }
- if (block.error !== null) {
- this.appendChildren(block.error);
- }
- }
- appendDeferredTriggers(block, triggers) {
- if (triggers.when !== undefined) {
- this.opQueue.push(new TcbExpressionOp(this.tcb, this, triggers.when.value));
- }
- if (triggers.hover !== undefined) {
- this.appendReferenceBasedDeferredTrigger(block, triggers.hover);
- }
- if (triggers.interaction !== undefined) {
- this.appendReferenceBasedDeferredTrigger(block, triggers.interaction);
- }
- if (triggers.viewport !== undefined) {
- this.appendReferenceBasedDeferredTrigger(block, triggers.viewport);
- }
- }
- appendReferenceBasedDeferredTrigger(block, trigger) {
- if (this.tcb.boundTarget.getDeferredTriggerTarget(block, trigger) === null) {
- this.tcb.oobRecorder.inaccessibleDeferredTriggerElement(this.tcb.id, trigger);
- }
- }
- /** Reports a diagnostic if there are any `@let` declarations that conflict with a node. */
- static checkConflictingLet(scope, node) {
- if (scope.letDeclOpMap.has(node.name)) {
- scope.tcb.oobRecorder.conflictingDeclaration(scope.tcb.id, scope.letDeclOpMap.get(node.name).node);
- }
- }
- }
- /**
- * Create the `this` parameter to the top-level TCB function, with the given generic type
- * arguments.
- */
- function tcbThisParam(name, typeArguments) {
- return ts.factory.createParameterDeclaration(
- /* modifiers */ undefined,
- /* dotDotDotToken */ undefined,
- /* name */ 'this',
- /* questionToken */ undefined,
- /* type */ ts.factory.createTypeReferenceNode(name, typeArguments),
- /* initializer */ undefined);
- }
- /**
- * Process an `AST` expression and convert it into a `ts.Expression`, generating references to the
- * correct identifiers in the current scope.
- */
- function tcbExpression(ast, tcb, scope) {
- const translator = new TcbExpressionTranslator(tcb, scope);
- return translator.translate(ast);
- }
- class TcbExpressionTranslator {
- tcb;
- scope;
- constructor(tcb, scope) {
- this.tcb = tcb;
- this.scope = scope;
- }
- translate(ast) {
- // `astToTypescript` actually does the conversion. A special resolver `tcbResolve` is passed
- // which interprets specific expression nodes that interact with the `ImplicitReceiver`. These
- // nodes actually refer to identifiers within the current scope.
- return astToTypescript(ast, (ast) => this.resolve(ast), this.tcb.env.config);
- }
- /**
- * Resolve an `AST` expression within the given scope.
- *
- * Some `AST` expressions refer to top-level concepts (references, variables, the component
- * context). This method assists in resolving those.
- */
- resolve(ast) {
- if (ast instanceof PropertyRead &&
- ast.receiver instanceof ImplicitReceiver &&
- !(ast.receiver instanceof ThisReceiver)) {
- // Try to resolve a bound target for this expression. If no such target is available, then
- // the expression is referencing the top-level component context. In that case, `null` is
- // returned here to let it fall through resolution so it will be caught when the
- // `ImplicitReceiver` is resolved in the branch below.
- const target = this.tcb.boundTarget.getExpressionTarget(ast);
- const targetExpression = target === null ? null : this.getTargetNodeExpression(target, ast);
- if (target instanceof LetDeclaration$1 &&
- !this.isValidLetDeclarationAccess(target, ast)) {
- this.tcb.oobRecorder.letUsedBeforeDefinition(this.tcb.id, ast, target);
- // Cast the expression to `any` so we don't produce additional diagnostics.
- // We don't use `markIgnoreForDiagnostics` here, because it won't prevent duplicate
- // diagnostics for nested accesses in cases like `@let value = value.foo.bar.baz`.
- if (targetExpression !== null) {
- return ts.factory.createAsExpression(targetExpression, ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
- }
- }
- return targetExpression;
- }
- else if (ast instanceof PropertyWrite && ast.receiver instanceof ImplicitReceiver) {
- const target = this.tcb.boundTarget.getExpressionTarget(ast);
- if (target === null) {
- return null;
- }
- const targetExpression = this.getTargetNodeExpression(target, ast);
- const expr = this.translate(ast.value);
- const result = ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression(targetExpression, ts.SyntaxKind.EqualsToken, expr));
- addParseSpanInfo(result, ast.sourceSpan);
- // Ignore diagnostics from TS produced for writes to `@let` and re-report them using
- // our own infrastructure. We can't rely on the TS reporting, because it includes
- // the name of the auto-generated TCB variable name.
- if (target instanceof LetDeclaration$1) {
- markIgnoreDiagnostics(result);
- this.tcb.oobRecorder.illegalWriteToLetDeclaration(this.tcb.id, ast, target);
- }
- return result;
- }
- else if (ast instanceof ImplicitReceiver) {
- // AST instances representing variables and references look very similar to property reads
- // or method calls from the component context: both have the shape
- // PropertyRead(ImplicitReceiver, 'propName') or Call(ImplicitReceiver, 'methodName').
- //
- // `translate` will first try to `resolve` the outer PropertyRead/Call. If this works,
- // it's because the `BoundTarget` found an expression target for the whole expression, and
- // therefore `translate` will never attempt to `resolve` the ImplicitReceiver of that
- // PropertyRead/Call.
- //
- // Therefore if `resolve` is called on an `ImplicitReceiver`, it's because no outer
- // PropertyRead/Call resolved to a variable or reference, and therefore this is a
- // property read or method call on the component context itself.
- return ts.factory.createThis();
- }
- else if (ast instanceof BindingPipe) {
- const expr = this.translate(ast.exp);
- const pipeMeta = this.tcb.getPipeByName(ast.name);
- let pipe;
- if (pipeMeta === null) {
- // No pipe by that name exists in scope. Record this as an error.
- this.tcb.oobRecorder.missingPipe(this.tcb.id, ast);
- // Use an 'any' value to at least allow the rest of the expression to be checked.
- pipe = ANY_EXPRESSION;
- }
- else if (pipeMeta.isExplicitlyDeferred &&
- this.tcb.boundTarget.getEagerlyUsedPipes().includes(ast.name)) {
- // This pipe was defer-loaded (included into `@Component.deferredImports`),
- // but was used outside of a `@defer` block, which is the error.
- this.tcb.oobRecorder.deferredPipeUsedEagerly(this.tcb.id, ast);
- // Use an 'any' value to at least allow the rest of the expression to be checked.
- pipe = ANY_EXPRESSION;
- }
- else {
- // Use a variable declared as the pipe's type.
- pipe = this.tcb.env.pipeInst(pipeMeta.ref);
- }
- const args = ast.args.map((arg) => this.translate(arg));
- let methodAccess = ts.factory.createPropertyAccessExpression(pipe, 'transform');
- addParseSpanInfo(methodAccess, ast.nameSpan);
- if (!this.tcb.env.config.checkTypeOfPipes) {
- methodAccess = ts.factory.createAsExpression(methodAccess, ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
- }
- const result = ts.factory.createCallExpression(
- /* expression */ methodAccess,
- /* typeArguments */ undefined,
- /* argumentsArray */ [expr, ...args]);
- addParseSpanInfo(result, ast.sourceSpan);
- return result;
- }
- else if ((ast instanceof Call || ast instanceof SafeCall) &&
- (ast.receiver instanceof PropertyRead || ast.receiver instanceof SafePropertyRead)) {
- // Resolve the special `$any(expr)` syntax to insert a cast of the argument to type `any`.
- // `$any(expr)` -> `expr as any`
- if (ast.receiver.receiver instanceof ImplicitReceiver &&
- !(ast.receiver.receiver instanceof ThisReceiver) &&
- ast.receiver.name === '$any' &&
- ast.args.length === 1) {
- const expr = this.translate(ast.args[0]);
- const exprAsAny = ts.factory.createAsExpression(expr, ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
- const result = ts.factory.createParenthesizedExpression(exprAsAny);
- addParseSpanInfo(result, ast.sourceSpan);
- return result;
- }
- // Attempt to resolve a bound target for the method, and generate the method call if a target
- // could be resolved. If no target is available, then the method is referencing the top-level
- // component context, in which case `null` is returned to let the `ImplicitReceiver` being
- // resolved to the component context.
- const target = this.tcb.boundTarget.getExpressionTarget(ast);
- if (target === null) {
- return null;
- }
- const receiver = this.getTargetNodeExpression(target, ast);
- const method = wrapForDiagnostics(receiver);
- addParseSpanInfo(method, ast.receiver.nameSpan);
- const args = ast.args.map((arg) => this.translate(arg));
- const node = ts.factory.createCallExpression(method, undefined, args);
- addParseSpanInfo(node, ast.sourceSpan);
- return node;
- }
- else {
- // This AST isn't special after all.
- return null;
- }
- }
- getTargetNodeExpression(targetNode, expressionNode) {
- const expr = this.scope.resolve(targetNode);
- addParseSpanInfo(expr, expressionNode.sourceSpan);
- return expr;
- }
- isValidLetDeclarationAccess(target, ast) {
- const targetStart = target.sourceSpan.start.offset;
- const targetEnd = target.sourceSpan.end.offset;
- const astStart = ast.sourceSpan.start;
- // We only flag local references that occur before the declaration, because embedded views
- // are updated before the child views. In practice this means that something like
- // `<ng-template [ngIf]="true">{{value}}</ng-template> @let value = 1;` is valid.
- return (targetStart < astStart && astStart > targetEnd) || !this.scope.isLocal(target);
- }
- }
- /**
- * Call the type constructor of a directive instance on a given template node, inferring a type for
- * the directive instance from any bound inputs.
- */
- function tcbCallTypeCtor(dir, tcb, inputs) {
- const typeCtor = tcb.env.typeCtorFor(dir);
- // Construct an array of `ts.PropertyAssignment`s for each of the directive's inputs.
- const members = inputs.map((input) => {
- const propertyName = ts.factory.createStringLiteral(input.field);
- if (input.type === 'binding') {
- // For bound inputs, the property is assigned the binding expression.
- let expr = widenBinding(input.expression, tcb);
- if (input.isTwoWayBinding && tcb.env.config.allowSignalsInTwoWayBindings) {
- expr = unwrapWritableSignal(expr, tcb);
- }
- const assignment = ts.factory.createPropertyAssignment(propertyName, wrapForDiagnostics(expr));
- addParseSpanInfo(assignment, input.sourceSpan);
- return assignment;
- }
- else {
- // A type constructor is required to be called with all input properties, so any unset
- // inputs are simply assigned a value of type `any` to ignore them.
- return ts.factory.createPropertyAssignment(propertyName, ANY_EXPRESSION);
- }
- });
- // Call the `ngTypeCtor` method on the directive class, with an object literal argument created
- // from the matched inputs.
- return ts.factory.createCallExpression(
- /* expression */ typeCtor,
- /* typeArguments */ undefined,
- /* argumentsArray */ [ts.factory.createObjectLiteralExpression(members)]);
- }
- function getBoundAttributes(directive, node) {
- const boundInputs = [];
- const processAttribute = (attr) => {
- // Skip non-property bindings.
- if (attr instanceof BoundAttribute &&
- attr.type !== exports.BindingType.Property &&
- attr.type !== exports.BindingType.TwoWay) {
- return;
- }
- // Skip the attribute if the directive does not have an input for it.
- const inputs = directive.inputs.getByBindingPropertyName(attr.name);
- if (inputs !== null) {
- boundInputs.push({
- attribute: attr,
- inputs: inputs.map((input) => {
- return {
- fieldName: input.classPropertyName,
- required: input.required,
- transformType: input.transform?.type || null,
- isSignal: input.isSignal,
- isTwoWayBinding: attr instanceof BoundAttribute && attr.type === exports.BindingType.TwoWay,
- };
- }),
- });
- }
- };
- node.inputs.forEach(processAttribute);
- node.attributes.forEach(processAttribute);
- if (node instanceof Template) {
- node.templateAttrs.forEach(processAttribute);
- }
- return boundInputs;
- }
- /**
- * Translates the given attribute binding to a `ts.Expression`.
- */
- function translateInput(attr, tcb, scope) {
- if (attr instanceof BoundAttribute) {
- // Produce an expression representing the value of the binding.
- return tcbExpression(attr.value, tcb, scope);
- }
- else {
- // For regular attributes with a static string value, use the represented string literal.
- return ts.factory.createStringLiteral(attr.value);
- }
- }
- /**
- * Potentially widens the type of `expr` according to the type-checking configuration.
- */
- function widenBinding(expr, tcb) {
- if (!tcb.env.config.checkTypeOfInputBindings) {
- // If checking the type of bindings is disabled, cast the resulting expression to 'any'
- // before the assignment.
- return tsCastToAny(expr);
- }
- else if (!tcb.env.config.strictNullInputBindings) {
- if (ts.isObjectLiteralExpression(expr) || ts.isArrayLiteralExpression(expr)) {
- // Object literals and array literals should not be wrapped in non-null assertions as that
- // would cause literals to be prematurely widened, resulting in type errors when assigning
- // into a literal type.
- return expr;
- }
- else {
- // If strict null checks are disabled, erase `null` and `undefined` from the type by
- // wrapping the expression in a non-null assertion.
- return ts.factory.createNonNullExpression(expr);
- }
- }
- else {
- // No widening is requested, use the expression as is.
- return expr;
- }
- }
- /**
- * Wraps an expression in an `unwrapSignal` call which extracts the signal's value.
- */
- function unwrapWritableSignal(expression, tcb) {
- const unwrapRef = tcb.env.referenceExternalSymbol(Identifiers.unwrapWritableSignal.moduleName, Identifiers.unwrapWritableSignal.name);
- return ts.factory.createCallExpression(unwrapRef, undefined, [expression]);
- }
- const EVENT_PARAMETER = '$event';
- /**
- * Creates an arrow function to be used as handler function for event bindings. The handler
- * function has a single parameter `$event` and the bound event's handler `AST` represented as a
- * TypeScript expression as its body.
- *
- * When `eventType` is set to `Infer`, the `$event` parameter will not have an explicit type. This
- * allows for the created handler function to have its `$event` parameter's type inferred based on
- * how it's used, to enable strict type checking of event bindings. When set to `Any`, the `$event`
- * parameter will have an explicit `any` type, effectively disabling strict type checking of event
- * bindings. Alternatively, an explicit type can be passed for the `$event` parameter.
- */
- function tcbCreateEventHandler(event, tcb, scope, eventType) {
- const handler = tcbEventHandlerExpression(event.handler, tcb, scope);
- const statements = [];
- // TODO(crisbeto): remove the `checkTwoWayBoundEvents` check in v20.
- if (event.type === exports.ParsedEventType.TwoWay && tcb.env.config.checkTwoWayBoundEvents) {
- // If we're dealing with a two-way event, we create a variable initialized to the unwrapped
- // signal value of the expression and then we assign `$event` to it. Note that in most cases
- // this will already be covered by the corresponding input binding, however it allows us to
- // handle the case where the input has a wider type than the output (see #58971).
- const target = tcb.allocateId();
- const assignment = ts.factory.createBinaryExpression(target, ts.SyntaxKind.EqualsToken, ts.factory.createIdentifier(EVENT_PARAMETER));
- statements.push(tsCreateVariable(target, tcb.env.config.allowSignalsInTwoWayBindings ? unwrapWritableSignal(handler, tcb) : handler), ts.factory.createExpressionStatement(assignment));
- }
- else {
- statements.push(ts.factory.createExpressionStatement(handler));
- }
- let eventParamType;
- if (eventType === 0 /* EventParamType.Infer */) {
- eventParamType = undefined;
- }
- else if (eventType === 1 /* EventParamType.Any */) {
- eventParamType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
- }
- else {
- eventParamType = eventType;
- }
- // Obtain all guards that have been applied to the scope and its parents, as they have to be
- // repeated within the handler function for their narrowing to be in effect within the handler.
- const guards = scope.guards();
- let body = ts.factory.createBlock(statements);
- if (guards !== null) {
- // Wrap the body in an `if` statement containing all guards that have to be applied.
- body = ts.factory.createBlock([ts.factory.createIfStatement(guards, body)]);
- }
- const eventParam = ts.factory.createParameterDeclaration(
- /* modifiers */ undefined,
- /* dotDotDotToken */ undefined,
- /* name */ EVENT_PARAMETER,
- /* questionToken */ undefined,
- /* type */ eventParamType);
- addExpressionIdentifier(eventParam, ExpressionIdentifier.EVENT_PARAMETER);
- // Return an arrow function instead of a function expression to preserve the `this` context.
- return ts.factory.createArrowFunction(
- /* modifiers */ undefined,
- /* typeParameters */ undefined,
- /* parameters */ [eventParam],
- /* type */ ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
- /* equalsGreaterThanToken */ undefined,
- /* body */ body);
- }
- /**
- * Similar to `tcbExpression`, this function converts the provided `AST` expression into a
- * `ts.Expression`, with special handling of the `$event` variable that can be used within event
- * bindings.
- */
- function tcbEventHandlerExpression(ast, tcb, scope) {
- const translator = new TcbEventHandlerTranslator(tcb, scope);
- return translator.translate(ast);
- }
- function checkSplitTwoWayBinding(inputName, output, inputs, tcb) {
- const input = inputs.find((input) => input.name === inputName);
- if (input === undefined || input.sourceSpan !== output.sourceSpan) {
- return false;
- }
- // Input consumer should be a directive because it's claimed
- const inputConsumer = tcb.boundTarget.getConsumerOfBinding(input);
- const outputConsumer = tcb.boundTarget.getConsumerOfBinding(output);
- if (outputConsumer === null ||
- inputConsumer.ref === undefined ||
- outputConsumer instanceof Template) {
- return false;
- }
- if (outputConsumer instanceof Element$1) {
- tcb.oobRecorder.splitTwoWayBinding(tcb.id, input, output, inputConsumer.ref.node, outputConsumer);
- return true;
- }
- else if (outputConsumer.ref !== inputConsumer.ref) {
- tcb.oobRecorder.splitTwoWayBinding(tcb.id, input, output, inputConsumer.ref.node, outputConsumer.ref.node);
- return true;
- }
- return false;
- }
- class TcbEventHandlerTranslator extends TcbExpressionTranslator {
- resolve(ast) {
- // Recognize a property read on the implicit receiver corresponding with the event parameter
- // that is available in event bindings. Since this variable is a parameter of the handler
- // function that the converted expression becomes a child of, just create a reference to the
- // parameter by its name.
- if (ast instanceof PropertyRead &&
- ast.receiver instanceof ImplicitReceiver &&
- !(ast.receiver instanceof ThisReceiver) &&
- ast.name === EVENT_PARAMETER) {
- const event = ts.factory.createIdentifier(EVENT_PARAMETER);
- addParseSpanInfo(event, ast.nameSpan);
- return event;
- }
- return super.resolve(ast);
- }
- isValidLetDeclarationAccess() {
- // Event listeners are allowed to read `@let` declarations before
- // they're declared since the callback won't be executed immediately.
- return true;
- }
- }
- class TcbForLoopTrackTranslator extends TcbExpressionTranslator {
- block;
- allowedVariables;
- constructor(tcb, scope, block) {
- super(tcb, scope);
- this.block = block;
- // Tracking expressions are only allowed to read the `$index`,
- // the item and properties off the component instance.
- this.allowedVariables = new Set([block.item]);
- for (const variable of block.contextVariables) {
- if (variable.value === '$index') {
- this.allowedVariables.add(variable);
- }
- }
- }
- resolve(ast) {
- if (ast instanceof PropertyRead && ast.receiver instanceof ImplicitReceiver) {
- const target = this.tcb.boundTarget.getExpressionTarget(ast);
- if (target !== null &&
- (!(target instanceof Variable) || !this.allowedVariables.has(target))) {
- this.tcb.oobRecorder.illegalForLoopTrackAccess(this.tcb.id, this.block, ast);
- }
- }
- return super.resolve(ast);
- }
- }
- /**
- * An `Environment` representing the single type-checking file into which most (if not all) Type
- * Check Blocks (TCBs) will be generated.
- *
- * The `TypeCheckFile` hosts multiple TCBs and allows the sharing of declarations (e.g. type
- * constructors) between them. Rather than return such declarations via `getPreludeStatements()`, it
- * hoists them to the top of the generated `ts.SourceFile`.
- */
- class TypeCheckFile extends Environment {
- fileName;
- nextTcbId = 1;
- tcbStatements = [];
- constructor(fileName, config, refEmitter, reflector, compilerHost) {
- super(config, new ImportManager({
- // This minimizes noticeable changes with older versions of `ImportManager`.
- forceGenerateNamespacesForNewImports: true,
- // Type check block code affects code completion and fix suggestions.
- // We want to encourage single quotes for now, like we always did.
- shouldUseSingleQuotes: () => true,
- }), refEmitter, reflector, ts.createSourceFile(compilerHost.getCanonicalFileName(fileName), '', ts.ScriptTarget.Latest, true));
- this.fileName = fileName;
- }
- addTypeCheckBlock(ref, meta, domSchemaChecker, oobRecorder, genericContextBehavior) {
- const fnId = ts.factory.createIdentifier(`_tcb${this.nextTcbId++}`);
- const fn = generateTypeCheckBlock(this, ref, fnId, meta, domSchemaChecker, oobRecorder, genericContextBehavior);
- this.tcbStatements.push(fn);
- }
- render(removeComments) {
- // NOTE: We are conditionally adding imports whenever we discover signal inputs. This has a
- // risk of changing the import graph of the TypeScript program, degrading incremental program
- // re-use due to program structure changes. For type check block files, we are ensuring an
- // import to e.g. `@angular/core` always exists to guarantee a stable graph.
- ensureTypeCheckFilePreparationImports(this);
- const importChanges = this.importManager.finalize();
- if (importChanges.updatedImports.size > 0) {
- throw new Error('AssertionError: Expected no imports to be updated for a new type check file.');
- }
- const printer = ts.createPrinter({ removeComments });
- let source = '';
- const newImports = importChanges.newImports.get(this.contextFile.fileName);
- if (newImports !== undefined) {
- source += newImports
- .map((i) => printer.printNode(ts.EmitHint.Unspecified, i, this.contextFile))
- .join('\n');
- }
- source += '\n';
- for (const stmt of this.pipeInstStatements) {
- source += printer.printNode(ts.EmitHint.Unspecified, stmt, this.contextFile) + '\n';
- }
- for (const stmt of this.typeCtorStatements) {
- source += printer.printNode(ts.EmitHint.Unspecified, stmt, this.contextFile) + '\n';
- }
- source += '\n';
- for (const stmt of this.tcbStatements) {
- source += printer.printNode(ts.EmitHint.Unspecified, stmt, this.contextFile) + '\n';
- }
- // Ensure the template type-checking file is an ES module. Otherwise, it's interpreted as some
- // kind of global namespace in TS, which forces a full re-typecheck of the user's program that
- // is somehow more expensive than the initial parse.
- source += '\nexport const IS_A_MODULE = true;\n';
- return source;
- }
- getPreludeStatements() {
- return [];
- }
- }
- /**
- * How a type-checking context should handle operations which would require inlining.
- */
- var InliningMode;
- (function (InliningMode) {
- /**
- * Use inlining operations when required.
- */
- InliningMode[InliningMode["InlineOps"] = 0] = "InlineOps";
- /**
- * Produce diagnostics if an operation would require inlining.
- */
- InliningMode[InliningMode["Error"] = 1] = "Error";
- })(InliningMode || (InliningMode = {}));
- /**
- * A template type checking context for a program.
- *
- * The `TypeCheckContext` allows registration of directives to be type checked.
- */
- class TypeCheckContextImpl {
- config;
- compilerHost;
- refEmitter;
- reflector;
- host;
- inlining;
- perf;
- fileMap = new Map();
- constructor(config, compilerHost, refEmitter, reflector, host, inlining, perf) {
- this.config = config;
- this.compilerHost = compilerHost;
- this.refEmitter = refEmitter;
- this.reflector = reflector;
- this.host = host;
- this.inlining = inlining;
- this.perf = perf;
- if (inlining === InliningMode.Error && config.useInlineTypeConstructors) {
- // We cannot use inlining for type checking since this environment does not support it.
- throw new Error(`AssertionError: invalid inlining configuration.`);
- }
- }
- /**
- * A `Map` of `ts.SourceFile`s that the context has seen to the operations (additions of methods
- * or type-check blocks) that need to be eventually performed on that file.
- */
- opMap = new Map();
- /**
- * Tracks when an a particular class has a pending type constructor patching operation already
- * queued.
- */
- typeCtorPending = new Set();
- /**
- * Register a template to potentially be type-checked.
- *
- * Implements `TypeCheckContext.addTemplate`.
- */
- addDirective(ref, binder, schemas, templateContext, isStandalone) {
- if (!this.host.shouldCheckClass(ref.node)) {
- return;
- }
- const fileData = this.dataForFile(ref.node.getSourceFile());
- const shimData = this.pendingShimForClass(ref.node);
- const id = fileData.sourceManager.getTypeCheckId(ref.node);
- const templateParsingDiagnostics = [];
- if (templateContext !== null && templateContext.parseErrors !== null) {
- templateParsingDiagnostics.push(...getTemplateDiagnostics(templateContext.parseErrors, id, templateContext.sourceMapping));
- }
- const boundTarget = binder.bind({ template: templateContext?.nodes });
- if (this.inlining === InliningMode.InlineOps) {
- // Get all of the directives used in the template and record inline type constructors when
- // required.
- for (const dir of boundTarget.getUsedDirectives()) {
- const dirRef = dir.ref;
- const dirNode = dirRef.node;
- if (!dir.isGeneric || !requiresInlineTypeCtor(dirNode, this.reflector, shimData.file)) {
- // inlining not required
- continue;
- }
- // Add an inline type constructor operation for the directive.
- this.addInlineTypeCtor(fileData, dirNode.getSourceFile(), dirRef, {
- fnName: 'ngTypeCtor',
- // The constructor should have a body if the directive comes from a .ts file, but not if
- // it comes from a .d.ts file. .d.ts declarations don't have bodies.
- body: !dirNode.getSourceFile().isDeclarationFile,
- fields: {
- inputs: dir.inputs,
- // TODO(alxhub): support queries
- queries: dir.queries,
- },
- coercedInputFields: dir.coercedInputFields,
- });
- }
- }
- shimData.data.set(id, {
- template: templateContext?.nodes || null,
- boundTarget,
- templateParsingDiagnostics,
- });
- const usedPipes = [];
- if (templateContext !== null) {
- for (const name of boundTarget.getUsedPipes()) {
- if (templateContext.pipes.has(name)) {
- usedPipes.push(templateContext.pipes.get(name).ref);
- }
- }
- }
- const inliningRequirement = requiresInlineTypeCheckBlock(ref, shimData.file, usedPipes, this.reflector);
- // If inlining is not supported, but is required for either the TCB or one of its directive
- // dependencies, then exit here with an error.
- if (this.inlining === InliningMode.Error &&
- inliningRequirement === TcbInliningRequirement.MustInline) {
- // This template cannot be supported because the underlying strategy does not support inlining
- // and inlining would be required.
- // Record diagnostics to indicate the issues with this template.
- shimData.oobRecorder.requiresInlineTcb(id, ref.node);
- // Checking this template would be unsupported, so don't try.
- this.perf.eventCount(exports.PerfEvent.SkipGenerateTcbNoInline);
- return;
- }
- if (templateContext !== null) {
- fileData.sourceManager.captureTemplateSource(id, templateContext.sourceMapping, templateContext.file);
- }
- const meta = {
- id,
- boundTarget,
- pipes: templateContext?.pipes || null,
- schemas,
- isStandalone,
- preserveWhitespaces: templateContext?.preserveWhitespaces ?? false,
- };
- this.perf.eventCount(exports.PerfEvent.GenerateTcb);
- if (inliningRequirement !== TcbInliningRequirement.None &&
- this.inlining === InliningMode.InlineOps) {
- // This class didn't meet the requirements for external type checking, so generate an inline
- // TCB for the class.
- this.addInlineTypeCheckBlock(fileData, shimData, ref, meta);
- }
- else if (inliningRequirement === TcbInliningRequirement.ShouldInlineForGenericBounds &&
- this.inlining === InliningMode.Error) {
- // It's suggested that this TCB should be generated inline due to the class' generic
- // bounds, but inlining is not supported by the current environment. Use a non-inline type
- // check block, but fall back to `any` generic parameters since the generic bounds can't be
- // referenced in that context. This will infer a less useful type for the class, but allow
- // for type-checking it in an environment where that would not be possible otherwise.
- shimData.file.addTypeCheckBlock(ref, meta, shimData.domSchemaChecker, shimData.oobRecorder, TcbGenericContextBehavior.FallbackToAny);
- }
- else {
- shimData.file.addTypeCheckBlock(ref, meta, shimData.domSchemaChecker, shimData.oobRecorder, TcbGenericContextBehavior.UseEmitter);
- }
- }
- /**
- * Record a type constructor for the given `node` with the given `ctorMetadata`.
- */
- addInlineTypeCtor(fileData, sf, ref, ctorMeta) {
- if (this.typeCtorPending.has(ref.node)) {
- return;
- }
- this.typeCtorPending.add(ref.node);
- // Lazily construct the operation map.
- if (!this.opMap.has(sf)) {
- this.opMap.set(sf, []);
- }
- const ops = this.opMap.get(sf);
- // Push a `TypeCtorOp` into the operation queue for the source file.
- ops.push(new TypeCtorOp(ref, this.reflector, ctorMeta));
- fileData.hasInlines = true;
- }
- /**
- * Transform a `ts.SourceFile` into a version that includes type checking code.
- *
- * If this particular `ts.SourceFile` requires changes, the text representing its new contents
- * will be returned. Otherwise, a `null` return indicates no changes were necessary.
- */
- transform(sf) {
- // If there are no operations pending for this particular file, return `null` to indicate no
- // changes.
- if (!this.opMap.has(sf)) {
- return null;
- }
- // Use a `ts.Printer` to generate source code.
- const printer = ts.createPrinter({ omitTrailingSemicolon: true });
- // Imports may need to be added to the file to support type-checking of directives
- // used in the template within it.
- const importManager = new ImportManager({
- // This minimizes noticeable changes with older versions of `ImportManager`.
- forceGenerateNamespacesForNewImports: true,
- // Type check block code affects code completion and fix suggestions.
- // We want to encourage single quotes for now, like we always did.
- shouldUseSingleQuotes: () => true,
- });
- // Execute ops.
- // Each Op has a splitPoint index into the text where it needs to be inserted.
- const updates = this.opMap
- .get(sf)
- .map((op) => {
- return {
- pos: op.splitPoint,
- text: op.execute(importManager, sf, this.refEmitter, printer),
- };
- });
- const { newImports, updatedImports } = importManager.finalize();
- // Capture new imports
- if (newImports.has(sf.fileName)) {
- newImports.get(sf.fileName).forEach((newImport) => {
- updates.push({
- pos: 0,
- text: printer.printNode(ts.EmitHint.Unspecified, newImport, sf),
- });
- });
- }
- // Capture updated imports
- for (const [oldBindings, newBindings] of updatedImports.entries()) {
- if (oldBindings.getSourceFile() !== sf) {
- throw new Error('Unexpected updates to unrelated source files.');
- }
- updates.push({
- pos: oldBindings.getStart(),
- deletePos: oldBindings.getEnd(),
- text: printer.printNode(ts.EmitHint.Unspecified, newBindings, sf),
- });
- }
- const result = new MagicString(sf.text, { filename: sf.fileName });
- for (const update of updates) {
- if (update.deletePos !== undefined) {
- result.remove(update.pos, update.deletePos);
- }
- result.appendLeft(update.pos, update.text);
- }
- return result.toString();
- }
- finalize() {
- // First, build the map of updates to source files.
- const updates = new Map();
- for (const originalSf of this.opMap.keys()) {
- const newText = this.transform(originalSf);
- if (newText !== null) {
- updates.set(absoluteFromSourceFile(originalSf), {
- newText,
- originalFile: originalSf,
- });
- }
- }
- // Then go through each input file that has pending code generation operations.
- for (const [sfPath, pendingFileData] of this.fileMap) {
- // For each input file, consider generation operations for each of its shims.
- for (const pendingShimData of pendingFileData.shimData.values()) {
- this.host.recordShimData(sfPath, {
- genesisDiagnostics: [
- ...pendingShimData.domSchemaChecker.diagnostics,
- ...pendingShimData.oobRecorder.diagnostics,
- ],
- hasInlines: pendingFileData.hasInlines,
- path: pendingShimData.file.fileName,
- data: pendingShimData.data,
- });
- const sfText = pendingShimData.file.render(false /* removeComments */);
- updates.set(pendingShimData.file.fileName, {
- newText: sfText,
- // Shim files do not have an associated original file.
- originalFile: null,
- });
- }
- }
- return updates;
- }
- addInlineTypeCheckBlock(fileData, shimData, ref, tcbMeta) {
- const sf = ref.node.getSourceFile();
- if (!this.opMap.has(sf)) {
- this.opMap.set(sf, []);
- }
- const ops = this.opMap.get(sf);
- ops.push(new InlineTcbOp(ref, tcbMeta, this.config, this.reflector, shimData.domSchemaChecker, shimData.oobRecorder));
- fileData.hasInlines = true;
- }
- pendingShimForClass(node) {
- const fileData = this.dataForFile(node.getSourceFile());
- const shimPath = TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(node.getSourceFile()));
- if (!fileData.shimData.has(shimPath)) {
- fileData.shimData.set(shimPath, {
- domSchemaChecker: new RegistryDomSchemaChecker(fileData.sourceManager),
- oobRecorder: new OutOfBandDiagnosticRecorderImpl(fileData.sourceManager),
- file: new TypeCheckFile(shimPath, this.config, this.refEmitter, this.reflector, this.compilerHost),
- data: new Map(),
- });
- }
- return fileData.shimData.get(shimPath);
- }
- dataForFile(sf) {
- const sfPath = absoluteFromSourceFile(sf);
- if (!this.fileMap.has(sfPath)) {
- const data = {
- hasInlines: false,
- sourceManager: this.host.getSourceManager(sfPath),
- shimData: new Map(),
- };
- this.fileMap.set(sfPath, data);
- }
- return this.fileMap.get(sfPath);
- }
- }
- function getTemplateDiagnostics(parseErrors, templateId, sourceMapping) {
- return parseErrors.map((error) => {
- const span = error.span;
- if (span.start.offset === span.end.offset) {
- // Template errors can contain zero-length spans, if the error occurs at a single point.
- // However, TypeScript does not handle displaying a zero-length diagnostic very well, so
- // increase the ending offset by 1 for such errors, to ensure the position is shown in the
- // diagnostic.
- span.end.offset++;
- }
- return makeTemplateDiagnostic(templateId, sourceMapping, span, ts.DiagnosticCategory.Error, ngErrorCode(exports.ErrorCode.TEMPLATE_PARSE_ERROR), error.msg);
- });
- }
- /**
- * A type check block operation which produces inline type check code for a particular directive.
- */
- class InlineTcbOp {
- ref;
- meta;
- config;
- reflector;
- domSchemaChecker;
- oobRecorder;
- constructor(ref, meta, config, reflector, domSchemaChecker, oobRecorder) {
- this.ref = ref;
- this.meta = meta;
- this.config = config;
- this.reflector = reflector;
- this.domSchemaChecker = domSchemaChecker;
- this.oobRecorder = oobRecorder;
- }
- /**
- * Type check blocks are inserted immediately after the end of the directve class.
- */
- get splitPoint() {
- return this.ref.node.end + 1;
- }
- execute(im, sf, refEmitter, printer) {
- const env = new Environment(this.config, im, refEmitter, this.reflector, sf);
- const fnName = ts.factory.createIdentifier(`_tcb_${this.ref.node.pos}`);
- // Inline TCBs should copy any generic type parameter nodes directly, as the TCB code is
- // inlined into the class in a context where that will always be legal.
- const fn = generateTypeCheckBlock(env, this.ref, fnName, this.meta, this.domSchemaChecker, this.oobRecorder, TcbGenericContextBehavior.CopyClassNodes);
- return printer.printNode(ts.EmitHint.Unspecified, fn, sf);
- }
- }
- /**
- * A type constructor operation which produces type constructor code for a particular directive.
- */
- class TypeCtorOp {
- ref;
- reflector;
- meta;
- constructor(ref, reflector, meta) {
- this.ref = ref;
- this.reflector = reflector;
- this.meta = meta;
- }
- /**
- * Type constructor operations are inserted immediately before the end of the directive class.
- */
- get splitPoint() {
- return this.ref.node.end - 1;
- }
- execute(im, sf, refEmitter, printer) {
- const emitEnv = new ReferenceEmitEnvironment(im, refEmitter, this.reflector, sf);
- const tcb = generateInlineTypeCtor(emitEnv, this.ref.node, this.meta);
- return printer.printNode(ts.EmitHint.Unspecified, tcb, sf);
- }
- }
- const LF_CHAR = 10;
- const CR_CHAR = 13;
- const LINE_SEP_CHAR = 8232;
- const PARAGRAPH_CHAR = 8233;
- /** Gets the line and character for the given position from the line starts map. */
- function getLineAndCharacterFromPosition(lineStartsMap, position) {
- const lineIndex = findClosestLineStartPosition(lineStartsMap, position);
- return { character: position - lineStartsMap[lineIndex], line: lineIndex };
- }
- /**
- * Computes the line start map of the given text. This can be used in order to
- * retrieve the line and character of a given text position index.
- */
- function computeLineStartsMap(text) {
- const result = [0];
- let pos = 0;
- while (pos < text.length) {
- const char = text.charCodeAt(pos++);
- // Handles the "CRLF" line break. In that case we peek the character
- // after the "CR" and check if it is a line feed.
- if (char === CR_CHAR) {
- if (text.charCodeAt(pos) === LF_CHAR) {
- pos++;
- }
- result.push(pos);
- }
- else if (char === LF_CHAR || char === LINE_SEP_CHAR || char === PARAGRAPH_CHAR) {
- result.push(pos);
- }
- }
- result.push(pos);
- return result;
- }
- /** Finds the closest line start for the given position. */
- function findClosestLineStartPosition(linesMap, position, low = 0, high = linesMap.length - 1) {
- while (low <= high) {
- const pivotIdx = Math.floor((low + high) / 2);
- const pivotEl = linesMap[pivotIdx];
- if (pivotEl === position) {
- return pivotIdx;
- }
- else if (position > pivotEl) {
- low = pivotIdx + 1;
- }
- else {
- high = pivotIdx - 1;
- }
- }
- // In case there was no exact match, return the closest "lower" line index. We also
- // subtract the index by one because want the index of the previous line start.
- return low - 1;
- }
- /**
- * Represents the source of a template that was processed during type-checking. This information is
- * used when translating parse offsets in diagnostics back to their original line/column location.
- */
- class TemplateSource {
- mapping;
- file;
- lineStarts = null;
- constructor(mapping, file) {
- this.mapping = mapping;
- this.file = file;
- }
- toParseSourceSpan(start, end) {
- const startLoc = this.toParseLocation(start);
- const endLoc = this.toParseLocation(end);
- return new ParseSourceSpan(startLoc, endLoc);
- }
- toParseLocation(position) {
- const lineStarts = this.acquireLineStarts();
- const { line, character } = getLineAndCharacterFromPosition(lineStarts, position);
- return new ParseLocation(this.file, position, line, character);
- }
- acquireLineStarts() {
- if (this.lineStarts === null) {
- this.lineStarts = computeLineStartsMap(this.file.content);
- }
- return this.lineStarts;
- }
- }
- /**
- * Assigns IDs for type checking and keeps track of their origins.
- *
- * Implements `TypeCheckSourceResolver` to resolve the source of a template based on these IDs.
- */
- class DirectiveSourceManager {
- /**
- * This map keeps track of all template sources that have been type-checked by the id that is
- * attached to a TCB's function declaration as leading trivia. This enables translation of
- * diagnostics produced for TCB code to their source location in the template.
- */
- templateSources = new Map();
- getTypeCheckId(node) {
- return getTypeCheckId(node);
- }
- captureTemplateSource(id, mapping, file) {
- this.templateSources.set(id, new TemplateSource(mapping, file));
- }
- getTemplateSourceMapping(id) {
- if (!this.templateSources.has(id)) {
- throw new Error(`Unexpected unknown type check ID: ${id}`);
- }
- return this.templateSources.get(id).mapping;
- }
- toTemplateParseSourceSpan(id, span) {
- if (!this.templateSources.has(id)) {
- return null;
- }
- const templateSource = this.templateSources.get(id);
- return templateSource.toParseSourceSpan(span.start, span.end);
- }
- }
- /**
- * Generates and caches `Symbol`s for various template structures for a given component.
- *
- * The `SymbolBuilder` internally caches the `Symbol`s it creates, and must be destroyed and
- * replaced if the component's template changes.
- */
- class SymbolBuilder {
- tcbPath;
- tcbIsShim;
- typeCheckBlock;
- typeCheckData;
- componentScopeReader;
- getTypeChecker;
- symbolCache = new Map();
- constructor(tcbPath, tcbIsShim, typeCheckBlock, typeCheckData, componentScopeReader,
- // The `ts.TypeChecker` depends on the current type-checking program, and so must be requested
- // on-demand instead of cached.
- getTypeChecker) {
- this.tcbPath = tcbPath;
- this.tcbIsShim = tcbIsShim;
- this.typeCheckBlock = typeCheckBlock;
- this.typeCheckData = typeCheckData;
- this.componentScopeReader = componentScopeReader;
- this.getTypeChecker = getTypeChecker;
- }
- getSymbol(node) {
- if (this.symbolCache.has(node)) {
- return this.symbolCache.get(node);
- }
- let symbol = null;
- if (node instanceof BoundAttribute || node instanceof TextAttribute) {
- // TODO(atscott): input and output bindings only return the first directive match but should
- // return a list of bindings for all of them.
- symbol = this.getSymbolOfInputBinding(node);
- }
- else if (node instanceof BoundEvent) {
- symbol = this.getSymbolOfBoundEvent(node);
- }
- else if (node instanceof Element$1) {
- symbol = this.getSymbolOfElement(node);
- }
- else if (node instanceof Template) {
- symbol = this.getSymbolOfAstTemplate(node);
- }
- else if (node instanceof Variable) {
- symbol = this.getSymbolOfVariable(node);
- }
- else if (node instanceof LetDeclaration$1) {
- symbol = this.getSymbolOfLetDeclaration(node);
- }
- else if (node instanceof Reference$1) {
- symbol = this.getSymbolOfReference(node);
- }
- else if (node instanceof BindingPipe) {
- symbol = this.getSymbolOfPipe(node);
- }
- else if (node instanceof AST) {
- symbol = this.getSymbolOfTemplateExpression(node);
- }
- else ;
- this.symbolCache.set(node, symbol);
- return symbol;
- }
- getSymbolOfAstTemplate(template) {
- const directives = this.getDirectivesOfNode(template);
- return { kind: exports.SymbolKind.Template, directives, templateNode: template };
- }
- getSymbolOfElement(element) {
- const elementSourceSpan = element.startSourceSpan ?? element.sourceSpan;
- const node = findFirstMatchingNode(this.typeCheckBlock, {
- withSpan: elementSourceSpan,
- filter: ts.isVariableDeclaration,
- });
- if (node === null) {
- return null;
- }
- const symbolFromDeclaration = this.getSymbolOfTsNode(node);
- if (symbolFromDeclaration === null || symbolFromDeclaration.tsSymbol === null) {
- return null;
- }
- const directives = this.getDirectivesOfNode(element);
- // All statements in the TCB are `Expression`s that optionally include more information.
- // An `ElementSymbol` uses the information returned for the variable declaration expression,
- // adds the directives for the element, and updates the `kind` to be `SymbolKind.Element`.
- return {
- ...symbolFromDeclaration,
- kind: exports.SymbolKind.Element,
- directives,
- templateNode: element,
- };
- }
- getDirectivesOfNode(element) {
- const elementSourceSpan = element.startSourceSpan ?? element.sourceSpan;
- const tcbSourceFile = this.typeCheckBlock.getSourceFile();
- // directives could be either:
- // - var _t1: TestDir /*T:D*/ = null! as TestDir;
- // - var _t1 /*T:D*/ = _ctor1({});
- const isDirectiveDeclaration = (node) => (ts.isTypeNode(node) || ts.isIdentifier(node)) &&
- ts.isVariableDeclaration(node.parent) &&
- hasExpressionIdentifier(tcbSourceFile, node, ExpressionIdentifier.DIRECTIVE);
- const nodes = findAllMatchingNodes(this.typeCheckBlock, {
- withSpan: elementSourceSpan,
- filter: isDirectiveDeclaration,
- });
- const symbols = [];
- for (const node of nodes) {
- const symbol = this.getSymbolOfTsNode(node.parent);
- if (symbol === null ||
- !isSymbolWithValueDeclaration(symbol.tsSymbol) ||
- !ts.isClassDeclaration(symbol.tsSymbol.valueDeclaration)) {
- continue;
- }
- const meta = this.getDirectiveMeta(element, symbol.tsSymbol.valueDeclaration);
- if (meta !== null && meta.selector !== null) {
- const ref = new Reference(symbol.tsSymbol.valueDeclaration);
- if (meta.hostDirectives !== null) {
- this.addHostDirectiveSymbols(element, meta.hostDirectives, symbols);
- }
- const directiveSymbol = {
- ...symbol,
- ref,
- tsSymbol: symbol.tsSymbol,
- selector: meta.selector,
- isComponent: meta.isComponent,
- ngModule: this.getDirectiveModule(symbol.tsSymbol.valueDeclaration),
- kind: exports.SymbolKind.Directive,
- isStructural: meta.isStructural,
- isInScope: true,
- isHostDirective: false,
- };
- symbols.push(directiveSymbol);
- }
- }
- return symbols;
- }
- addHostDirectiveSymbols(host, hostDirectives, symbols) {
- for (const current of hostDirectives) {
- if (!isHostDirectiveMetaForGlobalMode(current)) {
- throw new Error('Impossible state: typecheck code path in local compilation mode.');
- }
- if (!ts.isClassDeclaration(current.directive.node)) {
- continue;
- }
- const symbol = this.getSymbolOfTsNode(current.directive.node);
- const meta = this.getDirectiveMeta(host, current.directive.node);
- if (meta !== null && symbol !== null && isSymbolWithValueDeclaration(symbol.tsSymbol)) {
- if (meta.hostDirectives !== null) {
- this.addHostDirectiveSymbols(host, meta.hostDirectives, symbols);
- }
- const directiveSymbol = {
- ...symbol,
- isHostDirective: true,
- ref: current.directive,
- tsSymbol: symbol.tsSymbol,
- exposedInputs: current.inputs,
- exposedOutputs: current.outputs,
- selector: meta.selector,
- isComponent: meta.isComponent,
- ngModule: this.getDirectiveModule(current.directive.node),
- kind: exports.SymbolKind.Directive,
- isStructural: meta.isStructural,
- isInScope: true,
- };
- symbols.push(directiveSymbol);
- }
- }
- }
- getDirectiveMeta(host, directiveDeclaration) {
- let directives = this.typeCheckData.boundTarget.getDirectivesOfNode(host);
- // `getDirectivesOfNode` will not return the directives intended for an element
- // on a microsyntax template, for example `<div *ngFor="let user of users;" dir>`,
- // the `dir` will be skipped, but it's needed in language service.
- const firstChild = host.children[0];
- if (firstChild instanceof Element$1) {
- const isMicrosyntaxTemplate = host instanceof Template && sourceSpanEqual(firstChild.sourceSpan, host.sourceSpan);
- if (isMicrosyntaxTemplate) {
- const firstChildDirectives = this.typeCheckData.boundTarget.getDirectivesOfNode(firstChild);
- if (firstChildDirectives !== null && directives !== null) {
- directives = directives.concat(firstChildDirectives);
- }
- else {
- directives = directives ?? firstChildDirectives;
- }
- }
- }
- if (directives === null) {
- return null;
- }
- return directives.find((m) => m.ref.node === directiveDeclaration) ?? null;
- }
- getDirectiveModule(declaration) {
- const scope = this.componentScopeReader.getScopeForComponent(declaration);
- if (scope === null || scope.kind !== exports.ComponentScopeKind.NgModule) {
- return null;
- }
- return scope.ngModule;
- }
- getSymbolOfBoundEvent(eventBinding) {
- const consumer = this.typeCheckData.boundTarget.getConsumerOfBinding(eventBinding);
- if (consumer === null) {
- return null;
- }
- // Outputs in the TCB look like one of the two:
- // * _t1["outputField"].subscribe(handler);
- // * _t1.addEventListener(handler);
- // Even with strict null checks disabled, we still produce the access as a separate statement
- // so that it can be found here.
- let expectedAccess;
- if (consumer instanceof Template || consumer instanceof Element$1) {
- expectedAccess = 'addEventListener';
- }
- else {
- const bindingPropertyNames = consumer.outputs.getByBindingPropertyName(eventBinding.name);
- if (bindingPropertyNames === null || bindingPropertyNames.length === 0) {
- return null;
- }
- // Note that we only get the expectedAccess text from a single consumer of the binding. If
- // there are multiple consumers (not supported in the `boundTarget` API) and one of them has
- // an alias, it will not get matched here.
- expectedAccess = bindingPropertyNames[0].classPropertyName;
- }
- function filter(n) {
- if (!isAccessExpression(n)) {
- return false;
- }
- if (ts.isPropertyAccessExpression(n)) {
- return n.name.getText() === expectedAccess;
- }
- else {
- return (ts.isStringLiteral(n.argumentExpression) && n.argumentExpression.text === expectedAccess);
- }
- }
- const outputFieldAccesses = findAllMatchingNodes(this.typeCheckBlock, {
- withSpan: eventBinding.keySpan,
- filter,
- });
- const bindings = [];
- for (const outputFieldAccess of outputFieldAccesses) {
- if (consumer instanceof Template || consumer instanceof Element$1) {
- if (!ts.isPropertyAccessExpression(outputFieldAccess)) {
- continue;
- }
- const addEventListener = outputFieldAccess.name;
- const tsSymbol = this.getTypeChecker().getSymbolAtLocation(addEventListener);
- const tsType = this.getTypeChecker().getTypeAtLocation(addEventListener);
- const positionInFile = this.getTcbPositionForNode(addEventListener);
- const target = this.getSymbol(consumer);
- if (target === null || tsSymbol === undefined) {
- continue;
- }
- bindings.push({
- kind: exports.SymbolKind.Binding,
- tsSymbol,
- tsType,
- target,
- tcbLocation: {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- positionInFile,
- },
- });
- }
- else {
- if (!ts.isElementAccessExpression(outputFieldAccess)) {
- continue;
- }
- const tsSymbol = this.getTypeChecker().getSymbolAtLocation(outputFieldAccess.argumentExpression);
- if (tsSymbol === undefined) {
- continue;
- }
- const target = this.getDirectiveSymbolForAccessExpression(outputFieldAccess, consumer);
- if (target === null) {
- continue;
- }
- const positionInFile = this.getTcbPositionForNode(outputFieldAccess);
- const tsType = this.getTypeChecker().getTypeAtLocation(outputFieldAccess);
- bindings.push({
- kind: exports.SymbolKind.Binding,
- tsSymbol,
- tsType,
- target,
- tcbLocation: {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- positionInFile,
- },
- });
- }
- }
- if (bindings.length === 0) {
- return null;
- }
- return { kind: exports.SymbolKind.Output, bindings };
- }
- getSymbolOfInputBinding(binding) {
- const consumer = this.typeCheckData.boundTarget.getConsumerOfBinding(binding);
- if (consumer === null) {
- return null;
- }
- if (consumer instanceof Element$1 || consumer instanceof Template) {
- const host = this.getSymbol(consumer);
- return host !== null ? { kind: exports.SymbolKind.DomBinding, host } : null;
- }
- const nodes = findAllMatchingNodes(this.typeCheckBlock, {
- withSpan: binding.sourceSpan,
- filter: isAssignment,
- });
- const bindings = [];
- for (const node of nodes) {
- if (!isAccessExpression(node.left)) {
- continue;
- }
- const signalInputAssignment = unwrapSignalInputWriteTAccessor(node.left);
- let fieldAccessExpr;
- let symbolInfo = null;
- // Signal inputs need special treatment because they are generated with an extra keyed
- // access. E.g. `_t1.prop[WriteT_ACCESSOR_SYMBOL]`. Observations:
- // - The keyed access for the write type needs to be resolved for the "input type".
- // - The definition symbol of the input should be the input class member, and not the
- // internal write accessor. Symbol should resolve `_t1.prop`.
- if (signalInputAssignment !== null) {
- // Note: If the field expression for the input binding refers to just an identifier,
- // then we are handling the case of a temporary variable being used for the input field.
- // This is the case with `honorAccessModifiersForInputBindings = false` and in those cases
- // we cannot resolve the owning directive, similar to how we guard above with `isAccessExpression`.
- if (ts.isIdentifier(signalInputAssignment.fieldExpr)) {
- continue;
- }
- const fieldSymbol = this.getSymbolOfTsNode(signalInputAssignment.fieldExpr);
- const typeSymbol = this.getSymbolOfTsNode(signalInputAssignment.typeExpr);
- fieldAccessExpr = signalInputAssignment.fieldExpr;
- symbolInfo =
- fieldSymbol === null || typeSymbol === null
- ? null
- : {
- tcbLocation: fieldSymbol.tcbLocation,
- tsSymbol: fieldSymbol.tsSymbol,
- tsType: typeSymbol.tsType,
- };
- }
- else {
- fieldAccessExpr = node.left;
- symbolInfo = this.getSymbolOfTsNode(node.left);
- }
- if (symbolInfo === null || symbolInfo.tsSymbol === null) {
- continue;
- }
- const target = this.getDirectiveSymbolForAccessExpression(fieldAccessExpr, consumer);
- if (target === null) {
- continue;
- }
- bindings.push({
- ...symbolInfo,
- tsSymbol: symbolInfo.tsSymbol,
- kind: exports.SymbolKind.Binding,
- target,
- });
- }
- if (bindings.length === 0) {
- return null;
- }
- return { kind: exports.SymbolKind.Input, bindings };
- }
- getDirectiveSymbolForAccessExpression(fieldAccessExpr, { isComponent, selector, isStructural }) {
- // In all cases, `_t1["index"]` or `_t1.index`, `node.expression` is _t1.
- const tsSymbol = this.getTypeChecker().getSymbolAtLocation(fieldAccessExpr.expression);
- if (tsSymbol?.declarations === undefined ||
- tsSymbol.declarations.length === 0 ||
- selector === null) {
- return null;
- }
- const [declaration] = tsSymbol.declarations;
- if (!ts.isVariableDeclaration(declaration) ||
- !hasExpressionIdentifier(
- // The expression identifier could be on the type (for regular directives) or the name
- // (for generic directives and the ctor op).
- declaration.getSourceFile(), declaration.type ?? declaration.name, ExpressionIdentifier.DIRECTIVE)) {
- return null;
- }
- const symbol = this.getSymbolOfTsNode(declaration);
- if (symbol === null ||
- !isSymbolWithValueDeclaration(symbol.tsSymbol) ||
- !ts.isClassDeclaration(symbol.tsSymbol.valueDeclaration)) {
- return null;
- }
- const ref = new Reference(symbol.tsSymbol.valueDeclaration);
- const ngModule = this.getDirectiveModule(symbol.tsSymbol.valueDeclaration);
- return {
- ref,
- kind: exports.SymbolKind.Directive,
- tsSymbol: symbol.tsSymbol,
- tsType: symbol.tsType,
- tcbLocation: symbol.tcbLocation,
- isComponent,
- isStructural,
- selector,
- ngModule,
- isHostDirective: false,
- isInScope: true, // TODO: this should always be in scope in this context, right?
- };
- }
- getSymbolOfVariable(variable) {
- const node = findFirstMatchingNode(this.typeCheckBlock, {
- withSpan: variable.sourceSpan,
- filter: ts.isVariableDeclaration,
- });
- if (node === null) {
- return null;
- }
- let nodeValueSymbol = null;
- if (ts.isForOfStatement(node.parent.parent)) {
- nodeValueSymbol = this.getSymbolOfTsNode(node);
- }
- else if (node.initializer !== undefined) {
- nodeValueSymbol = this.getSymbolOfTsNode(node.initializer);
- }
- if (nodeValueSymbol === null) {
- return null;
- }
- return {
- tsType: nodeValueSymbol.tsType,
- tsSymbol: nodeValueSymbol.tsSymbol,
- initializerLocation: nodeValueSymbol.tcbLocation,
- kind: exports.SymbolKind.Variable,
- declaration: variable,
- localVarLocation: {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- positionInFile: this.getTcbPositionForNode(node.name),
- },
- };
- }
- getSymbolOfReference(ref) {
- const target = this.typeCheckData.boundTarget.getReferenceTarget(ref);
- // Find the node for the reference declaration, i.e. `var _t2 = _t1;`
- let node = findFirstMatchingNode(this.typeCheckBlock, {
- withSpan: ref.sourceSpan,
- filter: ts.isVariableDeclaration,
- });
- if (node === null || target === null || node.initializer === undefined) {
- return null;
- }
- // Get the original declaration for the references variable, with the exception of template refs
- // which are of the form var _t3 = (_t2 as any as i2.TemplateRef<any>)
- // TODO(atscott): Consider adding an `ExpressionIdentifier` to tag variable declaration
- // initializers as invalid for symbol retrieval.
- const originalDeclaration = ts.isParenthesizedExpression(node.initializer) &&
- ts.isAsExpression(node.initializer.expression)
- ? this.getTypeChecker().getSymbolAtLocation(node.name)
- : this.getTypeChecker().getSymbolAtLocation(node.initializer);
- if (originalDeclaration === undefined || originalDeclaration.valueDeclaration === undefined) {
- return null;
- }
- const symbol = this.getSymbolOfTsNode(originalDeclaration.valueDeclaration);
- if (symbol === null || symbol.tsSymbol === null) {
- return null;
- }
- const referenceVarTcbLocation = {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- positionInFile: this.getTcbPositionForNode(node),
- };
- if (target instanceof Template || target instanceof Element$1) {
- return {
- kind: exports.SymbolKind.Reference,
- tsSymbol: symbol.tsSymbol,
- tsType: symbol.tsType,
- target,
- declaration: ref,
- targetLocation: symbol.tcbLocation,
- referenceVarLocation: referenceVarTcbLocation,
- };
- }
- else {
- if (!ts.isClassDeclaration(target.directive.ref.node)) {
- return null;
- }
- return {
- kind: exports.SymbolKind.Reference,
- tsSymbol: symbol.tsSymbol,
- tsType: symbol.tsType,
- declaration: ref,
- target: target.directive.ref.node,
- targetLocation: symbol.tcbLocation,
- referenceVarLocation: referenceVarTcbLocation,
- };
- }
- }
- getSymbolOfLetDeclaration(decl) {
- const node = findFirstMatchingNode(this.typeCheckBlock, {
- withSpan: decl.sourceSpan,
- filter: ts.isVariableDeclaration,
- });
- if (node === null) {
- return null;
- }
- const nodeValueSymbol = this.getSymbolOfTsNode(node.initializer);
- if (nodeValueSymbol === null) {
- return null;
- }
- return {
- tsType: nodeValueSymbol.tsType,
- tsSymbol: nodeValueSymbol.tsSymbol,
- initializerLocation: nodeValueSymbol.tcbLocation,
- kind: exports.SymbolKind.LetDeclaration,
- declaration: decl,
- localVarLocation: {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- positionInFile: this.getTcbPositionForNode(node.name),
- },
- };
- }
- getSymbolOfPipe(expression) {
- const methodAccess = findFirstMatchingNode(this.typeCheckBlock, {
- withSpan: expression.nameSpan,
- filter: ts.isPropertyAccessExpression,
- });
- if (methodAccess === null) {
- return null;
- }
- const pipeVariableNode = methodAccess.expression;
- const pipeDeclaration = this.getTypeChecker().getSymbolAtLocation(pipeVariableNode);
- if (pipeDeclaration === undefined || pipeDeclaration.valueDeclaration === undefined) {
- return null;
- }
- const pipeInstance = this.getSymbolOfTsNode(pipeDeclaration.valueDeclaration);
- // The instance should never be null, nor should the symbol lack a value declaration. This
- // is because the node used to look for the `pipeInstance` symbol info is a value
- // declaration of another symbol (i.e. the `pipeDeclaration` symbol).
- if (pipeInstance === null || !isSymbolWithValueDeclaration(pipeInstance.tsSymbol)) {
- return null;
- }
- const symbolInfo = this.getSymbolOfTsNode(methodAccess);
- if (symbolInfo === null) {
- return null;
- }
- return {
- kind: exports.SymbolKind.Pipe,
- ...symbolInfo,
- classSymbol: {
- ...pipeInstance,
- tsSymbol: pipeInstance.tsSymbol,
- },
- };
- }
- getSymbolOfTemplateExpression(expression) {
- if (expression instanceof ASTWithSource) {
- expression = expression.ast;
- }
- const expressionTarget = this.typeCheckData.boundTarget.getExpressionTarget(expression);
- if (expressionTarget !== null) {
- return this.getSymbol(expressionTarget);
- }
- let withSpan = expression.sourceSpan;
- // The `name` part of a `PropertyWrite` and `ASTWithName` do not have their own
- // AST so there is no way to retrieve a `Symbol` for just the `name` via a specific node.
- // Also skipping SafePropertyReads as it breaks nullish coalescing not nullable extended diagnostic
- if (expression instanceof PropertyWrite ||
- (expression instanceof ASTWithName && !(expression instanceof SafePropertyRead))) {
- withSpan = expression.nameSpan;
- }
- let node = null;
- // Property reads in templates usually map to a `PropertyAccessExpression`
- // (e.g. `ctx.foo`) so try looking for one first.
- if (expression instanceof PropertyRead) {
- node = findFirstMatchingNode(this.typeCheckBlock, {
- withSpan,
- filter: ts.isPropertyAccessExpression,
- });
- }
- // Otherwise fall back to searching for any AST node.
- if (node === null) {
- node = findFirstMatchingNode(this.typeCheckBlock, { withSpan, filter: anyNodeFilter });
- }
- if (node === null) {
- return null;
- }
- while (ts.isParenthesizedExpression(node)) {
- node = node.expression;
- }
- // - If we have safe property read ("a?.b") we want to get the Symbol for b, the `whenTrue`
- // expression.
- // - If our expression is a pipe binding ("a | test:b:c"), we want the Symbol for the
- // `transform` on the pipe.
- // - Otherwise, we retrieve the symbol for the node itself with no special considerations
- if (expression instanceof SafePropertyRead && ts.isConditionalExpression(node)) {
- const whenTrueSymbol = this.getSymbolOfTsNode(node.whenTrue);
- if (whenTrueSymbol === null) {
- return null;
- }
- return {
- ...whenTrueSymbol,
- kind: exports.SymbolKind.Expression,
- // Rather than using the type of only the `whenTrue` part of the expression, we should
- // still get the type of the whole conditional expression to include `|undefined`.
- tsType: this.getTypeChecker().getTypeAtLocation(node),
- };
- }
- else {
- const symbolInfo = this.getSymbolOfTsNode(node);
- return symbolInfo === null ? null : { ...symbolInfo, kind: exports.SymbolKind.Expression };
- }
- }
- getSymbolOfTsNode(node) {
- while (ts.isParenthesizedExpression(node)) {
- node = node.expression;
- }
- let tsSymbol;
- if (ts.isPropertyAccessExpression(node)) {
- tsSymbol = this.getTypeChecker().getSymbolAtLocation(node.name);
- }
- else if (ts.isCallExpression(node)) {
- tsSymbol = this.getTypeChecker().getSymbolAtLocation(node.expression);
- }
- else {
- tsSymbol = this.getTypeChecker().getSymbolAtLocation(node);
- }
- const positionInFile = this.getTcbPositionForNode(node);
- const type = this.getTypeChecker().getTypeAtLocation(node);
- return {
- // If we could not find a symbol, fall back to the symbol on the type for the node.
- // Some nodes won't have a "symbol at location" but will have a symbol for the type.
- // Examples of this would be literals and `document.createElement('div')`.
- tsSymbol: tsSymbol ?? type.symbol ?? null,
- tsType: type,
- tcbLocation: {
- tcbPath: this.tcbPath,
- isShimFile: this.tcbIsShim,
- positionInFile,
- },
- };
- }
- getTcbPositionForNode(node) {
- if (ts.isTypeReferenceNode(node)) {
- return this.getTcbPositionForNode(node.typeName);
- }
- else if (ts.isQualifiedName(node)) {
- return node.right.getStart();
- }
- else if (ts.isPropertyAccessExpression(node)) {
- return node.name.getStart();
- }
- else if (ts.isElementAccessExpression(node)) {
- return node.argumentExpression.getStart();
- }
- else {
- return node.getStart();
- }
- }
- }
- /** Filter predicate function that matches any AST node. */
- function anyNodeFilter(n) {
- return true;
- }
- function sourceSpanEqual(a, b) {
- return a.start.offset === b.start.offset && a.end.offset === b.end.offset;
- }
- function unwrapSignalInputWriteTAccessor(expr) {
- // e.g. `_t2.inputA[i2.ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]`
- // 1. Assert that we are dealing with an element access expression.
- // 2. Assert that we are dealing with a signal brand symbol access in the argument expression.
- if (!ts.isElementAccessExpression(expr) ||
- !ts.isPropertyAccessExpression(expr.argumentExpression)) {
- return null;
- }
- // Assert that the property access in the element access is a simple identifier and
- // refers to `ɵINPUT_SIGNAL_BRAND_WRITE_TYPE`.
- if (!ts.isIdentifier(expr.argumentExpression.name) ||
- expr.argumentExpression.name.text !== Identifiers.InputSignalBrandWriteType.name) {
- return null;
- }
- // Assert that the expression is either:
- // - `_t2.inputA[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]` or (common case)
- // - or `_t2['input-A'][ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]` (non-identifier input field names)
- // - or `_dirInput[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE` (honorAccessModifiersForInputBindings=false)
- // This is checked for type safety and to catch unexpected cases.
- if (!ts.isPropertyAccessExpression(expr.expression) &&
- !ts.isElementAccessExpression(expr.expression) &&
- !ts.isIdentifier(expr.expression)) {
- throw new Error('Unexpected expression for signal input write type.');
- }
- return {
- fieldExpr: expr.expression,
- typeExpr: expr,
- };
- }
- const REGISTRY = new DomElementSchemaRegistry();
- /**
- * Primary template type-checking engine, which performs type-checking using a
- * `TypeCheckingProgramStrategy` for type-checking program maintenance, and the
- * `ProgramTypeCheckAdapter` for generation of template type-checking code.
- */
- class TemplateTypeCheckerImpl {
- originalProgram;
- programDriver;
- typeCheckAdapter;
- config;
- refEmitter;
- reflector;
- compilerHost;
- priorBuild;
- metaReader;
- localMetaReader;
- ngModuleIndex;
- componentScopeReader;
- typeCheckScopeRegistry;
- perf;
- state = new Map();
- /**
- * Stores the `CompletionEngine` which powers autocompletion for each component class.
- *
- * Must be invalidated whenever the component's template or the `ts.Program` changes. Invalidation
- * on template changes is performed within this `TemplateTypeCheckerImpl` instance. When the
- * `ts.Program` changes, the `TemplateTypeCheckerImpl` as a whole is destroyed and replaced.
- */
- completionCache = new Map();
- /**
- * Stores the `SymbolBuilder` which creates symbols for each component class.
- *
- * Must be invalidated whenever the component's template or the `ts.Program` changes. Invalidation
- * on template changes is performed within this `TemplateTypeCheckerImpl` instance. When the
- * `ts.Program` changes, the `TemplateTypeCheckerImpl` as a whole is destroyed and replaced.
- */
- symbolBuilderCache = new Map();
- /**
- * Stores directives and pipes that are in scope for each component.
- *
- * Unlike other caches, the scope of a component is not affected by its template. It will be
- * destroyed when the `ts.Program` changes and the `TemplateTypeCheckerImpl` as a whole is
- * destroyed and replaced.
- */
- scopeCache = new Map();
- /**
- * Stores potential element tags for each component (a union of DOM tags as well as directive
- * tags).
- *
- * Unlike other caches, the scope of a component is not affected by its template. It will be
- * destroyed when the `ts.Program` changes and the `TemplateTypeCheckerImpl` as a whole is
- * destroyed and replaced.
- */
- elementTagCache = new Map();
- isComplete = false;
- priorResultsAdopted = false;
- constructor(originalProgram, programDriver, typeCheckAdapter, config, refEmitter, reflector, compilerHost, priorBuild, metaReader, localMetaReader, ngModuleIndex, componentScopeReader, typeCheckScopeRegistry, perf) {
- this.originalProgram = originalProgram;
- this.programDriver = programDriver;
- this.typeCheckAdapter = typeCheckAdapter;
- this.config = config;
- this.refEmitter = refEmitter;
- this.reflector = reflector;
- this.compilerHost = compilerHost;
- this.priorBuild = priorBuild;
- this.metaReader = metaReader;
- this.localMetaReader = localMetaReader;
- this.ngModuleIndex = ngModuleIndex;
- this.componentScopeReader = componentScopeReader;
- this.typeCheckScopeRegistry = typeCheckScopeRegistry;
- this.perf = perf;
- }
- getTemplate(component, optimizeFor) {
- const { data } = this.getLatestComponentState(component, optimizeFor);
- if (data === null) {
- return null;
- }
- return data.template;
- }
- getUsedDirectives(component) {
- return this.getLatestComponentState(component).data?.boundTarget.getUsedDirectives() || null;
- }
- getUsedPipes(component) {
- return this.getLatestComponentState(component).data?.boundTarget.getUsedPipes() || null;
- }
- getLatestComponentState(component, optimizeFor = exports.OptimizeFor.SingleFile) {
- switch (optimizeFor) {
- case exports.OptimizeFor.WholeProgram:
- this.ensureAllShimsForAllFiles();
- break;
- case exports.OptimizeFor.SingleFile:
- this.ensureShimForComponent(component);
- break;
- }
- const sf = component.getSourceFile();
- const sfPath = absoluteFromSourceFile(sf);
- const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
- const fileRecord = this.getFileData(sfPath);
- if (!fileRecord.shimData.has(shimPath)) {
- return { data: null, tcb: null, tcbPath: shimPath, tcbIsShim: true };
- }
- const id = fileRecord.sourceManager.getTypeCheckId(component);
- const shimRecord = fileRecord.shimData.get(shimPath);
- const program = this.programDriver.getProgram();
- const shimSf = getSourceFileOrNull(program, shimPath);
- if (shimSf === null || !fileRecord.shimData.has(shimPath)) {
- throw new Error(`Error: no shim file in program: ${shimPath}`);
- }
- let tcb = findTypeCheckBlock(shimSf, id, /*isDiagnosticsRequest*/ false);
- let tcbPath = shimPath;
- if (tcb === null) {
- // Try for an inline block.
- const inlineSf = getSourceFileOrError(program, sfPath);
- tcb = findTypeCheckBlock(inlineSf, id, /*isDiagnosticsRequest*/ false);
- if (tcb !== null) {
- tcbPath = sfPath;
- }
- }
- let data = null;
- if (shimRecord.data.has(id)) {
- data = shimRecord.data.get(id);
- }
- return { data, tcb, tcbPath, tcbIsShim: tcbPath === shimPath };
- }
- isTrackedTypeCheckFile(filePath) {
- return this.getFileAndShimRecordsForPath(filePath) !== null;
- }
- getFileRecordForTcbLocation({ tcbPath, isShimFile, }) {
- if (!isShimFile) {
- // The location is not within a shim file but corresponds with an inline TCB in an original
- // source file; we can obtain the record directly by its path.
- if (this.state.has(tcbPath)) {
- return this.state.get(tcbPath);
- }
- else {
- return null;
- }
- }
- // The location is within a type-checking shim file; find the type-checking data that owns this
- // shim path.
- const records = this.getFileAndShimRecordsForPath(tcbPath);
- if (records !== null) {
- return records.fileRecord;
- }
- else {
- return null;
- }
- }
- getFileAndShimRecordsForPath(shimPath) {
- for (const fileRecord of this.state.values()) {
- if (fileRecord.shimData.has(shimPath)) {
- return { fileRecord, shimRecord: fileRecord.shimData.get(shimPath) };
- }
- }
- return null;
- }
- getSourceMappingAtTcbLocation(tcbLocation) {
- const fileRecord = this.getFileRecordForTcbLocation(tcbLocation);
- if (fileRecord === null) {
- return null;
- }
- const shimSf = this.programDriver.getProgram().getSourceFile(tcbLocation.tcbPath);
- if (shimSf === undefined) {
- return null;
- }
- return getSourceMapping(shimSf, tcbLocation.positionInFile, fileRecord.sourceManager,
- /*isDiagnosticsRequest*/ false);
- }
- generateAllTypeCheckBlocks() {
- this.ensureAllShimsForAllFiles();
- }
- /**
- * Retrieve type-checking and template parse diagnostics from the given `ts.SourceFile` using the
- * most recent type-checking program.
- */
- getDiagnosticsForFile(sf, optimizeFor) {
- switch (optimizeFor) {
- case exports.OptimizeFor.WholeProgram:
- this.ensureAllShimsForAllFiles();
- break;
- case exports.OptimizeFor.SingleFile:
- this.ensureAllShimsForOneFile(sf);
- break;
- }
- return this.perf.inPhase(exports.PerfPhase.TtcDiagnostics, () => {
- const sfPath = absoluteFromSourceFile(sf);
- const fileRecord = this.state.get(sfPath);
- const typeCheckProgram = this.programDriver.getProgram();
- const diagnostics = [];
- if (fileRecord.hasInlines) {
- const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
- diagnostics.push(...typeCheckProgram
- .getSemanticDiagnostics(inlineSf)
- .map((diag) => convertDiagnostic(diag, fileRecord.sourceManager)));
- }
- for (const [shimPath, shimRecord] of fileRecord.shimData) {
- const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
- diagnostics.push(...typeCheckProgram
- .getSemanticDiagnostics(shimSf)
- .map((diag) => convertDiagnostic(diag, fileRecord.sourceManager)));
- diagnostics.push(...shimRecord.genesisDiagnostics);
- for (const templateData of shimRecord.data.values()) {
- diagnostics.push(...templateData.templateParsingDiagnostics);
- }
- }
- return diagnostics.filter((diag) => diag !== null);
- });
- }
- getDiagnosticsForComponent(component) {
- this.ensureShimForComponent(component);
- return this.perf.inPhase(exports.PerfPhase.TtcDiagnostics, () => {
- const sf = component.getSourceFile();
- const sfPath = absoluteFromSourceFile(sf);
- const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
- const fileRecord = this.getFileData(sfPath);
- if (!fileRecord.shimData.has(shimPath)) {
- return [];
- }
- const id = fileRecord.sourceManager.getTypeCheckId(component);
- const shimRecord = fileRecord.shimData.get(shimPath);
- const typeCheckProgram = this.programDriver.getProgram();
- const diagnostics = [];
- if (shimRecord.hasInlines) {
- const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
- diagnostics.push(...typeCheckProgram
- .getSemanticDiagnostics(inlineSf)
- .map((diag) => convertDiagnostic(diag, fileRecord.sourceManager)));
- }
- const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
- diagnostics.push(...typeCheckProgram
- .getSemanticDiagnostics(shimSf)
- .map((diag) => convertDiagnostic(diag, fileRecord.sourceManager)));
- diagnostics.push(...shimRecord.genesisDiagnostics);
- for (const templateData of shimRecord.data.values()) {
- diagnostics.push(...templateData.templateParsingDiagnostics);
- }
- return diagnostics.filter((diag) => diag !== null && diag.typeCheckId === id);
- });
- }
- getTypeCheckBlock(component) {
- return this.getLatestComponentState(component).tcb;
- }
- getGlobalCompletions(context, component, node) {
- const engine = this.getOrCreateCompletionEngine(component);
- if (engine === null) {
- return null;
- }
- return this.perf.inPhase(exports.PerfPhase.TtcAutocompletion, () => engine.getGlobalCompletions(context, node));
- }
- getExpressionCompletionLocation(ast, component) {
- const engine = this.getOrCreateCompletionEngine(component);
- if (engine === null) {
- return null;
- }
- return this.perf.inPhase(exports.PerfPhase.TtcAutocompletion, () => engine.getExpressionCompletionLocation(ast));
- }
- getLiteralCompletionLocation(node, component) {
- const engine = this.getOrCreateCompletionEngine(component);
- if (engine === null) {
- return null;
- }
- return this.perf.inPhase(exports.PerfPhase.TtcAutocompletion, () => engine.getLiteralCompletionLocation(node));
- }
- invalidateClass(clazz) {
- this.completionCache.delete(clazz);
- this.symbolBuilderCache.delete(clazz);
- this.scopeCache.delete(clazz);
- this.elementTagCache.delete(clazz);
- const sf = clazz.getSourceFile();
- const sfPath = absoluteFromSourceFile(sf);
- const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
- const fileData = this.getFileData(sfPath);
- fileData.sourceManager.getTypeCheckId(clazz);
- fileData.shimData.delete(shimPath);
- fileData.isComplete = false;
- this.isComplete = false;
- }
- getExpressionTarget(expression, clazz) {
- return (this.getLatestComponentState(clazz).data?.boundTarget.getExpressionTarget(expression) || null);
- }
- makeTemplateDiagnostic(clazz, sourceSpan, category, errorCode, message, relatedInformation) {
- const sfPath = absoluteFromSourceFile(clazz.getSourceFile());
- const fileRecord = this.state.get(sfPath);
- const id = fileRecord.sourceManager.getTypeCheckId(clazz);
- const mapping = fileRecord.sourceManager.getTemplateSourceMapping(id);
- return {
- ...makeTemplateDiagnostic(id, mapping, sourceSpan, category, ngErrorCode(errorCode), message, relatedInformation),
- __ngCode: errorCode,
- };
- }
- getOrCreateCompletionEngine(component) {
- if (this.completionCache.has(component)) {
- return this.completionCache.get(component);
- }
- const { tcb, data, tcbPath, tcbIsShim } = this.getLatestComponentState(component);
- if (tcb === null || data === null) {
- return null;
- }
- const engine = new CompletionEngine(tcb, data, tcbPath, tcbIsShim);
- this.completionCache.set(component, engine);
- return engine;
- }
- maybeAdoptPriorResults() {
- if (this.priorResultsAdopted) {
- return;
- }
- for (const sf of this.originalProgram.getSourceFiles()) {
- if (sf.isDeclarationFile || isShim(sf)) {
- continue;
- }
- const sfPath = absoluteFromSourceFile(sf);
- if (this.state.has(sfPath)) {
- const existingResults = this.state.get(sfPath);
- if (existingResults.isComplete) {
- // All data for this file has already been generated, so no need to adopt anything.
- continue;
- }
- }
- const previousResults = this.priorBuild.priorTypeCheckingResultsFor(sf);
- if (previousResults === null || !previousResults.isComplete) {
- continue;
- }
- this.perf.eventCount(exports.PerfEvent.ReuseTypeCheckFile);
- this.state.set(sfPath, previousResults);
- }
- this.priorResultsAdopted = true;
- }
- ensureAllShimsForAllFiles() {
- if (this.isComplete) {
- return;
- }
- this.maybeAdoptPriorResults();
- this.perf.inPhase(exports.PerfPhase.TcbGeneration, () => {
- const host = new WholeProgramTypeCheckingHost(this);
- const ctx = this.newContext(host);
- for (const sf of this.originalProgram.getSourceFiles()) {
- if (sf.isDeclarationFile || isShim(sf)) {
- continue;
- }
- const sfPath = absoluteFromSourceFile(sf);
- const fileData = this.getFileData(sfPath);
- if (fileData.isComplete) {
- continue;
- }
- this.typeCheckAdapter.typeCheck(sf, ctx);
- fileData.isComplete = true;
- }
- this.updateFromContext(ctx);
- this.isComplete = true;
- });
- }
- ensureAllShimsForOneFile(sf) {
- this.maybeAdoptPriorResults();
- this.perf.inPhase(exports.PerfPhase.TcbGeneration, () => {
- const sfPath = absoluteFromSourceFile(sf);
- const fileData = this.getFileData(sfPath);
- if (fileData.isComplete) {
- // All data for this file is present and accounted for already.
- return;
- }
- const host = new SingleFileTypeCheckingHost(sfPath, fileData, this);
- const ctx = this.newContext(host);
- this.typeCheckAdapter.typeCheck(sf, ctx);
- fileData.isComplete = true;
- this.updateFromContext(ctx);
- });
- }
- ensureShimForComponent(component) {
- this.maybeAdoptPriorResults();
- const sf = component.getSourceFile();
- const sfPath = absoluteFromSourceFile(sf);
- const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
- const fileData = this.getFileData(sfPath);
- if (fileData.shimData.has(shimPath)) {
- // All data for this component is available.
- return;
- }
- const host = new SingleShimTypeCheckingHost(sfPath, fileData, this, shimPath);
- const ctx = this.newContext(host);
- this.typeCheckAdapter.typeCheck(sf, ctx);
- this.updateFromContext(ctx);
- }
- newContext(host) {
- const inlining = this.programDriver.supportsInlineOperations
- ? InliningMode.InlineOps
- : InliningMode.Error;
- return new TypeCheckContextImpl(this.config, this.compilerHost, this.refEmitter, this.reflector, host, inlining, this.perf);
- }
- /**
- * Remove any shim data that depends on inline operations applied to the type-checking program.
- *
- * This can be useful if new inlines need to be applied, and it's not possible to guarantee that
- * they won't overwrite or corrupt existing inlines that are used by such shims.
- */
- clearAllShimDataUsingInlines() {
- for (const fileData of this.state.values()) {
- if (!fileData.hasInlines) {
- continue;
- }
- for (const [shimFile, shimData] of fileData.shimData.entries()) {
- if (shimData.hasInlines) {
- fileData.shimData.delete(shimFile);
- }
- }
- fileData.hasInlines = false;
- fileData.isComplete = false;
- this.isComplete = false;
- }
- }
- updateFromContext(ctx) {
- const updates = ctx.finalize();
- return this.perf.inPhase(exports.PerfPhase.TcbUpdateProgram, () => {
- if (updates.size > 0) {
- this.perf.eventCount(exports.PerfEvent.UpdateTypeCheckProgram);
- }
- this.programDriver.updateFiles(updates, exports.UpdateMode.Incremental);
- this.priorBuild.recordSuccessfulTypeCheck(this.state);
- this.perf.memory(exports.PerfCheckpoint.TtcUpdateProgram);
- });
- }
- getFileData(path) {
- if (!this.state.has(path)) {
- this.state.set(path, {
- hasInlines: false,
- sourceManager: new DirectiveSourceManager(),
- isComplete: false,
- shimData: new Map(),
- });
- }
- return this.state.get(path);
- }
- getSymbolOfNode(node, component) {
- const builder = this.getOrCreateSymbolBuilder(component);
- if (builder === null) {
- return null;
- }
- return this.perf.inPhase(exports.PerfPhase.TtcSymbol, () => builder.getSymbol(node));
- }
- getOrCreateSymbolBuilder(component) {
- if (this.symbolBuilderCache.has(component)) {
- return this.symbolBuilderCache.get(component);
- }
- const { tcb, data, tcbPath, tcbIsShim } = this.getLatestComponentState(component);
- if (tcb === null || data === null) {
- return null;
- }
- const builder = new SymbolBuilder(tcbPath, tcbIsShim, tcb, data, this.componentScopeReader, () => this.programDriver.getProgram().getTypeChecker());
- this.symbolBuilderCache.set(component, builder);
- return builder;
- }
- getPotentialTemplateDirectives(component) {
- const typeChecker = this.programDriver.getProgram().getTypeChecker();
- const inScopeDirectives = this.getScopeData(component)?.directives ?? [];
- const resultingDirectives = new Map();
- // First, all in scope directives can be used.
- for (const d of inScopeDirectives) {
- resultingDirectives.set(d.ref.node, d);
- }
- // Any additional directives found from the global registry can be used, but are not in scope.
- // In the future, we can also walk other registries for .d.ts files, or traverse the
- // import/export graph.
- for (const directiveClass of this.localMetaReader.getKnown(exports.MetaKind.Directive)) {
- const directiveMeta = this.metaReader.getDirectiveMetadata(new Reference(directiveClass));
- if (directiveMeta === null)
- continue;
- if (resultingDirectives.has(directiveClass))
- continue;
- const withScope = this.scopeDataOfDirectiveMeta(typeChecker, directiveMeta);
- if (withScope === null)
- continue;
- resultingDirectives.set(directiveClass, { ...withScope, isInScope: false });
- }
- return Array.from(resultingDirectives.values());
- }
- getPotentialPipes(component) {
- // Very similar to the above `getPotentialTemplateDirectives`, but on pipes.
- const typeChecker = this.programDriver.getProgram().getTypeChecker();
- const inScopePipes = this.getScopeData(component)?.pipes ?? [];
- const resultingPipes = new Map();
- for (const p of inScopePipes) {
- resultingPipes.set(p.ref.node, p);
- }
- for (const pipeClass of this.localMetaReader.getKnown(exports.MetaKind.Pipe)) {
- const pipeMeta = this.metaReader.getPipeMetadata(new Reference(pipeClass));
- if (pipeMeta === null)
- continue;
- if (resultingPipes.has(pipeClass))
- continue;
- const withScope = this.scopeDataOfPipeMeta(typeChecker, pipeMeta);
- if (withScope === null)
- continue;
- resultingPipes.set(pipeClass, { ...withScope, isInScope: false });
- }
- return Array.from(resultingPipes.values());
- }
- getDirectiveMetadata(dir) {
- if (!isNamedClassDeclaration(dir)) {
- return null;
- }
- return this.typeCheckScopeRegistry.getTypeCheckDirectiveMetadata(new Reference(dir));
- }
- getNgModuleMetadata(module) {
- if (!isNamedClassDeclaration(module)) {
- return null;
- }
- return this.metaReader.getNgModuleMetadata(new Reference(module));
- }
- getPipeMetadata(pipe) {
- if (!isNamedClassDeclaration(pipe)) {
- return null;
- }
- return this.metaReader.getPipeMetadata(new Reference(pipe));
- }
- getPotentialElementTags(component) {
- if (this.elementTagCache.has(component)) {
- return this.elementTagCache.get(component);
- }
- const tagMap = new Map();
- for (const tag of REGISTRY.allKnownElementNames()) {
- tagMap.set(tag, null);
- }
- const potentialDirectives = this.getPotentialTemplateDirectives(component);
- for (const directive of potentialDirectives) {
- if (directive.selector === null) {
- continue;
- }
- for (const selector of CssSelector.parse(directive.selector)) {
- if (selector.element === null || tagMap.has(selector.element)) {
- // Skip this directive if it doesn't match an element tag, or if another directive has
- // already been included with the same element name.
- continue;
- }
- tagMap.set(selector.element, directive);
- }
- }
- this.elementTagCache.set(component, tagMap);
- return tagMap;
- }
- getPotentialDomBindings(tagName) {
- const attributes = REGISTRY.allKnownAttributesOfElement(tagName);
- return attributes.map((attribute) => ({
- attribute,
- property: REGISTRY.getMappedPropName(attribute),
- }));
- }
- getPotentialDomEvents(tagName) {
- return REGISTRY.allKnownEventsOfElement(tagName);
- }
- getPrimaryAngularDecorator(target) {
- this.ensureAllShimsForOneFile(target.getSourceFile());
- if (!isNamedClassDeclaration(target)) {
- return null;
- }
- const ref = new Reference(target);
- const dirMeta = this.metaReader.getDirectiveMetadata(ref);
- if (dirMeta !== null) {
- return dirMeta.decorator;
- }
- const pipeMeta = this.metaReader.getPipeMetadata(ref);
- if (pipeMeta !== null) {
- return pipeMeta.decorator;
- }
- const ngModuleMeta = this.metaReader.getNgModuleMetadata(ref);
- if (ngModuleMeta !== null) {
- return ngModuleMeta.decorator;
- }
- return null;
- }
- getOwningNgModule(component) {
- if (!isNamedClassDeclaration(component)) {
- return null;
- }
- const dirMeta = this.metaReader.getDirectiveMetadata(new Reference(component));
- if (dirMeta !== null && dirMeta.isStandalone) {
- return null;
- }
- const scope = this.componentScopeReader.getScopeForComponent(component);
- if (scope === null ||
- scope.kind !== exports.ComponentScopeKind.NgModule ||
- !isNamedClassDeclaration(scope.ngModule)) {
- return null;
- }
- return scope.ngModule;
- }
- emit(kind, refTo, inContext) {
- const emittedRef = this.refEmitter.emit(refTo, inContext.getSourceFile());
- if (emittedRef.kind === exports.ReferenceEmitKind.Failed) {
- return null;
- }
- const emitted = emittedRef.expression;
- if (emitted instanceof WrappedNodeExpr) {
- if (refTo.node === inContext) {
- // Suppress self-imports since components do not have to import themselves.
- return null;
- }
- let isForwardReference = false;
- if (emitted.node.getStart() > inContext.getStart()) {
- const declaration = this.programDriver
- .getProgram()
- .getTypeChecker()
- .getTypeAtLocation(emitted.node)
- .getSymbol()?.declarations?.[0];
- if (declaration && declaration.getSourceFile() === inContext.getSourceFile()) {
- isForwardReference = true;
- }
- }
- // An appropriate identifier is already in scope.
- return { kind, symbolName: emitted.node.text, isForwardReference };
- }
- else if (emitted instanceof ExternalExpr &&
- emitted.value.moduleName !== null &&
- emitted.value.name !== null) {
- return {
- kind,
- moduleSpecifier: emitted.value.moduleName,
- symbolName: emitted.value.name,
- isForwardReference: false,
- };
- }
- return null;
- }
- getPotentialImportsFor(toImport, inContext, importMode) {
- const imports = [];
- const meta = this.metaReader.getDirectiveMetadata(toImport) ?? this.metaReader.getPipeMetadata(toImport);
- if (meta === null) {
- return imports;
- }
- if (meta.isStandalone || importMode === exports.PotentialImportMode.ForceDirect) {
- const emitted = this.emit(exports.PotentialImportKind.Standalone, toImport, inContext);
- if (emitted !== null) {
- imports.push(emitted);
- }
- }
- const exportingNgModules = this.ngModuleIndex.getNgModulesExporting(meta.ref.node);
- if (exportingNgModules !== null) {
- for (const exporter of exportingNgModules) {
- const emittedRef = this.emit(exports.PotentialImportKind.NgModule, exporter, inContext);
- if (emittedRef !== null) {
- imports.push(emittedRef);
- }
- }
- }
- return imports;
- }
- getScopeData(component) {
- if (this.scopeCache.has(component)) {
- return this.scopeCache.get(component);
- }
- if (!isNamedClassDeclaration(component)) {
- throw new Error(`AssertionError: components must have names`);
- }
- const scope = this.componentScopeReader.getScopeForComponent(component);
- if (scope === null) {
- return null;
- }
- const dependencies = scope.kind === exports.ComponentScopeKind.NgModule
- ? scope.compilation.dependencies
- : scope.dependencies;
- const data = {
- directives: [],
- pipes: [],
- isPoisoned: scope.kind === exports.ComponentScopeKind.NgModule
- ? scope.compilation.isPoisoned
- : scope.isPoisoned,
- };
- const typeChecker = this.programDriver.getProgram().getTypeChecker();
- for (const dep of dependencies) {
- if (dep.kind === exports.MetaKind.Directive) {
- const dirScope = this.scopeDataOfDirectiveMeta(typeChecker, dep);
- if (dirScope === null)
- continue;
- data.directives.push({ ...dirScope, isInScope: true });
- }
- else if (dep.kind === exports.MetaKind.Pipe) {
- const pipeScope = this.scopeDataOfPipeMeta(typeChecker, dep);
- if (pipeScope === null)
- continue;
- data.pipes.push({ ...pipeScope, isInScope: true });
- }
- }
- this.scopeCache.set(component, data);
- return data;
- }
- scopeDataOfDirectiveMeta(typeChecker, dep) {
- if (dep.selector === null) {
- // Skip this directive, it can't be added to a template anyway.
- return null;
- }
- const tsSymbol = typeChecker.getSymbolAtLocation(dep.ref.node.name);
- if (!isSymbolWithValueDeclaration(tsSymbol)) {
- return null;
- }
- let ngModule = null;
- const moduleScopeOfDir = this.componentScopeReader.getScopeForComponent(dep.ref.node);
- if (moduleScopeOfDir !== null && moduleScopeOfDir.kind === exports.ComponentScopeKind.NgModule) {
- ngModule = moduleScopeOfDir.ngModule;
- }
- return {
- ref: dep.ref,
- isComponent: dep.isComponent,
- isStructural: dep.isStructural,
- selector: dep.selector,
- tsSymbol,
- ngModule,
- };
- }
- scopeDataOfPipeMeta(typeChecker, dep) {
- const tsSymbol = typeChecker.getSymbolAtLocation(dep.ref.node.name);
- if (tsSymbol === undefined) {
- return null;
- }
- return {
- ref: dep.ref,
- name: dep.name,
- tsSymbol,
- };
- }
- }
- function convertDiagnostic(diag, sourceResolver) {
- if (!shouldReportDiagnostic(diag)) {
- return null;
- }
- return translateDiagnostic(diag, sourceResolver);
- }
- /**
- * Drives a `TypeCheckContext` to generate type-checking code for every component in the program.
- */
- class WholeProgramTypeCheckingHost {
- impl;
- constructor(impl) {
- this.impl = impl;
- }
- getSourceManager(sfPath) {
- return this.impl.getFileData(sfPath).sourceManager;
- }
- shouldCheckClass(node) {
- const sfPath = absoluteFromSourceFile(node.getSourceFile());
- const shimPath = TypeCheckShimGenerator.shimFor(sfPath);
- const fileData = this.impl.getFileData(sfPath);
- // The component needs to be checked unless the shim which would contain it already exists.
- return !fileData.shimData.has(shimPath);
- }
- recordShimData(sfPath, data) {
- const fileData = this.impl.getFileData(sfPath);
- fileData.shimData.set(data.path, data);
- if (data.hasInlines) {
- fileData.hasInlines = true;
- }
- }
- recordComplete(sfPath) {
- this.impl.getFileData(sfPath).isComplete = true;
- }
- }
- /**
- * Drives a `TypeCheckContext` to generate type-checking code efficiently for a single input file.
- */
- class SingleFileTypeCheckingHost {
- sfPath;
- fileData;
- impl;
- seenInlines = false;
- constructor(sfPath, fileData, impl) {
- this.sfPath = sfPath;
- this.fileData = fileData;
- this.impl = impl;
- }
- assertPath(sfPath) {
- if (this.sfPath !== sfPath) {
- throw new Error(`AssertionError: querying TypeCheckingHost outside of assigned file`);
- }
- }
- getSourceManager(sfPath) {
- this.assertPath(sfPath);
- return this.fileData.sourceManager;
- }
- shouldCheckClass(node) {
- if (this.sfPath !== absoluteFromSourceFile(node.getSourceFile())) {
- return false;
- }
- const shimPath = TypeCheckShimGenerator.shimFor(this.sfPath);
- // Only need to generate a TCB for the class if no shim exists for it currently.
- return !this.fileData.shimData.has(shimPath);
- }
- recordShimData(sfPath, data) {
- this.assertPath(sfPath);
- // Previous type-checking state may have required the use of inlines (assuming they were
- // supported). If the current operation also requires inlines, this presents a problem:
- // generating new inlines may invalidate any old inlines that old state depends on.
- //
- // Rather than resolve this issue by tracking specific dependencies on inlines, if the new state
- // relies on inlines, any old state that relied on them is simply cleared. This happens when the
- // first new state that uses inlines is encountered.
- if (data.hasInlines && !this.seenInlines) {
- this.impl.clearAllShimDataUsingInlines();
- this.seenInlines = true;
- }
- this.fileData.shimData.set(data.path, data);
- if (data.hasInlines) {
- this.fileData.hasInlines = true;
- }
- }
- recordComplete(sfPath) {
- this.assertPath(sfPath);
- this.fileData.isComplete = true;
- }
- }
- /**
- * Drives a `TypeCheckContext` to generate type-checking code efficiently for only those components
- * which map to a single shim of a single input file.
- */
- class SingleShimTypeCheckingHost extends SingleFileTypeCheckingHost {
- shimPath;
- constructor(sfPath, fileData, impl, shimPath) {
- super(sfPath, fileData, impl);
- this.shimPath = shimPath;
- }
- shouldCheckNode(node) {
- if (this.sfPath !== absoluteFromSourceFile(node.getSourceFile())) {
- return false;
- }
- // Only generate a TCB for the component if it maps to the requested shim file.
- const shimPath = TypeCheckShimGenerator.shimFor(this.sfPath);
- if (shimPath !== this.shimPath) {
- return false;
- }
- // Only need to generate a TCB for the class if no shim exists for it currently.
- return !this.fileData.shimData.has(shimPath);
- }
- }
- exports.AST = AST;
- exports.ASTWithSource = ASTWithSource;
- exports.AbsoluteModuleStrategy = AbsoluteModuleStrategy;
- exports.ArrowFunctionExpr = ArrowFunctionExpr;
- exports.Binary = Binary;
- exports.BlockPlaceholder = BlockPlaceholder;
- exports.BoundAttribute = BoundAttribute;
- exports.BoundDeferredTrigger = BoundDeferredTrigger;
- exports.BoundEvent = BoundEvent;
- exports.COMPILER_ERRORS_WITH_GUIDES = COMPILER_ERRORS_WITH_GUIDES;
- exports.CR = CR;
- exports.CUSTOM_ELEMENTS_SCHEMA = CUSTOM_ELEMENTS_SCHEMA;
- exports.Call = Call;
- exports.Chain = Chain;
- exports.ClassPropertyMapping = ClassPropertyMapping;
- exports.CloneVisitor = CloneVisitor;
- exports.CompoundMetadataReader = CompoundMetadataReader;
- exports.Conditional = Conditional;
- exports.ConstantPool = ConstantPool;
- exports.Container = Container;
- exports.CssSelector = CssSelector;
- exports.DEFAULT_INTERPOLATION_CONFIG = DEFAULT_INTERPOLATION_CONFIG;
- exports.DYNAMIC_TYPE = DYNAMIC_TYPE;
- exports.Declaration = Declaration;
- exports.DeclareFunctionStmt = DeclareFunctionStmt;
- exports.DeclareVarStmt = DeclareVarStmt;
- exports.DefaultImportTracker = DefaultImportTracker;
- exports.DefinitionMap = DefinitionMap;
- exports.DomElementSchemaRegistry = DomElementSchemaRegistry;
- exports.DynamicImportExpr = DynamicImportExpr;
- exports.DynamicValue = DynamicValue;
- exports.Element = Element;
- exports.Element$1 = Element$1;
- exports.EnumValue = EnumValue;
- exports.ExternalExpr = ExternalExpr;
- exports.FatalDiagnosticError = FatalDiagnosticError;
- exports.FnParam = FnParam;
- exports.FunctionExpr = FunctionExpr;
- exports.HtmlParser = HtmlParser;
- exports.I18nError = I18nError;
- exports.INPUT_INITIALIZER_FN = INPUT_INITIALIZER_FN;
- exports.Icu = Icu;
- exports.IcuPlaceholder = IcuPlaceholder;
- exports.Identifiers = Identifiers;
- exports.ImplicitReceiver = ImplicitReceiver;
- exports.ImportManager = ImportManager;
- exports.Interpolation = Interpolation$1;
- exports.InterpolationConfig = InterpolationConfig;
- exports.InvokeFunctionExpr = InvokeFunctionExpr;
- exports.LetDeclaration = LetDeclaration$1;
- exports.LiteralArrayExpr = LiteralArrayExpr;
- exports.LiteralExpr = LiteralExpr;
- exports.LocalIdentifierStrategy = LocalIdentifierStrategy;
- exports.LogicalFileSystem = LogicalFileSystem;
- exports.LogicalProjectStrategy = LogicalProjectStrategy;
- exports.MODEL_INITIALIZER_FN = MODEL_INITIALIZER_FN;
- exports.Message = Message;
- exports.NO_ERRORS_SCHEMA = NO_ERRORS_SCHEMA;
- exports.NULL_EXPR = NULL_EXPR;
- exports.NgOriginalFile = NgOriginalFile;
- exports.NodeJSFileSystem = NodeJSFileSystem;
- exports.OUTPUT_INITIALIZER_FNS = OUTPUT_INITIALIZER_FNS;
- exports.ParseLocation = ParseLocation;
- exports.ParseSourceFile = ParseSourceFile;
- exports.ParseSourceSpan = ParseSourceSpan;
- exports.Parser = Parser$1;
- exports.Placeholder = Placeholder;
- exports.PropertyRead = PropertyRead;
- exports.PropertyWrite = PropertyWrite;
- exports.QUERY_INITIALIZER_FNS = QUERY_INITIALIZER_FNS;
- exports.R3TargetBinder = R3TargetBinder;
- exports.ReadVarExpr = ReadVarExpr;
- exports.RecursiveAstVisitor = RecursiveAstVisitor;
- exports.RecursiveAstVisitor$1 = RecursiveAstVisitor$1;
- exports.RecursiveVisitor = RecursiveVisitor;
- exports.RecursiveVisitor$1 = RecursiveVisitor$1;
- exports.Reference = Reference;
- exports.Reference$1 = Reference$1;
- exports.ReferenceEmitter = ReferenceEmitter;
- exports.RelativePathStrategy = RelativePathStrategy;
- exports.ReturnStatement = ReturnStatement;
- exports.SafeCall = SafeCall;
- exports.SafeKeyedRead = SafeKeyedRead;
- exports.SafePropertyRead = SafePropertyRead;
- exports.SelectorMatcher = SelectorMatcher;
- exports.Serializer = Serializer;
- exports.StaticInterpreter = StaticInterpreter;
- exports.SyntheticValue = SyntheticValue;
- exports.Tag = Tag;
- exports.TagPlaceholder = TagPlaceholder;
- exports.Template = Template;
- exports.TemplateTypeCheckerImpl = TemplateTypeCheckerImpl;
- exports.Text = Text;
- exports.Text$1 = Text$1;
- exports.Text$2 = Text$2;
- exports.TextAttribute = TextAttribute;
- exports.ThisReceiver = ThisReceiver;
- exports.Trait = Trait;
- exports.TypeCheckShimGenerator = TypeCheckShimGenerator;
- exports.TypeScriptReflectionHost = TypeScriptReflectionHost;
- exports.UNSAFE_OBJECT_KEY_NAME_REGEXP = UNSAFE_OBJECT_KEY_NAME_REGEXP;
- exports.UnifiedModulesStrategy = UnifiedModulesStrategy;
- exports.Variable = Variable;
- exports.Version = Version;
- exports.WhitespaceVisitor = WhitespaceVisitor;
- exports.WrappedNodeExpr = WrappedNodeExpr;
- exports.Xmb = Xmb;
- exports.absoluteFrom = absoluteFrom;
- exports.absoluteFromSourceFile = absoluteFromSourceFile;
- exports.arrowFn = arrowFn;
- exports.asLiteral = asLiteral;
- exports.assertLocalCompilationUnresolvedConst = assertLocalCompilationUnresolvedConst;
- exports.assertSuccessfulReferenceEmit = assertSuccessfulReferenceEmit;
- exports.checkInheritanceOfInjectable = checkInheritanceOfInjectable;
- exports.combineResolvers = combineResolvers;
- exports.compileComponentFromMetadata = compileComponentFromMetadata;
- exports.compileDeferResolverFunction = compileDeferResolverFunction;
- exports.compileDirectiveFromMetadata = compileDirectiveFromMetadata;
- exports.compileFactoryFunction = compileFactoryFunction;
- exports.compileInjectable = compileInjectable;
- exports.compileInjector = compileInjector;
- exports.compileNgModule = compileNgModule;
- exports.compilePipeFromMetadata = compilePipeFromMetadata;
- exports.compileResults = compileResults;
- exports.conditionallyCreateDirectiveBindingLiteral = conditionallyCreateDirectiveBindingLiteral;
- exports.convertFromMaybeForwardRefExpression = convertFromMaybeForwardRefExpression;
- exports.copyFileShimData = copyFileShimData;
- exports.createComponentType = createComponentType;
- exports.createDirectiveType = createDirectiveType;
- exports.createFactoryType = createFactoryType;
- exports.createForwardRefResolver = createForwardRefResolver;
- exports.createHostDirectivesMappingArray = createHostDirectivesMappingArray;
- exports.createInjectableType = createInjectableType;
- exports.createInjectorType = createInjectorType;
- exports.createMayBeForwardRefExpression = createMayBeForwardRefExpression;
- exports.createNgModuleType = createNgModuleType;
- exports.createPipeType = createPipeType;
- exports.createValueHasWrongTypeError = createValueHasWrongTypeError;
- exports.decimalDigest = decimalDigest;
- exports.devOnlyGuardedExpression = devOnlyGuardedExpression;
- exports.digest = digest$1;
- exports.dirname = dirname;
- exports.entityNameToValue = entityNameToValue;
- exports.extraReferenceFromTypeQuery = extraReferenceFromTypeQuery;
- exports.extractDecoratorQueryMetadata = extractDecoratorQueryMetadata;
- exports.extractDirectiveMetadata = extractDirectiveMetadata;
- exports.extractDirectiveTypeCheckMeta = extractDirectiveTypeCheckMeta;
- exports.extractMessages = extractMessages;
- exports.extractReferencesFromType = extractReferencesFromType;
- exports.findAngularDecorator = findAngularDecorator;
- exports.flattenInheritedDirectiveMetadata = flattenInheritedDirectiveMetadata;
- exports.generateForwardRef = generateForwardRef;
- exports.getAngularDecorators = getAngularDecorators;
- exports.getConstructorDependencies = getConstructorDependencies;
- exports.getContainingImportDeclaration = getContainingImportDeclaration;
- exports.getDefaultImportDeclaration = getDefaultImportDeclaration;
- exports.getDirectiveDiagnostics = getDirectiveDiagnostics;
- exports.getFileSystem = getFileSystem;
- exports.getOriginNodeForDiagnostics = getOriginNodeForDiagnostics;
- exports.getProviderDiagnostics = getProviderDiagnostics;
- exports.getRootDirs = getRootDirs;
- exports.getSourceFile = getSourceFile;
- exports.getSourceFileOrNull = getSourceFileOrNull;
- exports.getTemplateDiagnostics = getTemplateDiagnostics;
- exports.getUndecoratedClassWithAngularFeaturesDiagnostic = getUndecoratedClassWithAngularFeaturesDiagnostic;
- exports.getValidConstructorDependencies = getValidConstructorDependencies;
- exports.hasInjectableFields = hasInjectableFields;
- exports.identifierOfNode = identifierOfNode;
- exports.importExpr = importExpr;
- exports.isAbstractClassDeclaration = isAbstractClassDeclaration;
- exports.isAliasImportDeclaration = isAliasImportDeclaration;
- exports.isAngularCore = isAngularCore;
- exports.isAngularCoreReferenceWithPotentialAliasing = isAngularCoreReferenceWithPotentialAliasing;
- exports.isAngularDecorator = isAngularDecorator;
- exports.isDtsPath = isDtsPath;
- exports.isExpressionForwardReference = isExpressionForwardReference;
- exports.isFatalDiagnosticError = isFatalDiagnosticError;
- exports.isFileShimSourceFile = isFileShimSourceFile;
- exports.isHostDirectiveMetaForGlobalMode = isHostDirectiveMetaForGlobalMode;
- exports.isLocalRelativePath = isLocalRelativePath;
- exports.isNamedClassDeclaration = isNamedClassDeclaration;
- exports.isNonDeclarationTsPath = isNonDeclarationTsPath;
- exports.isShim = isShim;
- exports.join = join;
- exports.literal = literal$1;
- exports.literalArr = literalArr;
- exports.literalMap = literalMap;
- exports.loadIsReferencedAliasDeclarationPatch = loadIsReferencedAliasDeclarationPatch;
- exports.makeBindingParser = makeBindingParser;
- exports.makeDiagnostic = makeDiagnostic;
- exports.makeDuplicateDeclarationError = makeDuplicateDeclarationError;
- exports.makeRelatedInformation = makeRelatedInformation;
- exports.mapLiteral = mapLiteral;
- exports.ngErrorCode = ngErrorCode;
- exports.nodeDebugInfo = nodeDebugInfo;
- exports.nodeNameForError = nodeNameForError;
- exports.parseDecoratorInputTransformFunction = parseDecoratorInputTransformFunction;
- exports.parseDirectiveStyles = parseDirectiveStyles;
- exports.parseTemplate = parseTemplate;
- exports.presetImportManagerForceNamespaceImports = presetImportManagerForceNamespaceImports;
- exports.queryDecoratorNames = queryDecoratorNames;
- exports.readBaseClass = readBaseClass;
- exports.readBooleanType = readBooleanType;
- exports.readMapType = readMapType;
- exports.readStringArrayType = readStringArrayType;
- exports.readStringType = readStringType;
- exports.reflectClassMember = reflectClassMember;
- exports.reflectObjectLiteral = reflectObjectLiteral;
- exports.refsToArray = refsToArray;
- exports.relative = relative;
- exports.resolve = resolve;
- exports.resolveImportedFile = resolveImportedFile;
- exports.resolveModuleName = resolveModuleName;
- exports.resolveProvidersRequiringFactory = resolveProvidersRequiringFactory;
- exports.retagAllTsFiles = retagAllTsFiles;
- exports.serialize = serialize$1;
- exports.setFileSystem = setFileSystem;
- exports.sfExtensionData = sfExtensionData;
- exports.stripExtension = stripExtension;
- exports.toFactoryMetadata = toFactoryMetadata;
- exports.toR3Reference = toR3Reference;
- exports.toRelativeImport = toRelativeImport;
- exports.toUnredirectedSourceFile = toUnredirectedSourceFile;
- exports.translateExpression = translateExpression;
- exports.translateStatement = translateStatement;
- exports.translateType = translateType;
- exports.transplantedType = transplantedType;
- exports.tryParseInitializerApi = tryParseInitializerApi;
- exports.tryParseInitializerBasedOutput = tryParseInitializerBasedOutput;
- exports.tryParseSignalInputMapping = tryParseSignalInputMapping;
- exports.tryParseSignalModelMapping = tryParseSignalModelMapping;
- exports.tryParseSignalQueryFromInitializer = tryParseSignalQueryFromInitializer;
- exports.tryUnwrapForwardRef = tryUnwrapForwardRef;
- exports.typeNodeToValueExpr = typeNodeToValueExpr;
- exports.untagAllTsFiles = untagAllTsFiles;
- exports.unwrapConstructorDependencies = unwrapConstructorDependencies;
- exports.unwrapExpression = unwrapExpression;
- exports.validateConstructorDependencies = validateConstructorDependencies;
- exports.validateHostDirectives = validateHostDirectives;
- exports.valueReferenceToExpression = valueReferenceToExpression;
- exports.variable = variable;
- exports.visitAll = visitAll;
- exports.visitAll$1 = visitAll$1;
- exports.visitAllWithSiblings = visitAllWithSiblings;
- exports.wrapFunctionExpressionsInParens = wrapFunctionExpressionsInParens;
- exports.wrapTypeReference = wrapTypeReference;
|