index-BIvVb6in.cjs 866 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303
  1. 'use strict';
  2. /**
  3. * @license Angular v19.2.13
  4. * (c) 2010-2025 Google LLC. https://angular.io/
  5. * License: MIT
  6. */
  7. 'use strict';
  8. var checker = require('./checker-5pyJrZ9G.cjs');
  9. var ts = require('typescript');
  10. var p = require('path');
  11. require('os');
  12. function _interopNamespaceDefault(e) {
  13. var n = Object.create(null);
  14. if (e) {
  15. Object.keys(e).forEach(function (k) {
  16. if (k !== 'default') {
  17. var d = Object.getOwnPropertyDescriptor(e, k);
  18. Object.defineProperty(n, k, d.get ? d : {
  19. enumerable: true,
  20. get: function () { return e[k]; }
  21. });
  22. }
  23. });
  24. }
  25. n.default = e;
  26. return Object.freeze(n);
  27. }
  28. var p__namespace = /*#__PURE__*/_interopNamespaceDefault(p);
  29. class XmlTagDefinition {
  30. closedByParent = false;
  31. implicitNamespacePrefix = null;
  32. isVoid = false;
  33. ignoreFirstLf = false;
  34. canSelfClose = true;
  35. preventNamespaceInheritance = false;
  36. requireExtraParent(currentParent) {
  37. return false;
  38. }
  39. isClosedByChild(name) {
  40. return false;
  41. }
  42. getContentType() {
  43. return checker.TagContentType.PARSABLE_DATA;
  44. }
  45. }
  46. const _TAG_DEFINITION = new XmlTagDefinition();
  47. function getXmlTagDefinition(tagName) {
  48. return _TAG_DEFINITION;
  49. }
  50. class XmlParser extends checker.Parser {
  51. constructor() {
  52. super(getXmlTagDefinition);
  53. }
  54. parse(source, url, options = {}) {
  55. // Blocks and let declarations aren't supported in an XML context.
  56. return super.parse(source, url, { ...options, tokenizeBlocks: false, tokenizeLet: false });
  57. }
  58. }
  59. const _VERSION$1 = '1.2';
  60. const _XMLNS$1 = 'urn:oasis:names:tc:xliff:document:1.2';
  61. // TODO(vicb): make this a param (s/_/-/)
  62. const _DEFAULT_SOURCE_LANG$1 = 'en';
  63. const _PLACEHOLDER_TAG$1 = 'x';
  64. const _MARKER_TAG$1 = 'mrk';
  65. const _FILE_TAG = 'file';
  66. const _SOURCE_TAG$1 = 'source';
  67. const _SEGMENT_SOURCE_TAG = 'seg-source';
  68. const _ALT_TRANS_TAG = 'alt-trans';
  69. const _TARGET_TAG$1 = 'target';
  70. const _UNIT_TAG$1 = 'trans-unit';
  71. const _CONTEXT_GROUP_TAG = 'context-group';
  72. const _CONTEXT_TAG = 'context';
  73. // https://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
  74. // https://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
  75. class Xliff extends checker.Serializer {
  76. write(messages, locale) {
  77. const visitor = new _WriteVisitor$1();
  78. const transUnits = [];
  79. messages.forEach((message) => {
  80. let contextTags = [];
  81. message.sources.forEach((source) => {
  82. let contextGroupTag = new checker.Tag(_CONTEXT_GROUP_TAG, { purpose: 'location' });
  83. contextGroupTag.children.push(new checker.CR(10), new checker.Tag(_CONTEXT_TAG, { 'context-type': 'sourcefile' }, [
  84. new checker.Text$1(source.filePath),
  85. ]), new checker.CR(10), new checker.Tag(_CONTEXT_TAG, { 'context-type': 'linenumber' }, [
  86. new checker.Text$1(`${source.startLine}`),
  87. ]), new checker.CR(8));
  88. contextTags.push(new checker.CR(8), contextGroupTag);
  89. });
  90. const transUnit = new checker.Tag(_UNIT_TAG$1, { id: message.id, datatype: 'html' });
  91. transUnit.children.push(new checker.CR(8), new checker.Tag(_SOURCE_TAG$1, {}, visitor.serialize(message.nodes)), ...contextTags);
  92. if (message.description) {
  93. transUnit.children.push(new checker.CR(8), new checker.Tag('note', { priority: '1', from: 'description' }, [
  94. new checker.Text$1(message.description),
  95. ]));
  96. }
  97. if (message.meaning) {
  98. transUnit.children.push(new checker.CR(8), new checker.Tag('note', { priority: '1', from: 'meaning' }, [new checker.Text$1(message.meaning)]));
  99. }
  100. transUnit.children.push(new checker.CR(6));
  101. transUnits.push(new checker.CR(6), transUnit);
  102. });
  103. const body = new checker.Tag('body', {}, [...transUnits, new checker.CR(4)]);
  104. const file = new checker.Tag('file', {
  105. 'source-language': locale || _DEFAULT_SOURCE_LANG$1,
  106. datatype: 'plaintext',
  107. original: 'ng2.template',
  108. }, [new checker.CR(4), body, new checker.CR(2)]);
  109. const xliff = new checker.Tag('xliff', { version: _VERSION$1, xmlns: _XMLNS$1 }, [
  110. new checker.CR(2),
  111. file,
  112. new checker.CR(),
  113. ]);
  114. return checker.serialize([
  115. new checker.Declaration({ version: '1.0', encoding: 'UTF-8' }),
  116. new checker.CR(),
  117. xliff,
  118. new checker.CR(),
  119. ]);
  120. }
  121. load(content, url) {
  122. // xliff to xml nodes
  123. const xliffParser = new XliffParser();
  124. const { locale, msgIdToHtml, errors } = xliffParser.parse(content, url);
  125. // xml nodes to i18n nodes
  126. const i18nNodesByMsgId = {};
  127. const converter = new XmlToI18n$1();
  128. Object.keys(msgIdToHtml).forEach((msgId) => {
  129. const { i18nNodes, errors: e } = converter.convert(msgIdToHtml[msgId], url);
  130. errors.push(...e);
  131. i18nNodesByMsgId[msgId] = i18nNodes;
  132. });
  133. if (errors.length) {
  134. throw new Error(`xliff parse errors:\n${errors.join('\n')}`);
  135. }
  136. return { locale: locale, i18nNodesByMsgId };
  137. }
  138. digest(message) {
  139. return checker.digest(message);
  140. }
  141. }
  142. let _WriteVisitor$1 = class _WriteVisitor {
  143. visitText(text, context) {
  144. return [new checker.Text$1(text.value)];
  145. }
  146. visitContainer(container, context) {
  147. const nodes = [];
  148. container.children.forEach((node) => nodes.push(...node.visit(this)));
  149. return nodes;
  150. }
  151. visitIcu(icu, context) {
  152. const nodes = [new checker.Text$1(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
  153. Object.keys(icu.cases).forEach((c) => {
  154. nodes.push(new checker.Text$1(`${c} {`), ...icu.cases[c].visit(this), new checker.Text$1(`} `));
  155. });
  156. nodes.push(new checker.Text$1(`}`));
  157. return nodes;
  158. }
  159. visitTagPlaceholder(ph, context) {
  160. const ctype = getCtypeForTag(ph.tag);
  161. if (ph.isVoid) {
  162. // void tags have no children nor closing tags
  163. return [
  164. new checker.Tag(_PLACEHOLDER_TAG$1, { id: ph.startName, ctype, 'equiv-text': `<${ph.tag}/>` }),
  165. ];
  166. }
  167. const startTagPh = new checker.Tag(_PLACEHOLDER_TAG$1, {
  168. id: ph.startName,
  169. ctype,
  170. 'equiv-text': `<${ph.tag}>`,
  171. });
  172. const closeTagPh = new checker.Tag(_PLACEHOLDER_TAG$1, {
  173. id: ph.closeName,
  174. ctype,
  175. 'equiv-text': `</${ph.tag}>`,
  176. });
  177. return [startTagPh, ...this.serialize(ph.children), closeTagPh];
  178. }
  179. visitPlaceholder(ph, context) {
  180. return [new checker.Tag(_PLACEHOLDER_TAG$1, { id: ph.name, 'equiv-text': `{{${ph.value}}}` })];
  181. }
  182. visitBlockPlaceholder(ph, context) {
  183. const ctype = `x-${ph.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
  184. const startTagPh = new checker.Tag(_PLACEHOLDER_TAG$1, {
  185. id: ph.startName,
  186. ctype,
  187. 'equiv-text': `@${ph.name}`,
  188. });
  189. const closeTagPh = new checker.Tag(_PLACEHOLDER_TAG$1, { id: ph.closeName, ctype, 'equiv-text': `}` });
  190. return [startTagPh, ...this.serialize(ph.children), closeTagPh];
  191. }
  192. visitIcuPlaceholder(ph, context) {
  193. const equivText = `{${ph.value.expression}, ${ph.value.type}, ${Object.keys(ph.value.cases)
  194. .map((value) => value + ' {...}')
  195. .join(' ')}}`;
  196. return [new checker.Tag(_PLACEHOLDER_TAG$1, { id: ph.name, 'equiv-text': equivText })];
  197. }
  198. serialize(nodes) {
  199. return [].concat(...nodes.map((node) => node.visit(this)));
  200. }
  201. };
  202. // TODO(vicb): add error management (structure)
  203. // Extract messages as xml nodes from the xliff file
  204. class XliffParser {
  205. // using non-null assertions because they're re(set) by parse()
  206. _unitMlString;
  207. _errors;
  208. _msgIdToHtml;
  209. _locale = null;
  210. parse(xliff, url) {
  211. this._unitMlString = null;
  212. this._msgIdToHtml = {};
  213. const xml = new XmlParser().parse(xliff, url);
  214. this._errors = xml.errors;
  215. checker.visitAll(this, xml.rootNodes, null);
  216. return {
  217. msgIdToHtml: this._msgIdToHtml,
  218. errors: this._errors,
  219. locale: this._locale,
  220. };
  221. }
  222. visitElement(element, context) {
  223. switch (element.name) {
  224. case _UNIT_TAG$1:
  225. this._unitMlString = null;
  226. const idAttr = element.attrs.find((attr) => attr.name === 'id');
  227. if (!idAttr) {
  228. this._addError(element, `<${_UNIT_TAG$1}> misses the "id" attribute`);
  229. }
  230. else {
  231. const id = idAttr.value;
  232. if (this._msgIdToHtml.hasOwnProperty(id)) {
  233. this._addError(element, `Duplicated translations for msg ${id}`);
  234. }
  235. else {
  236. checker.visitAll(this, element.children, null);
  237. if (typeof this._unitMlString === 'string') {
  238. this._msgIdToHtml[id] = this._unitMlString;
  239. }
  240. else {
  241. this._addError(element, `Message ${id} misses a translation`);
  242. }
  243. }
  244. }
  245. break;
  246. // ignore those tags
  247. case _SOURCE_TAG$1:
  248. case _SEGMENT_SOURCE_TAG:
  249. case _ALT_TRANS_TAG:
  250. break;
  251. case _TARGET_TAG$1:
  252. const innerTextStart = element.startSourceSpan.end.offset;
  253. const innerTextEnd = element.endSourceSpan.start.offset;
  254. const content = element.startSourceSpan.start.file.content;
  255. const innerText = content.slice(innerTextStart, innerTextEnd);
  256. this._unitMlString = innerText;
  257. break;
  258. case _FILE_TAG:
  259. const localeAttr = element.attrs.find((attr) => attr.name === 'target-language');
  260. if (localeAttr) {
  261. this._locale = localeAttr.value;
  262. }
  263. checker.visitAll(this, element.children, null);
  264. break;
  265. default:
  266. // TODO(vicb): assert file structure, xliff version
  267. // For now only recurse on unhandled nodes
  268. checker.visitAll(this, element.children, null);
  269. }
  270. }
  271. visitAttribute(attribute, context) { }
  272. visitText(text, context) { }
  273. visitComment(comment, context) { }
  274. visitExpansion(expansion, context) { }
  275. visitExpansionCase(expansionCase, context) { }
  276. visitBlock(block, context) { }
  277. visitBlockParameter(parameter, context) { }
  278. visitLetDeclaration(decl, context) { }
  279. _addError(node, message) {
  280. this._errors.push(new checker.I18nError(node.sourceSpan, message));
  281. }
  282. }
  283. // Convert ml nodes (xliff syntax) to i18n nodes
  284. let XmlToI18n$1 = class XmlToI18n {
  285. // using non-null assertion because it's re(set) by convert()
  286. _errors;
  287. convert(message, url) {
  288. const xmlIcu = new XmlParser().parse(message, url, { tokenizeExpansionForms: true });
  289. this._errors = xmlIcu.errors;
  290. const i18nNodes = this._errors.length > 0 || xmlIcu.rootNodes.length == 0
  291. ? []
  292. : [].concat(...checker.visitAll(this, xmlIcu.rootNodes));
  293. return {
  294. i18nNodes: i18nNodes,
  295. errors: this._errors,
  296. };
  297. }
  298. visitText(text, context) {
  299. return new checker.Text$2(text.value, text.sourceSpan);
  300. }
  301. visitElement(el, context) {
  302. if (el.name === _PLACEHOLDER_TAG$1) {
  303. const nameAttr = el.attrs.find((attr) => attr.name === 'id');
  304. if (nameAttr) {
  305. return new checker.Placeholder('', nameAttr.value, el.sourceSpan);
  306. }
  307. this._addError(el, `<${_PLACEHOLDER_TAG$1}> misses the "id" attribute`);
  308. return null;
  309. }
  310. if (el.name === _MARKER_TAG$1) {
  311. return [].concat(...checker.visitAll(this, el.children));
  312. }
  313. this._addError(el, `Unexpected tag`);
  314. return null;
  315. }
  316. visitExpansion(icu, context) {
  317. const caseMap = {};
  318. checker.visitAll(this, icu.cases).forEach((c) => {
  319. caseMap[c.value] = new checker.Container(c.nodes, icu.sourceSpan);
  320. });
  321. return new checker.Icu(icu.switchValue, icu.type, caseMap, icu.sourceSpan);
  322. }
  323. visitExpansionCase(icuCase, context) {
  324. return {
  325. value: icuCase.value,
  326. nodes: checker.visitAll(this, icuCase.expression),
  327. };
  328. }
  329. visitComment(comment, context) { }
  330. visitAttribute(attribute, context) { }
  331. visitBlock(block, context) { }
  332. visitBlockParameter(parameter, context) { }
  333. visitLetDeclaration(decl, context) { }
  334. _addError(node, message) {
  335. this._errors.push(new checker.I18nError(node.sourceSpan, message));
  336. }
  337. };
  338. function getCtypeForTag(tag) {
  339. switch (tag.toLowerCase()) {
  340. case 'br':
  341. return 'lb';
  342. case 'img':
  343. return 'image';
  344. default:
  345. return `x-${tag}`;
  346. }
  347. }
  348. const _VERSION = '2.0';
  349. const _XMLNS = 'urn:oasis:names:tc:xliff:document:2.0';
  350. // TODO(vicb): make this a param (s/_/-/)
  351. const _DEFAULT_SOURCE_LANG = 'en';
  352. const _PLACEHOLDER_TAG = 'ph';
  353. const _PLACEHOLDER_SPANNING_TAG = 'pc';
  354. const _MARKER_TAG = 'mrk';
  355. const _XLIFF_TAG = 'xliff';
  356. const _SOURCE_TAG = 'source';
  357. const _TARGET_TAG = 'target';
  358. const _UNIT_TAG = 'unit';
  359. // https://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html
  360. class Xliff2 extends checker.Serializer {
  361. write(messages, locale) {
  362. const visitor = new _WriteVisitor();
  363. const units = [];
  364. messages.forEach((message) => {
  365. const unit = new checker.Tag(_UNIT_TAG, { id: message.id });
  366. const notes = new checker.Tag('notes');
  367. if (message.description || message.meaning) {
  368. if (message.description) {
  369. notes.children.push(new checker.CR(8), new checker.Tag('note', { category: 'description' }, [new checker.Text$1(message.description)]));
  370. }
  371. if (message.meaning) {
  372. notes.children.push(new checker.CR(8), new checker.Tag('note', { category: 'meaning' }, [new checker.Text$1(message.meaning)]));
  373. }
  374. }
  375. message.sources.forEach((source) => {
  376. notes.children.push(new checker.CR(8), new checker.Tag('note', { category: 'location' }, [
  377. new checker.Text$1(`${source.filePath}:${source.startLine}${source.endLine !== source.startLine ? ',' + source.endLine : ''}`),
  378. ]));
  379. });
  380. notes.children.push(new checker.CR(6));
  381. unit.children.push(new checker.CR(6), notes);
  382. const segment = new checker.Tag('segment');
  383. segment.children.push(new checker.CR(8), new checker.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)), new checker.CR(6));
  384. unit.children.push(new checker.CR(6), segment, new checker.CR(4));
  385. units.push(new checker.CR(4), unit);
  386. });
  387. const file = new checker.Tag('file', { 'original': 'ng.template', id: 'ngi18n' }, [
  388. ...units,
  389. new checker.CR(2),
  390. ]);
  391. const xliff = new checker.Tag(_XLIFF_TAG, { version: _VERSION, xmlns: _XMLNS, srcLang: locale || _DEFAULT_SOURCE_LANG }, [new checker.CR(2), file, new checker.CR()]);
  392. return checker.serialize([
  393. new checker.Declaration({ version: '1.0', encoding: 'UTF-8' }),
  394. new checker.CR(),
  395. xliff,
  396. new checker.CR(),
  397. ]);
  398. }
  399. load(content, url) {
  400. // xliff to xml nodes
  401. const xliff2Parser = new Xliff2Parser();
  402. const { locale, msgIdToHtml, errors } = xliff2Parser.parse(content, url);
  403. // xml nodes to i18n nodes
  404. const i18nNodesByMsgId = {};
  405. const converter = new XmlToI18n();
  406. Object.keys(msgIdToHtml).forEach((msgId) => {
  407. const { i18nNodes, errors: e } = converter.convert(msgIdToHtml[msgId], url);
  408. errors.push(...e);
  409. i18nNodesByMsgId[msgId] = i18nNodes;
  410. });
  411. if (errors.length) {
  412. throw new Error(`xliff2 parse errors:\n${errors.join('\n')}`);
  413. }
  414. return { locale: locale, i18nNodesByMsgId };
  415. }
  416. digest(message) {
  417. return checker.decimalDigest(message);
  418. }
  419. }
  420. class _WriteVisitor {
  421. _nextPlaceholderId = 0;
  422. visitText(text, context) {
  423. return [new checker.Text$1(text.value)];
  424. }
  425. visitContainer(container, context) {
  426. const nodes = [];
  427. container.children.forEach((node) => nodes.push(...node.visit(this)));
  428. return nodes;
  429. }
  430. visitIcu(icu, context) {
  431. const nodes = [new checker.Text$1(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
  432. Object.keys(icu.cases).forEach((c) => {
  433. nodes.push(new checker.Text$1(`${c} {`), ...icu.cases[c].visit(this), new checker.Text$1(`} `));
  434. });
  435. nodes.push(new checker.Text$1(`}`));
  436. return nodes;
  437. }
  438. visitTagPlaceholder(ph, context) {
  439. const type = getTypeForTag(ph.tag);
  440. if (ph.isVoid) {
  441. const tagPh = new checker.Tag(_PLACEHOLDER_TAG, {
  442. id: (this._nextPlaceholderId++).toString(),
  443. equiv: ph.startName,
  444. type: type,
  445. disp: `<${ph.tag}/>`,
  446. });
  447. return [tagPh];
  448. }
  449. const tagPc = new checker.Tag(_PLACEHOLDER_SPANNING_TAG, {
  450. id: (this._nextPlaceholderId++).toString(),
  451. equivStart: ph.startName,
  452. equivEnd: ph.closeName,
  453. type: type,
  454. dispStart: `<${ph.tag}>`,
  455. dispEnd: `</${ph.tag}>`,
  456. });
  457. const nodes = [].concat(...ph.children.map((node) => node.visit(this)));
  458. if (nodes.length) {
  459. nodes.forEach((node) => tagPc.children.push(node));
  460. }
  461. else {
  462. tagPc.children.push(new checker.Text$1(''));
  463. }
  464. return [tagPc];
  465. }
  466. visitPlaceholder(ph, context) {
  467. const idStr = (this._nextPlaceholderId++).toString();
  468. return [
  469. new checker.Tag(_PLACEHOLDER_TAG, {
  470. id: idStr,
  471. equiv: ph.name,
  472. disp: `{{${ph.value}}}`,
  473. }),
  474. ];
  475. }
  476. visitBlockPlaceholder(ph, context) {
  477. const tagPc = new checker.Tag(_PLACEHOLDER_SPANNING_TAG, {
  478. id: (this._nextPlaceholderId++).toString(),
  479. equivStart: ph.startName,
  480. equivEnd: ph.closeName,
  481. type: 'other',
  482. dispStart: `@${ph.name}`,
  483. dispEnd: `}`,
  484. });
  485. const nodes = [].concat(...ph.children.map((node) => node.visit(this)));
  486. if (nodes.length) {
  487. nodes.forEach((node) => tagPc.children.push(node));
  488. }
  489. else {
  490. tagPc.children.push(new checker.Text$1(''));
  491. }
  492. return [tagPc];
  493. }
  494. visitIcuPlaceholder(ph, context) {
  495. const cases = Object.keys(ph.value.cases)
  496. .map((value) => value + ' {...}')
  497. .join(' ');
  498. const idStr = (this._nextPlaceholderId++).toString();
  499. return [
  500. new checker.Tag(_PLACEHOLDER_TAG, {
  501. id: idStr,
  502. equiv: ph.name,
  503. disp: `{${ph.value.expression}, ${ph.value.type}, ${cases}}`,
  504. }),
  505. ];
  506. }
  507. serialize(nodes) {
  508. this._nextPlaceholderId = 0;
  509. return [].concat(...nodes.map((node) => node.visit(this)));
  510. }
  511. }
  512. // Extract messages as xml nodes from the xliff file
  513. class Xliff2Parser {
  514. // using non-null assertions because they're all (re)set by parse()
  515. _unitMlString;
  516. _errors;
  517. _msgIdToHtml;
  518. _locale = null;
  519. parse(xliff, url) {
  520. this._unitMlString = null;
  521. this._msgIdToHtml = {};
  522. const xml = new XmlParser().parse(xliff, url);
  523. this._errors = xml.errors;
  524. checker.visitAll(this, xml.rootNodes, null);
  525. return {
  526. msgIdToHtml: this._msgIdToHtml,
  527. errors: this._errors,
  528. locale: this._locale,
  529. };
  530. }
  531. visitElement(element, context) {
  532. switch (element.name) {
  533. case _UNIT_TAG:
  534. this._unitMlString = null;
  535. const idAttr = element.attrs.find((attr) => attr.name === 'id');
  536. if (!idAttr) {
  537. this._addError(element, `<${_UNIT_TAG}> misses the "id" attribute`);
  538. }
  539. else {
  540. const id = idAttr.value;
  541. if (this._msgIdToHtml.hasOwnProperty(id)) {
  542. this._addError(element, `Duplicated translations for msg ${id}`);
  543. }
  544. else {
  545. checker.visitAll(this, element.children, null);
  546. if (typeof this._unitMlString === 'string') {
  547. this._msgIdToHtml[id] = this._unitMlString;
  548. }
  549. else {
  550. this._addError(element, `Message ${id} misses a translation`);
  551. }
  552. }
  553. }
  554. break;
  555. case _SOURCE_TAG:
  556. // ignore source message
  557. break;
  558. case _TARGET_TAG:
  559. const innerTextStart = element.startSourceSpan.end.offset;
  560. const innerTextEnd = element.endSourceSpan.start.offset;
  561. const content = element.startSourceSpan.start.file.content;
  562. const innerText = content.slice(innerTextStart, innerTextEnd);
  563. this._unitMlString = innerText;
  564. break;
  565. case _XLIFF_TAG:
  566. const localeAttr = element.attrs.find((attr) => attr.name === 'trgLang');
  567. if (localeAttr) {
  568. this._locale = localeAttr.value;
  569. }
  570. const versionAttr = element.attrs.find((attr) => attr.name === 'version');
  571. if (versionAttr) {
  572. const version = versionAttr.value;
  573. if (version !== '2.0') {
  574. this._addError(element, `The XLIFF file version ${version} is not compatible with XLIFF 2.0 serializer`);
  575. }
  576. else {
  577. checker.visitAll(this, element.children, null);
  578. }
  579. }
  580. break;
  581. default:
  582. checker.visitAll(this, element.children, null);
  583. }
  584. }
  585. visitAttribute(attribute, context) { }
  586. visitText(text, context) { }
  587. visitComment(comment, context) { }
  588. visitExpansion(expansion, context) { }
  589. visitExpansionCase(expansionCase, context) { }
  590. visitBlock(block, context) { }
  591. visitBlockParameter(parameter, context) { }
  592. visitLetDeclaration(decl, context) { }
  593. _addError(node, message) {
  594. this._errors.push(new checker.I18nError(node.sourceSpan, message));
  595. }
  596. }
  597. // Convert ml nodes (xliff syntax) to i18n nodes
  598. class XmlToI18n {
  599. // using non-null assertion because re(set) by convert()
  600. _errors;
  601. convert(message, url) {
  602. const xmlIcu = new XmlParser().parse(message, url, { tokenizeExpansionForms: true });
  603. this._errors = xmlIcu.errors;
  604. const i18nNodes = this._errors.length > 0 || xmlIcu.rootNodes.length == 0
  605. ? []
  606. : [].concat(...checker.visitAll(this, xmlIcu.rootNodes));
  607. return {
  608. i18nNodes,
  609. errors: this._errors,
  610. };
  611. }
  612. visitText(text, context) {
  613. return new checker.Text$2(text.value, text.sourceSpan);
  614. }
  615. visitElement(el, context) {
  616. switch (el.name) {
  617. case _PLACEHOLDER_TAG:
  618. const nameAttr = el.attrs.find((attr) => attr.name === 'equiv');
  619. if (nameAttr) {
  620. return [new checker.Placeholder('', nameAttr.value, el.sourceSpan)];
  621. }
  622. this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equiv" attribute`);
  623. break;
  624. case _PLACEHOLDER_SPANNING_TAG:
  625. const startAttr = el.attrs.find((attr) => attr.name === 'equivStart');
  626. const endAttr = el.attrs.find((attr) => attr.name === 'equivEnd');
  627. if (!startAttr) {
  628. this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equivStart" attribute`);
  629. }
  630. else if (!endAttr) {
  631. this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equivEnd" attribute`);
  632. }
  633. else {
  634. const startId = startAttr.value;
  635. const endId = endAttr.value;
  636. const nodes = [];
  637. return nodes.concat(new checker.Placeholder('', startId, el.sourceSpan), ...el.children.map((node) => node.visit(this, null)), new checker.Placeholder('', endId, el.sourceSpan));
  638. }
  639. break;
  640. case _MARKER_TAG:
  641. return [].concat(...checker.visitAll(this, el.children));
  642. default:
  643. this._addError(el, `Unexpected tag`);
  644. }
  645. return null;
  646. }
  647. visitExpansion(icu, context) {
  648. const caseMap = {};
  649. checker.visitAll(this, icu.cases).forEach((c) => {
  650. caseMap[c.value] = new checker.Container(c.nodes, icu.sourceSpan);
  651. });
  652. return new checker.Icu(icu.switchValue, icu.type, caseMap, icu.sourceSpan);
  653. }
  654. visitExpansionCase(icuCase, context) {
  655. return {
  656. value: icuCase.value,
  657. nodes: [].concat(...checker.visitAll(this, icuCase.expression)),
  658. };
  659. }
  660. visitComment(comment, context) { }
  661. visitAttribute(attribute, context) { }
  662. visitBlock(block, context) { }
  663. visitBlockParameter(parameter, context) { }
  664. visitLetDeclaration(decl, context) { }
  665. _addError(node, message) {
  666. this._errors.push(new checker.I18nError(node.sourceSpan, message));
  667. }
  668. }
  669. function getTypeForTag(tag) {
  670. switch (tag.toLowerCase()) {
  671. case 'br':
  672. case 'b':
  673. case 'i':
  674. case 'u':
  675. return 'fmt';
  676. case 'img':
  677. return 'image';
  678. case 'a':
  679. return 'link';
  680. default:
  681. return 'other';
  682. }
  683. }
  684. /**
  685. * A container for message extracted from the templates.
  686. */
  687. class MessageBundle {
  688. _htmlParser;
  689. _implicitTags;
  690. _implicitAttrs;
  691. _locale;
  692. _preserveWhitespace;
  693. _messages = [];
  694. constructor(_htmlParser, _implicitTags, _implicitAttrs, _locale = null, _preserveWhitespace = true) {
  695. this._htmlParser = _htmlParser;
  696. this._implicitTags = _implicitTags;
  697. this._implicitAttrs = _implicitAttrs;
  698. this._locale = _locale;
  699. this._preserveWhitespace = _preserveWhitespace;
  700. }
  701. updateFromTemplate(source, url, interpolationConfig) {
  702. const htmlParserResult = this._htmlParser.parse(source, url, {
  703. tokenizeExpansionForms: true,
  704. interpolationConfig,
  705. });
  706. if (htmlParserResult.errors.length) {
  707. return htmlParserResult.errors;
  708. }
  709. // Trim unnecessary whitespace from extracted messages if requested. This
  710. // makes the messages more durable to trivial whitespace changes without
  711. // affected message IDs.
  712. const rootNodes = this._preserveWhitespace
  713. ? htmlParserResult.rootNodes
  714. : checker.visitAllWithSiblings(new checker.WhitespaceVisitor(/* preserveSignificantWhitespace */ false), htmlParserResult.rootNodes);
  715. const i18nParserResult = checker.extractMessages(rootNodes, interpolationConfig, this._implicitTags, this._implicitAttrs,
  716. /* preserveSignificantWhitespace */ this._preserveWhitespace);
  717. if (i18nParserResult.errors.length) {
  718. return i18nParserResult.errors;
  719. }
  720. this._messages.push(...i18nParserResult.messages);
  721. return [];
  722. }
  723. // Return the message in the internal format
  724. // The public (serialized) format might be different, see the `write` method.
  725. getMessages() {
  726. return this._messages;
  727. }
  728. write(serializer, filterSources) {
  729. const messages = {};
  730. const mapperVisitor = new MapPlaceholderNames();
  731. // Deduplicate messages based on their ID
  732. this._messages.forEach((message) => {
  733. const id = serializer.digest(message);
  734. if (!messages.hasOwnProperty(id)) {
  735. messages[id] = message;
  736. }
  737. else {
  738. messages[id].sources.push(...message.sources);
  739. }
  740. });
  741. // Transform placeholder names using the serializer mapping
  742. const msgList = Object.keys(messages).map((id) => {
  743. const mapper = serializer.createNameMapper(messages[id]);
  744. const src = messages[id];
  745. const nodes = mapper ? mapperVisitor.convert(src.nodes, mapper) : src.nodes;
  746. let transformedMessage = new checker.Message(nodes, {}, {}, src.meaning, src.description, id);
  747. transformedMessage.sources = src.sources;
  748. if (filterSources) {
  749. transformedMessage.sources.forEach((source) => (source.filePath = filterSources(source.filePath)));
  750. }
  751. return transformedMessage;
  752. });
  753. return serializer.write(msgList, this._locale);
  754. }
  755. }
  756. // Transform an i18n AST by renaming the placeholder nodes with the given mapper
  757. class MapPlaceholderNames extends checker.CloneVisitor {
  758. convert(nodes, mapper) {
  759. return mapper ? nodes.map((n) => n.visit(this, mapper)) : nodes;
  760. }
  761. visitTagPlaceholder(ph, mapper) {
  762. const startName = mapper.toPublicName(ph.startName);
  763. const closeName = ph.closeName ? mapper.toPublicName(ph.closeName) : ph.closeName;
  764. const children = ph.children.map((n) => n.visit(this, mapper));
  765. return new checker.TagPlaceholder(ph.tag, ph.attrs, startName, closeName, children, ph.isVoid, ph.sourceSpan, ph.startSourceSpan, ph.endSourceSpan);
  766. }
  767. visitBlockPlaceholder(ph, mapper) {
  768. const startName = mapper.toPublicName(ph.startName);
  769. const closeName = ph.closeName ? mapper.toPublicName(ph.closeName) : ph.closeName;
  770. const children = ph.children.map((n) => n.visit(this, mapper));
  771. return new checker.BlockPlaceholder(ph.name, ph.parameters, startName, closeName, children, ph.sourceSpan, ph.startSourceSpan, ph.endSourceSpan);
  772. }
  773. visitPlaceholder(ph, mapper) {
  774. return new checker.Placeholder(ph.value, mapper.toPublicName(ph.name), ph.sourceSpan);
  775. }
  776. visitIcuPlaceholder(ph, mapper) {
  777. return new checker.IcuPlaceholder(ph.value, mapper.toPublicName(ph.name), ph.sourceSpan);
  778. }
  779. }
  780. function compileClassMetadata(metadata) {
  781. const fnCall = internalCompileClassMetadata(metadata);
  782. return checker.arrowFn([], [checker.devOnlyGuardedExpression(fnCall).toStmt()]).callFn([]);
  783. }
  784. /** Compiles only the `setClassMetadata` call without any additional wrappers. */
  785. function internalCompileClassMetadata(metadata) {
  786. return checker.importExpr(checker.Identifiers.setClassMetadata)
  787. .callFn([
  788. metadata.type,
  789. metadata.decorators,
  790. metadata.ctorParameters ?? checker.literal(null),
  791. metadata.propDecorators ?? checker.literal(null),
  792. ]);
  793. }
  794. /**
  795. * Wraps the `setClassMetadata` function with extra logic that dynamically
  796. * loads dependencies from `@defer` blocks.
  797. *
  798. * Generates a call like this:
  799. * ```ts
  800. * setClassMetadataAsync(type, () => [
  801. * import('./cmp-a').then(m => m.CmpA);
  802. * import('./cmp-b').then(m => m.CmpB);
  803. * ], (CmpA, CmpB) => {
  804. * setClassMetadata(type, decorators, ctorParameters, propParameters);
  805. * });
  806. * ```
  807. *
  808. * Similar to the `setClassMetadata` call, it's wrapped into the `ngDevMode`
  809. * check to tree-shake away this code in production mode.
  810. */
  811. function compileComponentClassMetadata(metadata, dependencies) {
  812. if (dependencies === null || dependencies.length === 0) {
  813. // If there are no deferrable symbols - just generate a regular `setClassMetadata` call.
  814. return compileClassMetadata(metadata);
  815. }
  816. return internalCompileSetClassMetadataAsync(metadata, dependencies.map((dep) => new checker.FnParam(dep.symbolName, checker.DYNAMIC_TYPE)), compileComponentMetadataAsyncResolver(dependencies));
  817. }
  818. /**
  819. * Internal logic used to compile a `setClassMetadataAsync` call.
  820. * @param metadata Class metadata for the internal `setClassMetadata` call.
  821. * @param wrapperParams Parameters to be set on the callback that wraps `setClassMetata`.
  822. * @param dependencyResolverFn Function to resolve the deferred dependencies.
  823. */
  824. function internalCompileSetClassMetadataAsync(metadata, wrapperParams, dependencyResolverFn) {
  825. // Omit the wrapper since it'll be added around `setClassMetadataAsync` instead.
  826. const setClassMetadataCall = internalCompileClassMetadata(metadata);
  827. const setClassMetaWrapper = checker.arrowFn(wrapperParams, [setClassMetadataCall.toStmt()]);
  828. const setClassMetaAsync = checker.importExpr(checker.Identifiers.setClassMetadataAsync)
  829. .callFn([metadata.type, dependencyResolverFn, setClassMetaWrapper]);
  830. return checker.arrowFn([], [checker.devOnlyGuardedExpression(setClassMetaAsync).toStmt()]).callFn([]);
  831. }
  832. /**
  833. * Compiles the function that loads the dependencies for the
  834. * entire component in `setClassMetadataAsync`.
  835. */
  836. function compileComponentMetadataAsyncResolver(dependencies) {
  837. const dynamicImports = dependencies.map(({ symbolName, importPath, isDefaultImport }) => {
  838. // e.g. `(m) => m.CmpA`
  839. const innerFn =
  840. // Default imports are always accessed through the `default` property.
  841. checker.arrowFn([new checker.FnParam('m', checker.DYNAMIC_TYPE)], checker.variable('m').prop(isDefaultImport ? 'default' : symbolName));
  842. // e.g. `import('./cmp-a').then(...)`
  843. return new checker.DynamicImportExpr(importPath).prop('then').callFn([innerFn]);
  844. });
  845. // e.g. `() => [ ... ];`
  846. return checker.arrowFn([], checker.literalArr(dynamicImports));
  847. }
  848. /**
  849. * Generate an ngDevMode guarded call to setClassDebugInfo with the debug info about the class
  850. * (e.g., the file name in which the class is defined)
  851. */
  852. function compileClassDebugInfo(debugInfo) {
  853. const debugInfoObject = {
  854. className: debugInfo.className,
  855. };
  856. // Include file path and line number only if the file relative path is calculated successfully.
  857. if (debugInfo.filePath) {
  858. debugInfoObject.filePath = debugInfo.filePath;
  859. debugInfoObject.lineNumber = debugInfo.lineNumber;
  860. }
  861. // Include forbidOrphanRendering only if it's set to true (to reduce generated code)
  862. if (debugInfo.forbidOrphanRendering) {
  863. debugInfoObject.forbidOrphanRendering = checker.literal(true);
  864. }
  865. const fnCall = checker.importExpr(checker.Identifiers.setClassDebugInfo)
  866. .callFn([debugInfo.type, checker.mapLiteral(debugInfoObject)]);
  867. const iife = checker.arrowFn([], [checker.devOnlyGuardedExpression(fnCall).toStmt()]);
  868. return iife.callFn([]);
  869. }
  870. /*!
  871. * @license
  872. * Copyright Google LLC All Rights Reserved.
  873. *
  874. * Use of this source code is governed by an MIT-style license that can be
  875. * found in the LICENSE file at https://angular.dev/license
  876. */
  877. /**
  878. * Compiles the expression that initializes HMR for a class.
  879. * @param meta HMR metadata extracted from the class.
  880. */
  881. function compileHmrInitializer(meta) {
  882. const moduleName = 'm';
  883. const dataName = 'd';
  884. const timestampName = 't';
  885. const idName = 'id';
  886. const importCallbackName = `${meta.className}_HmrLoad`;
  887. const namespaces = meta.namespaceDependencies.map((dep) => {
  888. return new checker.ExternalExpr({ moduleName: dep.moduleName, name: null });
  889. });
  890. // m.default
  891. const defaultRead = checker.variable(moduleName).prop('default');
  892. // ɵɵreplaceMetadata(Comp, m.default, [...namespaces], [...locals], import.meta, id);
  893. const replaceCall = checker.importExpr(checker.Identifiers.replaceMetadata)
  894. .callFn([
  895. meta.type,
  896. defaultRead,
  897. checker.literalArr(namespaces),
  898. checker.literalArr(meta.localDependencies.map((l) => l.runtimeRepresentation)),
  899. checker.variable('import').prop('meta'),
  900. checker.variable(idName),
  901. ]);
  902. // (m) => m.default && ɵɵreplaceMetadata(...)
  903. const replaceCallback = checker.arrowFn([new checker.FnParam(moduleName)], defaultRead.and(replaceCall));
  904. // getReplaceMetadataURL(id, timestamp, import.meta.url)
  905. const url = checker.importExpr(checker.Identifiers.getReplaceMetadataURL)
  906. .callFn([
  907. checker.variable(idName),
  908. checker.variable(timestampName),
  909. checker.variable('import').prop('meta').prop('url'),
  910. ]);
  911. // function Cmp_HmrLoad(t) {
  912. // import(/* @vite-ignore */ url).then((m) => m.default && replaceMetadata(...));
  913. // }
  914. const importCallback = new checker.DeclareFunctionStmt(importCallbackName, [new checker.FnParam(timestampName)], [
  915. // The vite-ignore special comment is required to prevent Vite from generating a superfluous
  916. // warning for each usage within the development code. If Vite provides a method to
  917. // programmatically avoid this warning in the future, this added comment can be removed here.
  918. new checker.DynamicImportExpr(url, null, '@vite-ignore')
  919. .prop('then')
  920. .callFn([replaceCallback])
  921. .toStmt(),
  922. ], null, checker.StmtModifier.Final);
  923. // (d) => d.id === id && Cmp_HmrLoad(d.timestamp)
  924. const updateCallback = checker.arrowFn([new checker.FnParam(dataName)], checker.variable(dataName)
  925. .prop('id')
  926. .identical(checker.variable(idName))
  927. .and(checker.variable(importCallbackName).callFn([checker.variable(dataName).prop('timestamp')])));
  928. // Cmp_HmrLoad(Date.now());
  929. // Initial call to kick off the loading in order to avoid edge cases with components
  930. // coming from lazy chunks that change before the chunk has loaded.
  931. const initialCall = checker.variable(importCallbackName)
  932. .callFn([checker.variable('Date').prop('now').callFn([])]);
  933. // import.meta.hot
  934. const hotRead = checker.variable('import').prop('meta').prop('hot');
  935. // import.meta.hot.on('angular:component-update', () => ...);
  936. const hotListener = hotRead
  937. .clone()
  938. .prop('on')
  939. .callFn([checker.literal('angular:component-update'), updateCallback]);
  940. return checker.arrowFn([], [
  941. // const id = <id>;
  942. new checker.DeclareVarStmt(idName, checker.literal(encodeURIComponent(`${meta.filePath}@${meta.className}`)), null, checker.StmtModifier.Final),
  943. // function Cmp_HmrLoad() {...}.
  944. importCallback,
  945. // ngDevMode && Cmp_HmrLoad(Date.now());
  946. checker.devOnlyGuardedExpression(initialCall).toStmt(),
  947. // ngDevMode && import.meta.hot && import.meta.hot.on(...)
  948. checker.devOnlyGuardedExpression(hotRead.and(hotListener)).toStmt(),
  949. ])
  950. .callFn([]);
  951. }
  952. /**
  953. * Compiles the HMR update callback for a class.
  954. * @param definitions Compiled definitions for the class (e.g. `defineComponent` calls).
  955. * @param constantStatements Supporting constants statements that were generated alongside
  956. * the definition.
  957. * @param meta HMR metadata extracted from the class.
  958. */
  959. function compileHmrUpdateCallback(definitions, constantStatements, meta) {
  960. const namespaces = 'ɵɵnamespaces';
  961. const params = [meta.className, namespaces].map((name) => new checker.FnParam(name, checker.DYNAMIC_TYPE));
  962. const body = [];
  963. for (const local of meta.localDependencies) {
  964. params.push(new checker.FnParam(local.name));
  965. }
  966. // Declare variables that read out the individual namespaces.
  967. for (let i = 0; i < meta.namespaceDependencies.length; i++) {
  968. body.push(new checker.DeclareVarStmt(meta.namespaceDependencies[i].assignedName, checker.variable(namespaces).key(checker.literal(i)), checker.DYNAMIC_TYPE, checker.StmtModifier.Final));
  969. }
  970. body.push(...constantStatements);
  971. for (const field of definitions) {
  972. if (field.initializer !== null) {
  973. body.push(checker.variable(meta.className).prop(field.name).set(field.initializer).toStmt());
  974. for (const stmt of field.statements) {
  975. body.push(stmt);
  976. }
  977. }
  978. }
  979. return new checker.DeclareFunctionStmt(`${meta.className}_UpdateMetadata`, params, body, null, checker.StmtModifier.Final);
  980. }
  981. /**
  982. * Every time we make a breaking change to the declaration interface or partial-linker behavior, we
  983. * must update this constant to prevent old partial-linkers from incorrectly processing the
  984. * declaration.
  985. *
  986. * Do not include any prerelease in these versions as they are ignored.
  987. */
  988. const MINIMUM_PARTIAL_LINKER_VERSION$5 = '12.0.0';
  989. /**
  990. * Minimum version at which deferred blocks are supported in the linker.
  991. */
  992. const MINIMUM_PARTIAL_LINKER_DEFER_SUPPORT_VERSION = '18.0.0';
  993. function compileDeclareClassMetadata(metadata) {
  994. const definitionMap = new checker.DefinitionMap();
  995. definitionMap.set('minVersion', checker.literal(MINIMUM_PARTIAL_LINKER_VERSION$5));
  996. definitionMap.set('version', checker.literal('19.2.13'));
  997. definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
  998. definitionMap.set('type', metadata.type);
  999. definitionMap.set('decorators', metadata.decorators);
  1000. definitionMap.set('ctorParameters', metadata.ctorParameters);
  1001. definitionMap.set('propDecorators', metadata.propDecorators);
  1002. return checker.importExpr(checker.Identifiers.declareClassMetadata).callFn([definitionMap.toLiteralMap()]);
  1003. }
  1004. function compileComponentDeclareClassMetadata(metadata, dependencies) {
  1005. if (dependencies === null || dependencies.length === 0) {
  1006. return compileDeclareClassMetadata(metadata);
  1007. }
  1008. const definitionMap = new checker.DefinitionMap();
  1009. const callbackReturnDefinitionMap = new checker.DefinitionMap();
  1010. callbackReturnDefinitionMap.set('decorators', metadata.decorators);
  1011. callbackReturnDefinitionMap.set('ctorParameters', metadata.ctorParameters ?? checker.literal(null));
  1012. callbackReturnDefinitionMap.set('propDecorators', metadata.propDecorators ?? checker.literal(null));
  1013. definitionMap.set('minVersion', checker.literal(MINIMUM_PARTIAL_LINKER_DEFER_SUPPORT_VERSION));
  1014. definitionMap.set('version', checker.literal('19.2.13'));
  1015. definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
  1016. definitionMap.set('type', metadata.type);
  1017. definitionMap.set('resolveDeferredDeps', compileComponentMetadataAsyncResolver(dependencies));
  1018. definitionMap.set('resolveMetadata', checker.arrowFn(dependencies.map((dep) => new checker.FnParam(dep.symbolName, checker.DYNAMIC_TYPE)), callbackReturnDefinitionMap.toLiteralMap()));
  1019. return checker.importExpr(checker.Identifiers.declareClassMetadataAsync).callFn([definitionMap.toLiteralMap()]);
  1020. }
  1021. /**
  1022. * Creates an array literal expression from the given array, mapping all values to an expression
  1023. * using the provided mapping function. If the array is empty or null, then null is returned.
  1024. *
  1025. * @param values The array to transfer into literal array expression.
  1026. * @param mapper The logic to use for creating an expression for the array's values.
  1027. * @returns An array literal expression representing `values`, or null if `values` is empty or
  1028. * is itself null.
  1029. */
  1030. function toOptionalLiteralArray(values, mapper) {
  1031. if (values === null || values.length === 0) {
  1032. return null;
  1033. }
  1034. return checker.literalArr(values.map((value) => mapper(value)));
  1035. }
  1036. /**
  1037. * Creates an object literal expression from the given object, mapping all values to an expression
  1038. * using the provided mapping function. If the object has no keys, then null is returned.
  1039. *
  1040. * @param object The object to transfer into an object literal expression.
  1041. * @param mapper The logic to use for creating an expression for the object's values.
  1042. * @returns An object literal expression representing `object`, or null if `object` does not have
  1043. * any keys.
  1044. */
  1045. function toOptionalLiteralMap(object, mapper) {
  1046. const entries = Object.keys(object).map((key) => {
  1047. const value = object[key];
  1048. return { key, value: mapper(value), quoted: true };
  1049. });
  1050. if (entries.length > 0) {
  1051. return checker.literalMap(entries);
  1052. }
  1053. else {
  1054. return null;
  1055. }
  1056. }
  1057. function compileDependencies(deps) {
  1058. if (deps === 'invalid') {
  1059. // The `deps` can be set to the string "invalid" by the `unwrapConstructorDependencies()`
  1060. // function, which tries to convert `ConstructorDeps` into `R3DependencyMetadata[]`.
  1061. return checker.literal('invalid');
  1062. }
  1063. else if (deps === null) {
  1064. return checker.literal(null);
  1065. }
  1066. else {
  1067. return checker.literalArr(deps.map(compileDependency));
  1068. }
  1069. }
  1070. function compileDependency(dep) {
  1071. const depMeta = new checker.DefinitionMap();
  1072. depMeta.set('token', dep.token);
  1073. if (dep.attributeNameType !== null) {
  1074. depMeta.set('attribute', checker.literal(true));
  1075. }
  1076. if (dep.host) {
  1077. depMeta.set('host', checker.literal(true));
  1078. }
  1079. if (dep.optional) {
  1080. depMeta.set('optional', checker.literal(true));
  1081. }
  1082. if (dep.self) {
  1083. depMeta.set('self', checker.literal(true));
  1084. }
  1085. if (dep.skipSelf) {
  1086. depMeta.set('skipSelf', checker.literal(true));
  1087. }
  1088. return depMeta.toLiteralMap();
  1089. }
  1090. /**
  1091. * Compile a directive declaration defined by the `R3DirectiveMetadata`.
  1092. */
  1093. function compileDeclareDirectiveFromMetadata(meta) {
  1094. const definitionMap = createDirectiveDefinitionMap(meta);
  1095. const expression = checker.importExpr(checker.Identifiers.declareDirective).callFn([definitionMap.toLiteralMap()]);
  1096. const type = checker.createDirectiveType(meta);
  1097. return { expression, type, statements: [] };
  1098. }
  1099. /**
  1100. * Gathers the declaration fields for a directive into a `DefinitionMap`. This allows for reusing
  1101. * this logic for components, as they extend the directive metadata.
  1102. */
  1103. function createDirectiveDefinitionMap(meta) {
  1104. const definitionMap = new checker.DefinitionMap();
  1105. const minVersion = getMinimumVersionForPartialOutput(meta);
  1106. definitionMap.set('minVersion', checker.literal(minVersion));
  1107. definitionMap.set('version', checker.literal('19.2.13'));
  1108. // e.g. `type: MyDirective`
  1109. definitionMap.set('type', meta.type.value);
  1110. if (meta.isStandalone !== undefined) {
  1111. definitionMap.set('isStandalone', checker.literal(meta.isStandalone));
  1112. }
  1113. if (meta.isSignal) {
  1114. definitionMap.set('isSignal', checker.literal(meta.isSignal));
  1115. }
  1116. // e.g. `selector: 'some-dir'`
  1117. if (meta.selector !== null) {
  1118. definitionMap.set('selector', checker.literal(meta.selector));
  1119. }
  1120. definitionMap.set('inputs', needsNewInputPartialOutput(meta)
  1121. ? createInputsPartialMetadata(meta.inputs)
  1122. : legacyInputsPartialMetadata(meta.inputs));
  1123. definitionMap.set('outputs', checker.conditionallyCreateDirectiveBindingLiteral(meta.outputs));
  1124. definitionMap.set('host', compileHostMetadata(meta.host));
  1125. definitionMap.set('providers', meta.providers);
  1126. if (meta.queries.length > 0) {
  1127. definitionMap.set('queries', checker.literalArr(meta.queries.map(compileQuery)));
  1128. }
  1129. if (meta.viewQueries.length > 0) {
  1130. definitionMap.set('viewQueries', checker.literalArr(meta.viewQueries.map(compileQuery)));
  1131. }
  1132. if (meta.exportAs !== null) {
  1133. definitionMap.set('exportAs', checker.asLiteral(meta.exportAs));
  1134. }
  1135. if (meta.usesInheritance) {
  1136. definitionMap.set('usesInheritance', checker.literal(true));
  1137. }
  1138. if (meta.lifecycle.usesOnChanges) {
  1139. definitionMap.set('usesOnChanges', checker.literal(true));
  1140. }
  1141. if (meta.hostDirectives?.length) {
  1142. definitionMap.set('hostDirectives', createHostDirectives(meta.hostDirectives));
  1143. }
  1144. definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
  1145. return definitionMap;
  1146. }
  1147. /**
  1148. * Determines the minimum linker version for the partial output
  1149. * generated for this directive.
  1150. *
  1151. * Every time we make a breaking change to the declaration interface or partial-linker
  1152. * behavior, we must update the minimum versions to prevent old partial-linkers from
  1153. * incorrectly processing the declaration.
  1154. *
  1155. * NOTE: Do not include any prerelease in these versions as they are ignored.
  1156. */
  1157. function getMinimumVersionForPartialOutput(meta) {
  1158. // We are starting with the oldest minimum version that can work for common
  1159. // directive partial compilation output. As we discover usages of new features
  1160. // that require a newer partial output emit, we bump the `minVersion`. Our goal
  1161. // is to keep libraries as much compatible with older linker versions as possible.
  1162. let minVersion = '14.0.0';
  1163. // Note: in order to allow consuming Angular libraries that have been compiled with 16.1+ in
  1164. // Angular 16.0, we only force a minimum version of 16.1 if input transform feature as introduced
  1165. // in 16.1 is actually used.
  1166. const hasDecoratorTransformFunctions = Object.values(meta.inputs).some((input) => input.transformFunction !== null);
  1167. if (hasDecoratorTransformFunctions) {
  1168. minVersion = '16.1.0';
  1169. }
  1170. // If there are input flags and we need the new emit, use the actual minimum version,
  1171. // where this was introduced. i.e. in 17.1.0
  1172. // TODO(legacy-partial-output-inputs): Remove in v18.
  1173. if (needsNewInputPartialOutput(meta)) {
  1174. minVersion = '17.1.0';
  1175. }
  1176. // If there are signal-based queries, partial output generates an extra field
  1177. // that should be parsed by linkers. Ensure a proper minimum linker version.
  1178. if (meta.queries.some((q) => q.isSignal) || meta.viewQueries.some((q) => q.isSignal)) {
  1179. minVersion = '17.2.0';
  1180. }
  1181. return minVersion;
  1182. }
  1183. /**
  1184. * Gets whether the given directive needs the new input partial output structure
  1185. * that can hold additional metadata like `isRequired`, `isSignal` etc.
  1186. */
  1187. function needsNewInputPartialOutput(meta) {
  1188. return Object.values(meta.inputs).some((input) => input.isSignal);
  1189. }
  1190. /**
  1191. * Compiles the metadata of a single query into its partial declaration form as declared
  1192. * by `R3DeclareQueryMetadata`.
  1193. */
  1194. function compileQuery(query) {
  1195. const meta = new checker.DefinitionMap();
  1196. meta.set('propertyName', checker.literal(query.propertyName));
  1197. if (query.first) {
  1198. meta.set('first', checker.literal(true));
  1199. }
  1200. meta.set('predicate', Array.isArray(query.predicate)
  1201. ? checker.asLiteral(query.predicate)
  1202. : checker.convertFromMaybeForwardRefExpression(query.predicate));
  1203. if (!query.emitDistinctChangesOnly) {
  1204. // `emitDistinctChangesOnly` is special because we expect it to be `true`.
  1205. // Therefore we explicitly emit the field, and explicitly place it only when it's `false`.
  1206. meta.set('emitDistinctChangesOnly', checker.literal(false));
  1207. }
  1208. if (query.descendants) {
  1209. meta.set('descendants', checker.literal(true));
  1210. }
  1211. meta.set('read', query.read);
  1212. if (query.static) {
  1213. meta.set('static', checker.literal(true));
  1214. }
  1215. if (query.isSignal) {
  1216. meta.set('isSignal', checker.literal(true));
  1217. }
  1218. return meta.toLiteralMap();
  1219. }
  1220. /**
  1221. * Compiles the host metadata into its partial declaration form as declared
  1222. * in `R3DeclareDirectiveMetadata['host']`
  1223. */
  1224. function compileHostMetadata(meta) {
  1225. const hostMetadata = new checker.DefinitionMap();
  1226. hostMetadata.set('attributes', toOptionalLiteralMap(meta.attributes, (expression) => expression));
  1227. hostMetadata.set('listeners', toOptionalLiteralMap(meta.listeners, checker.literal));
  1228. hostMetadata.set('properties', toOptionalLiteralMap(meta.properties, checker.literal));
  1229. if (meta.specialAttributes.styleAttr) {
  1230. hostMetadata.set('styleAttribute', checker.literal(meta.specialAttributes.styleAttr));
  1231. }
  1232. if (meta.specialAttributes.classAttr) {
  1233. hostMetadata.set('classAttribute', checker.literal(meta.specialAttributes.classAttr));
  1234. }
  1235. if (hostMetadata.values.length > 0) {
  1236. return hostMetadata.toLiteralMap();
  1237. }
  1238. else {
  1239. return null;
  1240. }
  1241. }
  1242. function createHostDirectives(hostDirectives) {
  1243. const expressions = hostDirectives.map((current) => {
  1244. const keys = [
  1245. {
  1246. key: 'directive',
  1247. value: current.isForwardReference
  1248. ? checker.generateForwardRef(current.directive.type)
  1249. : current.directive.type,
  1250. quoted: false,
  1251. },
  1252. ];
  1253. const inputsLiteral = current.inputs ? checker.createHostDirectivesMappingArray(current.inputs) : null;
  1254. const outputsLiteral = current.outputs
  1255. ? checker.createHostDirectivesMappingArray(current.outputs)
  1256. : null;
  1257. if (inputsLiteral) {
  1258. keys.push({ key: 'inputs', value: inputsLiteral, quoted: false });
  1259. }
  1260. if (outputsLiteral) {
  1261. keys.push({ key: 'outputs', value: outputsLiteral, quoted: false });
  1262. }
  1263. return checker.literalMap(keys);
  1264. });
  1265. // If there's a forward reference, we generate a `function() { return [{directive: HostDir}] }`,
  1266. // otherwise we can save some bytes by using a plain array, e.g. `[{directive: HostDir}]`.
  1267. return checker.literalArr(expressions);
  1268. }
  1269. /**
  1270. * Generates partial output metadata for inputs of a directive.
  1271. *
  1272. * The generated structure is expected to match `R3DeclareDirectiveFacade['inputs']`.
  1273. */
  1274. function createInputsPartialMetadata(inputs) {
  1275. const keys = Object.getOwnPropertyNames(inputs);
  1276. if (keys.length === 0) {
  1277. return null;
  1278. }
  1279. return checker.literalMap(keys.map((declaredName) => {
  1280. const value = inputs[declaredName];
  1281. return {
  1282. key: declaredName,
  1283. // put quotes around keys that contain potentially unsafe characters
  1284. quoted: checker.UNSAFE_OBJECT_KEY_NAME_REGEXP.test(declaredName),
  1285. value: checker.literalMap([
  1286. { key: 'classPropertyName', quoted: false, value: checker.asLiteral(value.classPropertyName) },
  1287. { key: 'publicName', quoted: false, value: checker.asLiteral(value.bindingPropertyName) },
  1288. { key: 'isSignal', quoted: false, value: checker.asLiteral(value.isSignal) },
  1289. { key: 'isRequired', quoted: false, value: checker.asLiteral(value.required) },
  1290. { key: 'transformFunction', quoted: false, value: value.transformFunction ?? checker.NULL_EXPR },
  1291. ]),
  1292. };
  1293. }));
  1294. }
  1295. /**
  1296. * Pre v18 legacy partial output for inputs.
  1297. *
  1298. * Previously, inputs did not capture metadata like `isSignal` in the partial compilation output.
  1299. * To enable capturing such metadata, we restructured how input metadata is communicated in the
  1300. * partial output. This would make libraries incompatible with older Angular FW versions where the
  1301. * linker would not know how to handle this new "format". For this reason, if we know this metadata
  1302. * does not need to be captured- we fall back to the old format. This is what this function
  1303. * generates.
  1304. *
  1305. * See:
  1306. * https://github.com/angular/angular/blob/d4b423690210872b5c32a322a6090beda30b05a3/packages/core/src/compiler/compiler_facade_interface.ts#L197-L199
  1307. */
  1308. function legacyInputsPartialMetadata(inputs) {
  1309. // TODO(legacy-partial-output-inputs): Remove function in v18.
  1310. const keys = Object.getOwnPropertyNames(inputs);
  1311. if (keys.length === 0) {
  1312. return null;
  1313. }
  1314. return checker.literalMap(keys.map((declaredName) => {
  1315. const value = inputs[declaredName];
  1316. const publicName = value.bindingPropertyName;
  1317. const differentDeclaringName = publicName !== declaredName;
  1318. let result;
  1319. if (differentDeclaringName || value.transformFunction !== null) {
  1320. const values = [checker.asLiteral(publicName), checker.asLiteral(declaredName)];
  1321. if (value.transformFunction !== null) {
  1322. values.push(value.transformFunction);
  1323. }
  1324. result = checker.literalArr(values);
  1325. }
  1326. else {
  1327. result = checker.asLiteral(publicName);
  1328. }
  1329. return {
  1330. key: declaredName,
  1331. // put quotes around keys that contain potentially unsafe characters
  1332. quoted: checker.UNSAFE_OBJECT_KEY_NAME_REGEXP.test(declaredName),
  1333. value: result,
  1334. };
  1335. }));
  1336. }
  1337. /**
  1338. * Compile a component declaration defined by the `R3ComponentMetadata`.
  1339. */
  1340. function compileDeclareComponentFromMetadata(meta, template, additionalTemplateInfo) {
  1341. const definitionMap = createComponentDefinitionMap(meta, template, additionalTemplateInfo);
  1342. const expression = checker.importExpr(checker.Identifiers.declareComponent).callFn([definitionMap.toLiteralMap()]);
  1343. const type = checker.createComponentType(meta);
  1344. return { expression, type, statements: [] };
  1345. }
  1346. /**
  1347. * Gathers the declaration fields for a component into a `DefinitionMap`.
  1348. */
  1349. function createComponentDefinitionMap(meta, template, templateInfo) {
  1350. const definitionMap = createDirectiveDefinitionMap(meta);
  1351. const blockVisitor = new BlockPresenceVisitor();
  1352. checker.visitAll$1(blockVisitor, template.nodes);
  1353. definitionMap.set('template', getTemplateExpression(template, templateInfo));
  1354. if (templateInfo.isInline) {
  1355. definitionMap.set('isInline', checker.literal(true));
  1356. }
  1357. // Set the minVersion to 17.0.0 if the component is using at least one block in its template.
  1358. // We don't do this for templates without blocks, in order to preserve backwards compatibility.
  1359. if (blockVisitor.hasBlocks) {
  1360. definitionMap.set('minVersion', checker.literal('17.0.0'));
  1361. }
  1362. definitionMap.set('styles', toOptionalLiteralArray(meta.styles, checker.literal));
  1363. definitionMap.set('dependencies', compileUsedDependenciesMetadata(meta));
  1364. definitionMap.set('viewProviders', meta.viewProviders);
  1365. definitionMap.set('animations', meta.animations);
  1366. if (meta.changeDetection !== null) {
  1367. if (typeof meta.changeDetection === 'object') {
  1368. throw new Error('Impossible state! Change detection flag is not resolved!');
  1369. }
  1370. definitionMap.set('changeDetection', checker.importExpr(checker.Identifiers.ChangeDetectionStrategy)
  1371. .prop(checker.ChangeDetectionStrategy[meta.changeDetection]));
  1372. }
  1373. if (meta.encapsulation !== checker.ViewEncapsulation.Emulated) {
  1374. definitionMap.set('encapsulation', checker.importExpr(checker.Identifiers.ViewEncapsulation).prop(checker.ViewEncapsulation[meta.encapsulation]));
  1375. }
  1376. if (meta.interpolation !== checker.DEFAULT_INTERPOLATION_CONFIG) {
  1377. definitionMap.set('interpolation', checker.literalArr([checker.literal(meta.interpolation.start), checker.literal(meta.interpolation.end)]));
  1378. }
  1379. if (template.preserveWhitespaces === true) {
  1380. definitionMap.set('preserveWhitespaces', checker.literal(true));
  1381. }
  1382. if (meta.defer.mode === 0 /* DeferBlockDepsEmitMode.PerBlock */) {
  1383. const resolvers = [];
  1384. let hasResolvers = false;
  1385. for (const deps of meta.defer.blocks.values()) {
  1386. // Note: we need to push a `null` even if there are no dependencies, because matching of
  1387. // defer resolver functions to defer blocks happens by index and not adding an array
  1388. // entry for a block can throw off the blocks coming after it.
  1389. if (deps === null) {
  1390. resolvers.push(checker.literal(null));
  1391. }
  1392. else {
  1393. resolvers.push(deps);
  1394. hasResolvers = true;
  1395. }
  1396. }
  1397. // If *all* the resolvers are null, we can skip the field.
  1398. if (hasResolvers) {
  1399. definitionMap.set('deferBlockDependencies', checker.literalArr(resolvers));
  1400. }
  1401. }
  1402. else {
  1403. throw new Error('Unsupported defer function emit mode in partial compilation');
  1404. }
  1405. return definitionMap;
  1406. }
  1407. function getTemplateExpression(template, templateInfo) {
  1408. // If the template has been defined using a direct literal, we use that expression directly
  1409. // without any modifications. This is ensures proper source mapping from the partially
  1410. // compiled code to the source file declaring the template. Note that this does not capture
  1411. // template literals referenced indirectly through an identifier.
  1412. if (templateInfo.inlineTemplateLiteralExpression !== null) {
  1413. return templateInfo.inlineTemplateLiteralExpression;
  1414. }
  1415. // If the template is defined inline but not through a literal, the template has been resolved
  1416. // through static interpretation. We create a literal but cannot provide any source span. Note
  1417. // that we cannot use the expression defining the template because the linker expects the template
  1418. // to be defined as a literal in the declaration.
  1419. if (templateInfo.isInline) {
  1420. return checker.literal(templateInfo.content, null, null);
  1421. }
  1422. // The template is external so we must synthesize an expression node with
  1423. // the appropriate source-span.
  1424. const contents = templateInfo.content;
  1425. const file = new checker.ParseSourceFile(contents, templateInfo.sourceUrl);
  1426. const start = new checker.ParseLocation(file, 0, 0, 0);
  1427. const end = computeEndLocation(file, contents);
  1428. const span = new checker.ParseSourceSpan(start, end);
  1429. return checker.literal(contents, null, span);
  1430. }
  1431. function computeEndLocation(file, contents) {
  1432. const length = contents.length;
  1433. let lineStart = 0;
  1434. let lastLineStart = 0;
  1435. let line = 0;
  1436. do {
  1437. lineStart = contents.indexOf('\n', lastLineStart);
  1438. if (lineStart !== -1) {
  1439. lastLineStart = lineStart + 1;
  1440. line++;
  1441. }
  1442. } while (lineStart !== -1);
  1443. return new checker.ParseLocation(file, length, line, length - lastLineStart);
  1444. }
  1445. function compileUsedDependenciesMetadata(meta) {
  1446. const wrapType = meta.declarationListEmitMode !== 0 /* DeclarationListEmitMode.Direct */
  1447. ? checker.generateForwardRef
  1448. : (expr) => expr;
  1449. if (meta.declarationListEmitMode === 3 /* DeclarationListEmitMode.RuntimeResolved */) {
  1450. throw new Error(`Unsupported emit mode`);
  1451. }
  1452. return toOptionalLiteralArray(meta.declarations, (decl) => {
  1453. switch (decl.kind) {
  1454. case checker.R3TemplateDependencyKind.Directive:
  1455. const dirMeta = new checker.DefinitionMap();
  1456. dirMeta.set('kind', checker.literal(decl.isComponent ? 'component' : 'directive'));
  1457. dirMeta.set('type', wrapType(decl.type));
  1458. dirMeta.set('selector', checker.literal(decl.selector));
  1459. dirMeta.set('inputs', toOptionalLiteralArray(decl.inputs, checker.literal));
  1460. dirMeta.set('outputs', toOptionalLiteralArray(decl.outputs, checker.literal));
  1461. dirMeta.set('exportAs', toOptionalLiteralArray(decl.exportAs, checker.literal));
  1462. return dirMeta.toLiteralMap();
  1463. case checker.R3TemplateDependencyKind.Pipe:
  1464. const pipeMeta = new checker.DefinitionMap();
  1465. pipeMeta.set('kind', checker.literal('pipe'));
  1466. pipeMeta.set('type', wrapType(decl.type));
  1467. pipeMeta.set('name', checker.literal(decl.name));
  1468. return pipeMeta.toLiteralMap();
  1469. case checker.R3TemplateDependencyKind.NgModule:
  1470. const ngModuleMeta = new checker.DefinitionMap();
  1471. ngModuleMeta.set('kind', checker.literal('ngmodule'));
  1472. ngModuleMeta.set('type', wrapType(decl.type));
  1473. return ngModuleMeta.toLiteralMap();
  1474. }
  1475. });
  1476. }
  1477. class BlockPresenceVisitor extends checker.RecursiveVisitor$1 {
  1478. hasBlocks = false;
  1479. visitDeferredBlock() {
  1480. this.hasBlocks = true;
  1481. }
  1482. visitDeferredBlockPlaceholder() {
  1483. this.hasBlocks = true;
  1484. }
  1485. visitDeferredBlockLoading() {
  1486. this.hasBlocks = true;
  1487. }
  1488. visitDeferredBlockError() {
  1489. this.hasBlocks = true;
  1490. }
  1491. visitIfBlock() {
  1492. this.hasBlocks = true;
  1493. }
  1494. visitIfBlockBranch() {
  1495. this.hasBlocks = true;
  1496. }
  1497. visitForLoopBlock() {
  1498. this.hasBlocks = true;
  1499. }
  1500. visitForLoopBlockEmpty() {
  1501. this.hasBlocks = true;
  1502. }
  1503. visitSwitchBlock() {
  1504. this.hasBlocks = true;
  1505. }
  1506. visitSwitchBlockCase() {
  1507. this.hasBlocks = true;
  1508. }
  1509. }
  1510. /**
  1511. * Every time we make a breaking change to the declaration interface or partial-linker behavior, we
  1512. * must update this constant to prevent old partial-linkers from incorrectly processing the
  1513. * declaration.
  1514. *
  1515. * Do not include any prerelease in these versions as they are ignored.
  1516. */
  1517. const MINIMUM_PARTIAL_LINKER_VERSION$4 = '12.0.0';
  1518. function compileDeclareFactoryFunction(meta) {
  1519. const definitionMap = new checker.DefinitionMap();
  1520. definitionMap.set('minVersion', checker.literal(MINIMUM_PARTIAL_LINKER_VERSION$4));
  1521. definitionMap.set('version', checker.literal('19.2.13'));
  1522. definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
  1523. definitionMap.set('type', meta.type.value);
  1524. definitionMap.set('deps', compileDependencies(meta.deps));
  1525. definitionMap.set('target', checker.importExpr(checker.Identifiers.FactoryTarget).prop(checker.FactoryTarget[meta.target]));
  1526. return {
  1527. expression: checker.importExpr(checker.Identifiers.declareFactory).callFn([definitionMap.toLiteralMap()]),
  1528. statements: [],
  1529. type: checker.createFactoryType(meta),
  1530. };
  1531. }
  1532. /**
  1533. * Every time we make a breaking change to the declaration interface or partial-linker behavior, we
  1534. * must update this constant to prevent old partial-linkers from incorrectly processing the
  1535. * declaration.
  1536. *
  1537. * Do not include any prerelease in these versions as they are ignored.
  1538. */
  1539. const MINIMUM_PARTIAL_LINKER_VERSION$3 = '12.0.0';
  1540. /**
  1541. * Compile a Injectable declaration defined by the `R3InjectableMetadata`.
  1542. */
  1543. function compileDeclareInjectableFromMetadata(meta) {
  1544. const definitionMap = createInjectableDefinitionMap(meta);
  1545. const expression = checker.importExpr(checker.Identifiers.declareInjectable).callFn([definitionMap.toLiteralMap()]);
  1546. const type = checker.createInjectableType(meta);
  1547. return { expression, type, statements: [] };
  1548. }
  1549. /**
  1550. * Gathers the declaration fields for a Injectable into a `DefinitionMap`.
  1551. */
  1552. function createInjectableDefinitionMap(meta) {
  1553. const definitionMap = new checker.DefinitionMap();
  1554. definitionMap.set('minVersion', checker.literal(MINIMUM_PARTIAL_LINKER_VERSION$3));
  1555. definitionMap.set('version', checker.literal('19.2.13'));
  1556. definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
  1557. definitionMap.set('type', meta.type.value);
  1558. // Only generate providedIn property if it has a non-null value
  1559. if (meta.providedIn !== undefined) {
  1560. const providedIn = checker.convertFromMaybeForwardRefExpression(meta.providedIn);
  1561. if (providedIn.value !== null) {
  1562. definitionMap.set('providedIn', providedIn);
  1563. }
  1564. }
  1565. if (meta.useClass !== undefined) {
  1566. definitionMap.set('useClass', checker.convertFromMaybeForwardRefExpression(meta.useClass));
  1567. }
  1568. if (meta.useExisting !== undefined) {
  1569. definitionMap.set('useExisting', checker.convertFromMaybeForwardRefExpression(meta.useExisting));
  1570. }
  1571. if (meta.useValue !== undefined) {
  1572. definitionMap.set('useValue', checker.convertFromMaybeForwardRefExpression(meta.useValue));
  1573. }
  1574. // Factories do not contain `ForwardRef`s since any types are already wrapped in a function call
  1575. // so the types will not be eagerly evaluated. Therefore we do not need to process this expression
  1576. // with `convertFromProviderExpression()`.
  1577. if (meta.useFactory !== undefined) {
  1578. definitionMap.set('useFactory', meta.useFactory);
  1579. }
  1580. if (meta.deps !== undefined) {
  1581. definitionMap.set('deps', checker.literalArr(meta.deps.map(compileDependency)));
  1582. }
  1583. return definitionMap;
  1584. }
  1585. /**
  1586. * Every time we make a breaking change to the declaration interface or partial-linker behavior, we
  1587. * must update this constant to prevent old partial-linkers from incorrectly processing the
  1588. * declaration.
  1589. *
  1590. * Do not include any prerelease in these versions as they are ignored.
  1591. */
  1592. const MINIMUM_PARTIAL_LINKER_VERSION$2 = '12.0.0';
  1593. function compileDeclareInjectorFromMetadata(meta) {
  1594. const definitionMap = createInjectorDefinitionMap(meta);
  1595. const expression = checker.importExpr(checker.Identifiers.declareInjector).callFn([definitionMap.toLiteralMap()]);
  1596. const type = checker.createInjectorType(meta);
  1597. return { expression, type, statements: [] };
  1598. }
  1599. /**
  1600. * Gathers the declaration fields for an Injector into a `DefinitionMap`.
  1601. */
  1602. function createInjectorDefinitionMap(meta) {
  1603. const definitionMap = new checker.DefinitionMap();
  1604. definitionMap.set('minVersion', checker.literal(MINIMUM_PARTIAL_LINKER_VERSION$2));
  1605. definitionMap.set('version', checker.literal('19.2.13'));
  1606. definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
  1607. definitionMap.set('type', meta.type.value);
  1608. definitionMap.set('providers', meta.providers);
  1609. if (meta.imports.length > 0) {
  1610. definitionMap.set('imports', checker.literalArr(meta.imports));
  1611. }
  1612. return definitionMap;
  1613. }
  1614. /**
  1615. * Every time we make a breaking change to the declaration interface or partial-linker behavior, we
  1616. * must update this constant to prevent old partial-linkers from incorrectly processing the
  1617. * declaration.
  1618. *
  1619. * Do not include any prerelease in these versions as they are ignored.
  1620. */
  1621. const MINIMUM_PARTIAL_LINKER_VERSION$1 = '14.0.0';
  1622. function compileDeclareNgModuleFromMetadata(meta) {
  1623. const definitionMap = createNgModuleDefinitionMap(meta);
  1624. const expression = checker.importExpr(checker.Identifiers.declareNgModule).callFn([definitionMap.toLiteralMap()]);
  1625. const type = checker.createNgModuleType(meta);
  1626. return { expression, type, statements: [] };
  1627. }
  1628. /**
  1629. * Gathers the declaration fields for an NgModule into a `DefinitionMap`.
  1630. */
  1631. function createNgModuleDefinitionMap(meta) {
  1632. const definitionMap = new checker.DefinitionMap();
  1633. if (meta.kind === checker.R3NgModuleMetadataKind.Local) {
  1634. throw new Error('Invalid path! Local compilation mode should not get into the partial compilation path');
  1635. }
  1636. definitionMap.set('minVersion', checker.literal(MINIMUM_PARTIAL_LINKER_VERSION$1));
  1637. definitionMap.set('version', checker.literal('19.2.13'));
  1638. definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
  1639. definitionMap.set('type', meta.type.value);
  1640. // We only generate the keys in the metadata if the arrays contain values.
  1641. // We must wrap the arrays inside a function if any of the values are a forward reference to a
  1642. // not-yet-declared class. This is to support JIT execution of the `ɵɵngDeclareNgModule()` call.
  1643. // In the linker these wrappers are stripped and then reapplied for the `ɵɵdefineNgModule()` call.
  1644. if (meta.bootstrap.length > 0) {
  1645. definitionMap.set('bootstrap', checker.refsToArray(meta.bootstrap, meta.containsForwardDecls));
  1646. }
  1647. if (meta.declarations.length > 0) {
  1648. definitionMap.set('declarations', checker.refsToArray(meta.declarations, meta.containsForwardDecls));
  1649. }
  1650. if (meta.imports.length > 0) {
  1651. definitionMap.set('imports', checker.refsToArray(meta.imports, meta.containsForwardDecls));
  1652. }
  1653. if (meta.exports.length > 0) {
  1654. definitionMap.set('exports', checker.refsToArray(meta.exports, meta.containsForwardDecls));
  1655. }
  1656. if (meta.schemas !== null && meta.schemas.length > 0) {
  1657. definitionMap.set('schemas', checker.literalArr(meta.schemas.map((ref) => ref.value)));
  1658. }
  1659. if (meta.id !== null) {
  1660. definitionMap.set('id', meta.id);
  1661. }
  1662. return definitionMap;
  1663. }
  1664. /**
  1665. * Every time we make a breaking change to the declaration interface or partial-linker behavior, we
  1666. * must update this constant to prevent old partial-linkers from incorrectly processing the
  1667. * declaration.
  1668. *
  1669. * Do not include any prerelease in these versions as they are ignored.
  1670. */
  1671. const MINIMUM_PARTIAL_LINKER_VERSION = '14.0.0';
  1672. /**
  1673. * Compile a Pipe declaration defined by the `R3PipeMetadata`.
  1674. */
  1675. function compileDeclarePipeFromMetadata(meta) {
  1676. const definitionMap = createPipeDefinitionMap(meta);
  1677. const expression = checker.importExpr(checker.Identifiers.declarePipe).callFn([definitionMap.toLiteralMap()]);
  1678. const type = checker.createPipeType(meta);
  1679. return { expression, type, statements: [] };
  1680. }
  1681. /**
  1682. * Gathers the declaration fields for a Pipe into a `DefinitionMap`.
  1683. */
  1684. function createPipeDefinitionMap(meta) {
  1685. const definitionMap = new checker.DefinitionMap();
  1686. definitionMap.set('minVersion', checker.literal(MINIMUM_PARTIAL_LINKER_VERSION));
  1687. definitionMap.set('version', checker.literal('19.2.13'));
  1688. definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
  1689. // e.g. `type: MyPipe`
  1690. definitionMap.set('type', meta.type.value);
  1691. if (meta.isStandalone !== undefined) {
  1692. definitionMap.set('isStandalone', checker.literal(meta.isStandalone));
  1693. }
  1694. // e.g. `name: "myPipe"`
  1695. definitionMap.set('name', checker.literal(meta.pipeName));
  1696. if (meta.pure === false) {
  1697. // e.g. `pure: false`
  1698. definitionMap.set('pure', checker.literal(meta.pure));
  1699. }
  1700. return definitionMap;
  1701. }
  1702. /**
  1703. * Base URL for the error details page.
  1704. *
  1705. * Keep the files below in full sync:
  1706. * - packages/compiler-cli/src/ngtsc/diagnostics/src/error_details_base_url.ts
  1707. * - packages/core/src/error_details_base_url.ts
  1708. */
  1709. const ERROR_DETAILS_PAGE_BASE_URL = 'https://angular.dev/errors';
  1710. // Escape anything that isn't alphanumeric, '/' or '_'.
  1711. const CHARS_TO_ESCAPE = /[^a-zA-Z0-9/_]/g;
  1712. /**
  1713. * An `AliasingHost` which generates and consumes alias re-exports when module names for each file
  1714. * are determined by a `UnifiedModulesHost`.
  1715. *
  1716. * When using a `UnifiedModulesHost`, aliasing prevents issues with transitive dependencies. See the
  1717. * README.md for more details.
  1718. */
  1719. class UnifiedModulesAliasingHost {
  1720. unifiedModulesHost;
  1721. constructor(unifiedModulesHost) {
  1722. this.unifiedModulesHost = unifiedModulesHost;
  1723. }
  1724. /**
  1725. * With a `UnifiedModulesHost`, aliases are chosen automatically without the need to look through
  1726. * the exports present in a .d.ts file, so we can avoid cluttering the .d.ts files.
  1727. */
  1728. aliasExportsInDts = false;
  1729. maybeAliasSymbolAs(ref, context, ngModuleName, isReExport) {
  1730. if (!isReExport) {
  1731. // Aliasing is used with a UnifiedModulesHost to prevent transitive dependencies. Thus,
  1732. // aliases
  1733. // only need to be created for directives/pipes which are not direct declarations of an
  1734. // NgModule which exports them.
  1735. return null;
  1736. }
  1737. return this.aliasName(ref.node, context);
  1738. }
  1739. /**
  1740. * Generates an `Expression` to import `decl` from `via`, assuming an export was added when `via`
  1741. * was compiled per `maybeAliasSymbolAs` above.
  1742. */
  1743. getAliasIn(decl, via, isReExport) {
  1744. if (!isReExport) {
  1745. // Directly exported directives/pipes don't require an alias, per the logic in
  1746. // `maybeAliasSymbolAs`.
  1747. return null;
  1748. }
  1749. // viaModule is the module it'll actually be imported from.
  1750. const moduleName = this.unifiedModulesHost.fileNameToModuleName(via.fileName, via.fileName);
  1751. return new checker.ExternalExpr({ moduleName, name: this.aliasName(decl, via) });
  1752. }
  1753. /**
  1754. * Generates an alias name based on the full module name of the file which declares the aliased
  1755. * directive/pipe.
  1756. */
  1757. aliasName(decl, context) {
  1758. // The declared module is used to get the name of the alias.
  1759. const declModule = this.unifiedModulesHost.fileNameToModuleName(decl.getSourceFile().fileName, context.fileName);
  1760. const replaced = declModule.replace(CHARS_TO_ESCAPE, '_').replace(/\//g, '$');
  1761. return 'ɵng$' + replaced + '$$' + decl.name.text;
  1762. }
  1763. }
  1764. /**
  1765. * An `AliasingHost` which exports directives from any file containing an NgModule in which they're
  1766. * declared/exported, under a private symbol name.
  1767. *
  1768. * These exports support cases where an NgModule is imported deeply from an absolute module path
  1769. * (that is, it's not part of an Angular Package Format entrypoint), and the compiler needs to
  1770. * import any matched directives/pipes from the same path (to the NgModule file). See README.md for
  1771. * more details.
  1772. */
  1773. class PrivateExportAliasingHost {
  1774. host;
  1775. constructor(host) {
  1776. this.host = host;
  1777. }
  1778. /**
  1779. * Under private export aliasing, the `AbsoluteModuleStrategy` used for emitting references will
  1780. * will select aliased exports that it finds in the .d.ts file for an NgModule's file. Thus,
  1781. * emitting these exports in .d.ts is a requirement for the `PrivateExportAliasingHost` to
  1782. * function correctly.
  1783. */
  1784. aliasExportsInDts = true;
  1785. maybeAliasSymbolAs(ref, context, ngModuleName) {
  1786. if (ref.hasOwningModuleGuess) {
  1787. // Skip nodes that already have an associated absolute module specifier, since they can be
  1788. // safely imported from that specifier.
  1789. return null;
  1790. }
  1791. // Look for a user-provided export of `decl` in `context`. If one exists, then an alias export
  1792. // is not needed.
  1793. // TODO(alxhub): maybe add a host method to check for the existence of an export without going
  1794. // through the entire list of exports.
  1795. const exports = this.host.getExportsOfModule(context);
  1796. if (exports === null) {
  1797. // Something went wrong, and no exports were available at all. Bail rather than risk creating
  1798. // re-exports when they're not needed.
  1799. throw new Error(`Could not determine the exports of: ${context.fileName}`);
  1800. }
  1801. let found = false;
  1802. exports.forEach((value) => {
  1803. if (value.node === ref.node) {
  1804. found = true;
  1805. }
  1806. });
  1807. if (found) {
  1808. // The module exports the declared class directly, no alias is necessary.
  1809. return null;
  1810. }
  1811. return `ɵngExportɵ${ngModuleName}ɵ${ref.node.name.text}`;
  1812. }
  1813. /**
  1814. * A `PrivateExportAliasingHost` only generates re-exports and does not direct the compiler to
  1815. * directly consume the aliases it creates.
  1816. *
  1817. * Instead, they're consumed indirectly: `AbsoluteModuleStrategy` `ReferenceEmitterStrategy` will
  1818. * select these alias exports automatically when looking for an export of the directive/pipe from
  1819. * the same path as the NgModule was imported.
  1820. *
  1821. * Thus, `getAliasIn` always returns `null`.
  1822. */
  1823. getAliasIn() {
  1824. return null;
  1825. }
  1826. }
  1827. /**
  1828. * A `ReferenceEmitStrategy` which will consume the alias attached to a particular `Reference` to a
  1829. * directive or pipe, if it exists.
  1830. */
  1831. class AliasStrategy {
  1832. emit(ref, context, importMode) {
  1833. if (importMode & checker.ImportFlags.NoAliasing || ref.alias === null) {
  1834. return null;
  1835. }
  1836. return {
  1837. kind: checker.ReferenceEmitKind.Success,
  1838. expression: ref.alias,
  1839. importedFile: 'unknown',
  1840. };
  1841. }
  1842. }
  1843. function relativePathBetween(from, to) {
  1844. const relativePath = checker.stripExtension(checker.relative(checker.dirname(checker.resolve(from)), checker.resolve(to)));
  1845. return relativePath !== '' ? checker.toRelativeImport(relativePath) : null;
  1846. }
  1847. function normalizeSeparators(path) {
  1848. // TODO: normalize path only for OS that need it.
  1849. return path.replace(/\\/g, '/');
  1850. }
  1851. /**
  1852. * Attempts to generate a project-relative path for a file.
  1853. * @param fileName Absolute path to the file.
  1854. * @param rootDirs Root directories of the project.
  1855. * @param compilerHost Host used to resolve file names.
  1856. * @returns
  1857. */
  1858. function getProjectRelativePath(fileName, rootDirs, compilerHost) {
  1859. // Note: we need to pass both the file name and the root directories through getCanonicalFileName,
  1860. // because the root directories might've been passed through it already while the source files
  1861. // definitely have not. This can break the relative return value, because in some platforms
  1862. // getCanonicalFileName lowercases the path.
  1863. const filePath = compilerHost.getCanonicalFileName(fileName);
  1864. for (const rootDir of rootDirs) {
  1865. const rel = checker.relative(compilerHost.getCanonicalFileName(rootDir), filePath);
  1866. if (!rel.startsWith('..')) {
  1867. return rel;
  1868. }
  1869. }
  1870. return null;
  1871. }
  1872. /**
  1873. * `ImportRewriter` that does no rewriting.
  1874. */
  1875. class NoopImportRewriter {
  1876. rewriteSymbol(symbol, specifier) {
  1877. return symbol;
  1878. }
  1879. rewriteSpecifier(specifier, inContextOfFile) {
  1880. return specifier;
  1881. }
  1882. rewriteNamespaceImportIdentifier(specifier) {
  1883. return specifier;
  1884. }
  1885. }
  1886. /**
  1887. * A mapping of supported symbols that can be imported from within @angular/core, and the names by
  1888. * which they're exported from r3_symbols.
  1889. */
  1890. const CORE_SUPPORTED_SYMBOLS = new Map([
  1891. ['ɵɵdefineInjectable', 'ɵɵdefineInjectable'],
  1892. ['ɵɵdefineInjector', 'ɵɵdefineInjector'],
  1893. ['ɵɵdefineNgModule', 'ɵɵdefineNgModule'],
  1894. ['ɵɵsetNgModuleScope', 'ɵɵsetNgModuleScope'],
  1895. ['ɵɵinject', 'ɵɵinject'],
  1896. ['ɵɵFactoryDeclaration', 'ɵɵFactoryDeclaration'],
  1897. ['ɵsetClassMetadata', 'setClassMetadata'],
  1898. ['ɵsetClassMetadataAsync', 'setClassMetadataAsync'],
  1899. ['ɵɵInjectableDeclaration', 'ɵɵInjectableDeclaration'],
  1900. ['ɵɵInjectorDeclaration', 'ɵɵInjectorDeclaration'],
  1901. ['ɵɵNgModuleDeclaration', 'ɵɵNgModuleDeclaration'],
  1902. ['ɵNgModuleFactory', 'NgModuleFactory'],
  1903. ['ɵnoSideEffects', 'ɵnoSideEffects'],
  1904. ]);
  1905. const CORE_MODULE = '@angular/core';
  1906. /**
  1907. * `ImportRewriter` that rewrites imports from '@angular/core' to be imported from the r3_symbols.ts
  1908. * file instead.
  1909. */
  1910. class R3SymbolsImportRewriter {
  1911. r3SymbolsPath;
  1912. constructor(r3SymbolsPath) {
  1913. this.r3SymbolsPath = r3SymbolsPath;
  1914. }
  1915. rewriteSymbol(symbol, specifier) {
  1916. if (specifier !== CORE_MODULE) {
  1917. // This import isn't from core, so ignore it.
  1918. return symbol;
  1919. }
  1920. return validateAndRewriteCoreSymbol(symbol);
  1921. }
  1922. rewriteSpecifier(specifier, inContextOfFile) {
  1923. if (specifier !== CORE_MODULE) {
  1924. // This module isn't core, so ignore it.
  1925. return specifier;
  1926. }
  1927. const relativePathToR3Symbols = relativePathBetween(inContextOfFile, this.r3SymbolsPath);
  1928. if (relativePathToR3Symbols === null) {
  1929. throw new Error(`Failed to rewrite import inside ${CORE_MODULE}: ${inContextOfFile} -> ${this.r3SymbolsPath}`);
  1930. }
  1931. return relativePathToR3Symbols;
  1932. }
  1933. rewriteNamespaceImportIdentifier(specifier) {
  1934. return specifier;
  1935. }
  1936. }
  1937. function validateAndRewriteCoreSymbol(name) {
  1938. if (!CORE_SUPPORTED_SYMBOLS.has(name)) {
  1939. throw new Error(`Importing unexpected symbol ${name} while compiling ${CORE_MODULE}`);
  1940. }
  1941. return CORE_SUPPORTED_SYMBOLS.get(name);
  1942. }
  1943. const AssumeEager = 'AssumeEager';
  1944. /**
  1945. * Allows to register a symbol as deferrable and keep track of its usage.
  1946. *
  1947. * This information is later used to determine whether it's safe to drop
  1948. * a regular import of this symbol (actually the entire import declaration)
  1949. * in favor of using a dynamic import for cases when defer blocks are used.
  1950. */
  1951. class DeferredSymbolTracker {
  1952. typeChecker;
  1953. onlyExplicitDeferDependencyImports;
  1954. imports = new Map();
  1955. /**
  1956. * Map of a component class -> all import declarations that bring symbols
  1957. * used within `@Component.deferredImports` field.
  1958. */
  1959. explicitlyDeferredImports = new Map();
  1960. constructor(typeChecker, onlyExplicitDeferDependencyImports) {
  1961. this.typeChecker = typeChecker;
  1962. this.onlyExplicitDeferDependencyImports = onlyExplicitDeferDependencyImports;
  1963. }
  1964. /**
  1965. * Given an import declaration node, extract the names of all imported symbols
  1966. * and return them as a map where each symbol is a key and `AssumeEager` is a value.
  1967. *
  1968. * The logic recognizes the following import shapes:
  1969. *
  1970. * Case 1: `import {a, b as B} from 'a'`
  1971. * Case 2: `import X from 'a'`
  1972. * Case 3: `import * as x from 'a'`
  1973. */
  1974. extractImportedSymbols(importDecl) {
  1975. const symbolMap = new Map();
  1976. // Unsupported case: `import 'a'`
  1977. if (importDecl.importClause === undefined) {
  1978. throw new Error(`Provided import declaration doesn't have any symbols.`);
  1979. }
  1980. // If the entire import is a type-only import, none of the symbols can be eager.
  1981. if (importDecl.importClause.isTypeOnly) {
  1982. return symbolMap;
  1983. }
  1984. if (importDecl.importClause.namedBindings !== undefined) {
  1985. const bindings = importDecl.importClause.namedBindings;
  1986. if (ts.isNamedImports(bindings)) {
  1987. // Case 1: `import {a, b as B} from 'a'`
  1988. for (const element of bindings.elements) {
  1989. if (!element.isTypeOnly) {
  1990. symbolMap.set(element.name.text, AssumeEager);
  1991. }
  1992. }
  1993. }
  1994. else {
  1995. // Case 2: `import X from 'a'`
  1996. symbolMap.set(bindings.name.text, AssumeEager);
  1997. }
  1998. }
  1999. else if (importDecl.importClause.name !== undefined) {
  2000. // Case 2: `import * as x from 'a'`
  2001. symbolMap.set(importDecl.importClause.name.text, AssumeEager);
  2002. }
  2003. else {
  2004. throw new Error('Unrecognized import structure.');
  2005. }
  2006. return symbolMap;
  2007. }
  2008. /**
  2009. * Retrieves a list of import declarations that contain symbols used within
  2010. * `@Component.deferredImports` of a specific component class, but those imports
  2011. * can not be removed, since there are other symbols imported alongside deferred
  2012. * components.
  2013. */
  2014. getNonRemovableDeferredImports(sourceFile, classDecl) {
  2015. const affectedImports = [];
  2016. const importDecls = this.explicitlyDeferredImports.get(classDecl) ?? [];
  2017. for (const importDecl of importDecls) {
  2018. if (importDecl.getSourceFile() === sourceFile && !this.canDefer(importDecl)) {
  2019. affectedImports.push(importDecl);
  2020. }
  2021. }
  2022. return affectedImports;
  2023. }
  2024. /**
  2025. * Marks a given identifier and an associated import declaration as a candidate
  2026. * for defer loading.
  2027. */
  2028. markAsDeferrableCandidate(identifier, importDecl, componentClassDecl, isExplicitlyDeferred) {
  2029. if (this.onlyExplicitDeferDependencyImports && !isExplicitlyDeferred) {
  2030. // Ignore deferrable candidates when only explicit deferred imports mode is enabled.
  2031. // In that mode only dependencies from the `@Component.deferredImports` field are
  2032. // defer-loadable.
  2033. return;
  2034. }
  2035. if (isExplicitlyDeferred) {
  2036. if (this.explicitlyDeferredImports.has(componentClassDecl)) {
  2037. this.explicitlyDeferredImports.get(componentClassDecl).push(importDecl);
  2038. }
  2039. else {
  2040. this.explicitlyDeferredImports.set(componentClassDecl, [importDecl]);
  2041. }
  2042. }
  2043. let symbolMap = this.imports.get(importDecl);
  2044. // Do we come across this import for the first time?
  2045. if (!symbolMap) {
  2046. symbolMap = this.extractImportedSymbols(importDecl);
  2047. this.imports.set(importDecl, symbolMap);
  2048. }
  2049. if (!symbolMap.has(identifier.text)) {
  2050. throw new Error(`The '${identifier.text}' identifier doesn't belong ` +
  2051. `to the provided import declaration.`);
  2052. }
  2053. if (symbolMap.get(identifier.text) === AssumeEager) {
  2054. // We process this symbol for the first time, populate references.
  2055. symbolMap.set(identifier.text, this.lookupIdentifiersInSourceFile(identifier.text, importDecl));
  2056. }
  2057. const identifiers = symbolMap.get(identifier.text);
  2058. // Drop the current identifier, since we are trying to make it deferrable
  2059. // (it's used as a dependency in one of the defer blocks).
  2060. identifiers.delete(identifier);
  2061. }
  2062. /**
  2063. * Whether all symbols from a given import declaration have no references
  2064. * in a source file, thus it's safe to use dynamic imports.
  2065. */
  2066. canDefer(importDecl) {
  2067. if (!this.imports.has(importDecl)) {
  2068. return false;
  2069. }
  2070. const symbolsMap = this.imports.get(importDecl);
  2071. for (const refs of symbolsMap.values()) {
  2072. if (refs === AssumeEager || refs.size > 0) {
  2073. // There may be still eager references to this symbol.
  2074. return false;
  2075. }
  2076. }
  2077. return true;
  2078. }
  2079. /**
  2080. * Returns a set of import declarations that is safe to remove
  2081. * from the current source file and generate dynamic imports instead.
  2082. */
  2083. getDeferrableImportDecls() {
  2084. const deferrableDecls = new Set();
  2085. for (const [importDecl] of this.imports) {
  2086. if (this.canDefer(importDecl)) {
  2087. deferrableDecls.add(importDecl);
  2088. }
  2089. }
  2090. return deferrableDecls;
  2091. }
  2092. lookupIdentifiersInSourceFile(name, importDecl) {
  2093. const results = new Set();
  2094. const visit = (node) => {
  2095. // Don't record references from the declaration itself or inside
  2096. // type nodes which will be stripped from the JS output.
  2097. if (node === importDecl || ts.isTypeNode(node)) {
  2098. return;
  2099. }
  2100. if (ts.isIdentifier(node) && node.text === name) {
  2101. // Is `node` actually a reference to this symbol?
  2102. const sym = this.typeChecker.getSymbolAtLocation(node);
  2103. if (sym === undefined) {
  2104. return;
  2105. }
  2106. if (sym.declarations === undefined || sym.declarations.length === 0) {
  2107. return;
  2108. }
  2109. const importClause = sym.declarations[0];
  2110. // Is declaration from this import statement?
  2111. const decl = checker.getContainingImportDeclaration(importClause);
  2112. if (decl !== importDecl) {
  2113. return;
  2114. }
  2115. // `node` *is* a reference to the same import.
  2116. results.add(node);
  2117. }
  2118. ts.forEachChild(node, visit);
  2119. };
  2120. visit(importDecl.getSourceFile());
  2121. return results;
  2122. }
  2123. }
  2124. /*!
  2125. * @license
  2126. * Copyright Google LLC All Rights Reserved.
  2127. *
  2128. * Use of this source code is governed by an MIT-style license that can be
  2129. * found in the LICENSE file at https://angular.dev/license
  2130. */
  2131. /**
  2132. * Tracks which symbols are imported in specific files and under what names. Allows for efficient
  2133. * querying for references to those symbols without having to consult the type checker early in the
  2134. * process.
  2135. *
  2136. * Note that the tracker doesn't account for variable shadowing so a final verification with the
  2137. * type checker may be necessary, depending on the context. Also does not track dynamic imports.
  2138. */
  2139. class ImportedSymbolsTracker {
  2140. fileToNamedImports = new WeakMap();
  2141. fileToNamespaceImports = new WeakMap();
  2142. /**
  2143. * Checks if an identifier is a potential reference to a specific named import within the same
  2144. * file.
  2145. * @param node Identifier to be checked.
  2146. * @param exportedName Name of the exported symbol that is being searched for.
  2147. * @param moduleName Module from which the symbol should be imported.
  2148. */
  2149. isPotentialReferenceToNamedImport(node, exportedName, moduleName) {
  2150. const sourceFile = node.getSourceFile();
  2151. this.scanImports(sourceFile);
  2152. const fileImports = this.fileToNamedImports.get(sourceFile);
  2153. const moduleImports = fileImports.get(moduleName);
  2154. const symbolImports = moduleImports?.get(exportedName);
  2155. return symbolImports !== undefined && symbolImports.has(node.text);
  2156. }
  2157. /**
  2158. * Checks if an identifier is a potential reference to a specific namespace import within the same
  2159. * file.
  2160. * @param node Identifier to be checked.
  2161. * @param moduleName Module from which the namespace is imported.
  2162. */
  2163. isPotentialReferenceToNamespaceImport(node, moduleName) {
  2164. const sourceFile = node.getSourceFile();
  2165. this.scanImports(sourceFile);
  2166. const namespaces = this.fileToNamespaceImports.get(sourceFile);
  2167. return namespaces.get(moduleName)?.has(node.text) ?? false;
  2168. }
  2169. /**
  2170. * Checks if a file has a named imported of a certain symbol.
  2171. * @param sourceFile File to be checked.
  2172. * @param exportedName Name of the exported symbol that is being checked.
  2173. * @param moduleName Module that exports the symbol.
  2174. */
  2175. hasNamedImport(sourceFile, exportedName, moduleName) {
  2176. this.scanImports(sourceFile);
  2177. const fileImports = this.fileToNamedImports.get(sourceFile);
  2178. const moduleImports = fileImports.get(moduleName);
  2179. return moduleImports !== undefined && moduleImports.has(exportedName);
  2180. }
  2181. /**
  2182. * Checks if a file has namespace imports of a certain symbol.
  2183. * @param sourceFile File to be checked.
  2184. * @param moduleName Module whose namespace import is being searched for.
  2185. */
  2186. hasNamespaceImport(sourceFile, moduleName) {
  2187. this.scanImports(sourceFile);
  2188. const namespaces = this.fileToNamespaceImports.get(sourceFile);
  2189. return namespaces.has(moduleName);
  2190. }
  2191. /** Scans a `SourceFile` for import statements and caches them for later use. */
  2192. scanImports(sourceFile) {
  2193. if (this.fileToNamedImports.has(sourceFile) && this.fileToNamespaceImports.has(sourceFile)) {
  2194. return;
  2195. }
  2196. const namedImports = new Map();
  2197. const namespaceImports = new Map();
  2198. this.fileToNamedImports.set(sourceFile, namedImports);
  2199. this.fileToNamespaceImports.set(sourceFile, namespaceImports);
  2200. // Only check top-level imports.
  2201. for (const stmt of sourceFile.statements) {
  2202. if (!ts.isImportDeclaration(stmt) ||
  2203. !ts.isStringLiteralLike(stmt.moduleSpecifier) ||
  2204. stmt.importClause?.namedBindings === undefined) {
  2205. continue;
  2206. }
  2207. const moduleName = stmt.moduleSpecifier.text;
  2208. if (ts.isNamespaceImport(stmt.importClause.namedBindings)) {
  2209. // import * as foo from 'module'
  2210. if (!namespaceImports.has(moduleName)) {
  2211. namespaceImports.set(moduleName, new Set());
  2212. }
  2213. namespaceImports.get(moduleName).add(stmt.importClause.namedBindings.name.text);
  2214. }
  2215. else {
  2216. // import {foo, bar as alias} from 'module'
  2217. for (const element of stmt.importClause.namedBindings.elements) {
  2218. const localName = element.name.text;
  2219. const exportedName = element.propertyName === undefined ? localName : element.propertyName.text;
  2220. if (!namedImports.has(moduleName)) {
  2221. namedImports.set(moduleName, new Map());
  2222. }
  2223. const localNames = namedImports.get(moduleName);
  2224. if (!localNames.has(exportedName)) {
  2225. localNames.set(exportedName, new Set());
  2226. }
  2227. localNames.get(exportedName)?.add(localName);
  2228. }
  2229. }
  2230. }
  2231. }
  2232. }
  2233. /**
  2234. * A tool to track extra imports to be added to the generated files in the local compilation mode.
  2235. *
  2236. * This is needed for g3 bundling mechanism which requires dev files (= locally compiled) to have
  2237. * imports resemble those generated for prod files (= full compilation mode). In full compilation
  2238. * mode Angular compiler generates extra imports for statically analyzed component dependencies. We
  2239. * need similar imports in local compilation as well.
  2240. *
  2241. * The tool offers API for adding local imports (to be added to a specific file) and global imports
  2242. * (to be added to all the files in the local compilation). For more details on how these extra
  2243. * imports are determined see this design doc:
  2244. * https://docs.google.com/document/d/1dOWoSDvOY9ozlMmyCnxoFLEzGgHmTFVRAOVdVU-bxlI/edit?tab=t.0#heading=h.5n3k516r57g5
  2245. *
  2246. * An instance of this class will be passed to each annotation handler so that they can register the
  2247. * extra imports that they see fit. Later on, the instance is passed to the Ivy transformer ({@link
  2248. * ivyTransformFactory}) and it is used to add the extra imports registered by the handlers to the
  2249. * import manager ({@link ImportManager}) in order to have these imports generated.
  2250. *
  2251. * The extra imports are all side effect imports, and so they are identified by a single string
  2252. * containing the module name.
  2253. *
  2254. */
  2255. class LocalCompilationExtraImportsTracker {
  2256. typeChecker;
  2257. localImportsMap = new Map();
  2258. globalImportsSet = new Set();
  2259. /** Names of the files marked for extra import generation. */
  2260. markedFilesSet = new Set();
  2261. constructor(typeChecker) {
  2262. this.typeChecker = typeChecker;
  2263. }
  2264. /**
  2265. * Marks the source file for extra imports generation.
  2266. *
  2267. * The extra imports are generated only for the files marked through this method. In other words,
  2268. * the method {@link getImportsForFile} returns empty if the file is not marked. This allows the
  2269. * consumers of this tool to avoid generating extra imports for unrelated files (e.g., non-Angular
  2270. * files)
  2271. */
  2272. markFileForExtraImportGeneration(sf) {
  2273. this.markedFilesSet.add(sf.fileName);
  2274. }
  2275. /**
  2276. * Adds an extra import to be added to the generated file of a specific source file.
  2277. */
  2278. addImportForFile(sf, moduleName) {
  2279. if (!this.localImportsMap.has(sf.fileName)) {
  2280. this.localImportsMap.set(sf.fileName, new Set());
  2281. }
  2282. this.localImportsMap.get(sf.fileName).add(moduleName);
  2283. }
  2284. /**
  2285. * If the given node is an imported identifier, this method adds the module from which it is
  2286. * imported as an extra import to the generated file of each source file in the compilation unit,
  2287. * otherwise the method is noop.
  2288. *
  2289. * Adding an extra import to all files is not optimal though. There are rooms to optimize and a
  2290. * add the import to a subset of files (e.g., exclude all the non Angular files as they don't need
  2291. * any extra import). However for this first version of this feature we go by this mechanism for
  2292. * simplicity. There will be on-going work to further optimize this method to add the extra import
  2293. * to smallest possible candidate files instead of all files.
  2294. */
  2295. addGlobalImportFromIdentifier(node) {
  2296. let identifier = null;
  2297. if (ts.isIdentifier(node)) {
  2298. identifier = node;
  2299. }
  2300. else if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
  2301. identifier = node.expression;
  2302. }
  2303. if (identifier === null) {
  2304. return;
  2305. }
  2306. const sym = this.typeChecker.getSymbolAtLocation(identifier);
  2307. if (!sym?.declarations?.length) {
  2308. return;
  2309. }
  2310. const importClause = sym.declarations[0];
  2311. const decl = checker.getContainingImportDeclaration(importClause);
  2312. if (decl !== null) {
  2313. this.globalImportsSet.add(removeQuotations(decl.moduleSpecifier.getText()));
  2314. }
  2315. }
  2316. /**
  2317. * Returns the list of all module names that the given file should include as its extra imports.
  2318. */
  2319. getImportsForFile(sf) {
  2320. if (!this.markedFilesSet.has(sf.fileName)) {
  2321. return [];
  2322. }
  2323. return [...this.globalImportsSet, ...(this.localImportsMap.get(sf.fileName) ?? [])];
  2324. }
  2325. }
  2326. function removeQuotations(s) {
  2327. return s.substring(1, s.length - 1).trim();
  2328. }
  2329. /**
  2330. * Used by `RouterEntryPointManager` and `NgModuleRouteAnalyzer` (which is in turn is used by
  2331. * `NgModuleDecoratorHandler`) for resolving the module source-files references in lazy-loaded
  2332. * routes (relative to the source-file containing the `NgModule` that provides the route
  2333. * definitions).
  2334. */
  2335. class ModuleResolver {
  2336. program;
  2337. compilerOptions;
  2338. host;
  2339. moduleResolutionCache;
  2340. constructor(program, compilerOptions, host, moduleResolutionCache) {
  2341. this.program = program;
  2342. this.compilerOptions = compilerOptions;
  2343. this.host = host;
  2344. this.moduleResolutionCache = moduleResolutionCache;
  2345. }
  2346. resolveModule(moduleName, containingFile) {
  2347. const resolved = checker.resolveModuleName(moduleName, containingFile, this.compilerOptions, this.host, this.moduleResolutionCache);
  2348. if (resolved === undefined) {
  2349. return null;
  2350. }
  2351. return checker.getSourceFileOrNull(this.program, checker.absoluteFrom(resolved.resolvedFileName));
  2352. }
  2353. }
  2354. /**
  2355. * A `MetadataReader` that can read metadata from `.d.ts` files, which have static Ivy properties
  2356. * from an upstream compilation already.
  2357. */
  2358. class DtsMetadataReader {
  2359. checker;
  2360. reflector;
  2361. constructor(checker, reflector) {
  2362. this.checker = checker;
  2363. this.reflector = reflector;
  2364. }
  2365. /**
  2366. * Read the metadata from a class that has already been compiled somehow (either it's in a .d.ts
  2367. * file, or in a .ts file with a handwritten definition).
  2368. *
  2369. * @param ref `Reference` to the class of interest, with the context of how it was obtained.
  2370. */
  2371. getNgModuleMetadata(ref) {
  2372. const clazz = ref.node;
  2373. // This operation is explicitly not memoized, as it depends on `ref.ownedByModuleGuess`.
  2374. // TODO(alxhub): investigate caching of .d.ts module metadata.
  2375. const ngModuleDef = this.reflector
  2376. .getMembersOfClass(clazz)
  2377. .find((member) => member.name === 'ɵmod' && member.isStatic);
  2378. if (ngModuleDef === undefined) {
  2379. return null;
  2380. }
  2381. else if (
  2382. // Validate that the shape of the ngModuleDef type is correct.
  2383. ngModuleDef.type === null ||
  2384. !ts.isTypeReferenceNode(ngModuleDef.type) ||
  2385. ngModuleDef.type.typeArguments === undefined ||
  2386. ngModuleDef.type.typeArguments.length !== 4) {
  2387. return null;
  2388. }
  2389. // Read the ModuleData out of the type arguments.
  2390. const [_, declarationMetadata, importMetadata, exportMetadata] = ngModuleDef.type.typeArguments;
  2391. const declarations = checker.extractReferencesFromType(this.checker, declarationMetadata, ref.bestGuessOwningModule);
  2392. const exports = checker.extractReferencesFromType(this.checker, exportMetadata, ref.bestGuessOwningModule);
  2393. const imports = checker.extractReferencesFromType(this.checker, importMetadata, ref.bestGuessOwningModule);
  2394. // The module is considered poisoned if it's exports couldn't be
  2395. // resolved completely. This would make the module not necessarily
  2396. // usable for scope computation relying on this module; so we propagate
  2397. // this "incompleteness" information to the caller.
  2398. const isPoisoned = exports.isIncomplete;
  2399. return {
  2400. kind: checker.MetaKind.NgModule,
  2401. ref,
  2402. declarations: declarations.result,
  2403. isPoisoned,
  2404. exports: exports.result,
  2405. imports: imports.result,
  2406. schemas: [],
  2407. rawDeclarations: null,
  2408. rawImports: null,
  2409. rawExports: null,
  2410. decorator: null,
  2411. // NgModules declared outside the current compilation are assumed to contain providers, as it
  2412. // would be a non-breaking change for a library to introduce providers at any point.
  2413. mayDeclareProviders: true,
  2414. };
  2415. }
  2416. /**
  2417. * Read directive (or component) metadata from a referenced class in a .d.ts file.
  2418. */
  2419. getDirectiveMetadata(ref) {
  2420. const clazz = ref.node;
  2421. const def = this.reflector
  2422. .getMembersOfClass(clazz)
  2423. .find((field) => field.isStatic && (field.name === 'ɵcmp' || field.name === 'ɵdir'));
  2424. if (def === undefined) {
  2425. // No definition could be found.
  2426. return null;
  2427. }
  2428. else if (def.type === null ||
  2429. !ts.isTypeReferenceNode(def.type) ||
  2430. def.type.typeArguments === undefined ||
  2431. def.type.typeArguments.length < 2) {
  2432. // The type metadata was the wrong shape.
  2433. return null;
  2434. }
  2435. const isComponent = def.name === 'ɵcmp';
  2436. const ctorParams = this.reflector.getConstructorParameters(clazz);
  2437. // A directive is considered to be structural if:
  2438. // 1) it's a directive, not a component, and
  2439. // 2) it injects `TemplateRef`
  2440. const isStructural = !isComponent &&
  2441. ctorParams !== null &&
  2442. ctorParams.some((param) => {
  2443. return (param.typeValueReference.kind === 1 /* TypeValueReferenceKind.IMPORTED */ &&
  2444. param.typeValueReference.moduleName === '@angular/core' &&
  2445. param.typeValueReference.importedName === 'TemplateRef');
  2446. });
  2447. const ngContentSelectors = def.type.typeArguments.length > 6 ? checker.readStringArrayType(def.type.typeArguments[6]) : null;
  2448. // Note: the default value is still `false` here, because only legacy .d.ts files written before
  2449. // we had so many arguments use this default.
  2450. const isStandalone = def.type.typeArguments.length > 7 && (checker.readBooleanType(def.type.typeArguments[7]) ?? false);
  2451. const inputs = checker.ClassPropertyMapping.fromMappedObject(readInputsType(def.type.typeArguments[3]));
  2452. const outputs = checker.ClassPropertyMapping.fromMappedObject(checker.readMapType(def.type.typeArguments[4], checker.readStringType));
  2453. const hostDirectives = def.type.typeArguments.length > 8
  2454. ? readHostDirectivesType(this.checker, def.type.typeArguments[8], ref.bestGuessOwningModule)
  2455. : null;
  2456. const isSignal = def.type.typeArguments.length > 9 && (checker.readBooleanType(def.type.typeArguments[9]) ?? false);
  2457. // At this point in time, the `.d.ts` may not be fully extractable when
  2458. // trying to resolve host directive types to their declarations.
  2459. // If this cannot be done completely, the metadata is incomplete and "poisoned".
  2460. const isPoisoned = hostDirectives !== null && hostDirectives?.isIncomplete;
  2461. return {
  2462. kind: checker.MetaKind.Directive,
  2463. matchSource: checker.MatchSource.Selector,
  2464. ref,
  2465. name: clazz.name.text,
  2466. isComponent,
  2467. selector: checker.readStringType(def.type.typeArguments[1]),
  2468. exportAs: checker.readStringArrayType(def.type.typeArguments[2]),
  2469. inputs,
  2470. outputs,
  2471. hostDirectives: hostDirectives?.result ?? null,
  2472. queries: checker.readStringArrayType(def.type.typeArguments[5]),
  2473. ...checker.extractDirectiveTypeCheckMeta(clazz, inputs, this.reflector),
  2474. baseClass: readBaseClass(clazz, this.checker, this.reflector),
  2475. isPoisoned,
  2476. isStructural,
  2477. animationTriggerNames: null,
  2478. ngContentSelectors,
  2479. isStandalone,
  2480. isSignal,
  2481. // We do not transfer information about inputs from class metadata
  2482. // via `.d.ts` declarations. This is fine because this metadata is
  2483. // currently only used for classes defined in source files. E.g. in migrations.
  2484. inputFieldNamesFromMetadataArray: null,
  2485. // Imports are tracked in metadata only for template type-checking purposes,
  2486. // so standalone components from .d.ts files don't have any.
  2487. imports: null,
  2488. rawImports: null,
  2489. deferredImports: null,
  2490. // The same goes for schemas.
  2491. schemas: null,
  2492. decorator: null,
  2493. // Assume that standalone components from .d.ts files may export providers.
  2494. assumedToExportProviders: isComponent && isStandalone,
  2495. // `preserveWhitespaces` isn't encoded in the .d.ts and is only
  2496. // used to increase the accuracy of a diagnostic.
  2497. preserveWhitespaces: false,
  2498. isExplicitlyDeferred: false,
  2499. };
  2500. }
  2501. /**
  2502. * Read pipe metadata from a referenced class in a .d.ts file.
  2503. */
  2504. getPipeMetadata(ref) {
  2505. const def = this.reflector
  2506. .getMembersOfClass(ref.node)
  2507. .find((field) => field.isStatic && field.name === 'ɵpipe');
  2508. if (def === undefined) {
  2509. // No definition could be found.
  2510. return null;
  2511. }
  2512. else if (def.type === null ||
  2513. !ts.isTypeReferenceNode(def.type) ||
  2514. def.type.typeArguments === undefined ||
  2515. def.type.typeArguments.length < 2) {
  2516. // The type metadata was the wrong shape.
  2517. return null;
  2518. }
  2519. const type = def.type.typeArguments[1];
  2520. if (!ts.isLiteralTypeNode(type) || !ts.isStringLiteral(type.literal)) {
  2521. // The type metadata was the wrong type.
  2522. return null;
  2523. }
  2524. const name = type.literal.text;
  2525. const isStandalone = def.type.typeArguments.length > 2 && (checker.readBooleanType(def.type.typeArguments[2]) ?? false);
  2526. return {
  2527. kind: checker.MetaKind.Pipe,
  2528. ref,
  2529. name,
  2530. nameExpr: null,
  2531. isStandalone,
  2532. isPure: null, // The DTS has no idea about that
  2533. decorator: null,
  2534. isExplicitlyDeferred: false,
  2535. };
  2536. }
  2537. }
  2538. function readInputsType(type) {
  2539. const inputsMap = {};
  2540. if (ts.isTypeLiteralNode(type)) {
  2541. for (const member of type.members) {
  2542. if (!ts.isPropertySignature(member) ||
  2543. member.type === undefined ||
  2544. member.name === undefined ||
  2545. (!ts.isStringLiteral(member.name) && !ts.isIdentifier(member.name))) {
  2546. continue;
  2547. }
  2548. const stringValue = checker.readStringType(member.type);
  2549. const classPropertyName = member.name.text;
  2550. // Before v16 the inputs map has the type of `{[field: string]: string}`.
  2551. // After v16 it has the type of `{[field: string]: {alias: string, required: boolean}}`.
  2552. if (stringValue != null) {
  2553. inputsMap[classPropertyName] = {
  2554. bindingPropertyName: stringValue,
  2555. classPropertyName,
  2556. required: false,
  2557. // Signal inputs were not supported pre v16- so those inputs are never signal based.
  2558. isSignal: false,
  2559. // Input transform are only tracked for locally-compiled directives. Directives coming
  2560. // from the .d.ts already have them included through `ngAcceptInputType` class members,
  2561. // or via the `InputSignal` type of the member.
  2562. transform: null,
  2563. };
  2564. }
  2565. else {
  2566. const config = checker.readMapType(member.type, (innerValue) => {
  2567. return checker.readStringType(innerValue) ?? checker.readBooleanType(innerValue);
  2568. });
  2569. inputsMap[classPropertyName] = {
  2570. classPropertyName,
  2571. bindingPropertyName: config.alias,
  2572. required: config.required,
  2573. isSignal: !!config.isSignal,
  2574. // Input transform are only tracked for locally-compiled directives. Directives coming
  2575. // from the .d.ts already have them included through `ngAcceptInputType` class members,
  2576. // or via the `InputSignal` type of the member.
  2577. transform: null,
  2578. };
  2579. }
  2580. }
  2581. }
  2582. return inputsMap;
  2583. }
  2584. function readBaseClass(clazz, checker$1, reflector) {
  2585. if (!checker.isNamedClassDeclaration(clazz)) {
  2586. // Technically this is an error in a .d.ts file, but for the purposes of finding the base class
  2587. // it's ignored.
  2588. return reflector.hasBaseClass(clazz) ? 'dynamic' : null;
  2589. }
  2590. if (clazz.heritageClauses !== undefined) {
  2591. for (const clause of clazz.heritageClauses) {
  2592. if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
  2593. const baseExpr = clause.types[0].expression;
  2594. let symbol = checker$1.getSymbolAtLocation(baseExpr);
  2595. if (symbol === undefined) {
  2596. return 'dynamic';
  2597. }
  2598. else if (symbol.flags & ts.SymbolFlags.Alias) {
  2599. symbol = checker$1.getAliasedSymbol(symbol);
  2600. }
  2601. if (symbol.valueDeclaration !== undefined &&
  2602. checker.isNamedClassDeclaration(symbol.valueDeclaration)) {
  2603. return new checker.Reference(symbol.valueDeclaration);
  2604. }
  2605. else {
  2606. return 'dynamic';
  2607. }
  2608. }
  2609. }
  2610. }
  2611. return null;
  2612. }
  2613. function readHostDirectivesType(checker$1, type, bestGuessOwningModule) {
  2614. if (!ts.isTupleTypeNode(type) || type.elements.length === 0) {
  2615. return null;
  2616. }
  2617. const result = [];
  2618. let isIncomplete = false;
  2619. for (const hostDirectiveType of type.elements) {
  2620. const { directive, inputs, outputs } = checker.readMapType(hostDirectiveType, (type) => type);
  2621. if (directive) {
  2622. if (!ts.isTypeQueryNode(directive)) {
  2623. throw new Error(`Expected TypeQueryNode: ${checker.nodeDebugInfo(directive)}`);
  2624. }
  2625. const ref = checker.extraReferenceFromTypeQuery(checker$1, directive, type, bestGuessOwningModule);
  2626. if (ref === null) {
  2627. isIncomplete = true;
  2628. continue;
  2629. }
  2630. result.push({
  2631. directive: ref,
  2632. isForwardReference: false,
  2633. inputs: checker.readMapType(inputs, checker.readStringType),
  2634. outputs: checker.readMapType(outputs, checker.readStringType),
  2635. });
  2636. }
  2637. }
  2638. return result.length > 0 ? { result, isIncomplete } : null;
  2639. }
  2640. /**
  2641. * A registry of directive, pipe, and module metadata for types defined in the current compilation
  2642. * unit, which supports both reading and registering.
  2643. */
  2644. class LocalMetadataRegistry {
  2645. directives = new Map();
  2646. ngModules = new Map();
  2647. pipes = new Map();
  2648. getDirectiveMetadata(ref) {
  2649. return this.directives.has(ref.node) ? this.directives.get(ref.node) : null;
  2650. }
  2651. getNgModuleMetadata(ref) {
  2652. return this.ngModules.has(ref.node) ? this.ngModules.get(ref.node) : null;
  2653. }
  2654. getPipeMetadata(ref) {
  2655. return this.pipes.has(ref.node) ? this.pipes.get(ref.node) : null;
  2656. }
  2657. registerDirectiveMetadata(meta) {
  2658. this.directives.set(meta.ref.node, meta);
  2659. }
  2660. registerNgModuleMetadata(meta) {
  2661. this.ngModules.set(meta.ref.node, meta);
  2662. }
  2663. registerPipeMetadata(meta) {
  2664. this.pipes.set(meta.ref.node, meta);
  2665. }
  2666. getKnown(kind) {
  2667. switch (kind) {
  2668. case checker.MetaKind.Directive:
  2669. return Array.from(this.directives.values()).map((v) => v.ref.node);
  2670. case checker.MetaKind.Pipe:
  2671. return Array.from(this.pipes.values()).map((v) => v.ref.node);
  2672. case checker.MetaKind.NgModule:
  2673. return Array.from(this.ngModules.values()).map((v) => v.ref.node);
  2674. }
  2675. }
  2676. }
  2677. /**
  2678. * A `MetadataRegistry` which registers metadata with multiple delegate `MetadataRegistry`
  2679. * instances.
  2680. */
  2681. class CompoundMetadataRegistry {
  2682. registries;
  2683. constructor(registries) {
  2684. this.registries = registries;
  2685. }
  2686. registerDirectiveMetadata(meta) {
  2687. for (const registry of this.registries) {
  2688. registry.registerDirectiveMetadata(meta);
  2689. }
  2690. }
  2691. registerNgModuleMetadata(meta) {
  2692. for (const registry of this.registries) {
  2693. registry.registerNgModuleMetadata(meta);
  2694. }
  2695. }
  2696. registerPipeMetadata(meta) {
  2697. for (const registry of this.registries) {
  2698. registry.registerPipeMetadata(meta);
  2699. }
  2700. }
  2701. }
  2702. /**
  2703. * Tracks the mapping between external resources and the directives(s) which use them.
  2704. *
  2705. * This information is produced during analysis of the program and is used mainly to support
  2706. * external tooling, for which such a mapping is challenging to determine without compiler
  2707. * assistance.
  2708. */
  2709. class ResourceRegistry {
  2710. externalTemplateToComponentsMap = new Map();
  2711. componentToTemplateMap = new Map();
  2712. componentToStylesMap = new Map();
  2713. externalStyleToComponentsMap = new Map();
  2714. getComponentsWithTemplate(template) {
  2715. if (!this.externalTemplateToComponentsMap.has(template)) {
  2716. return new Set();
  2717. }
  2718. return this.externalTemplateToComponentsMap.get(template);
  2719. }
  2720. registerResources(resources, directive) {
  2721. if (resources.template !== null) {
  2722. this.registerTemplate(resources.template, directive);
  2723. }
  2724. if (resources.styles !== null) {
  2725. for (const style of resources.styles) {
  2726. this.registerStyle(style, directive);
  2727. }
  2728. }
  2729. }
  2730. registerTemplate(templateResource, component) {
  2731. const { path } = templateResource;
  2732. if (path !== null) {
  2733. if (!this.externalTemplateToComponentsMap.has(path)) {
  2734. this.externalTemplateToComponentsMap.set(path, new Set());
  2735. }
  2736. this.externalTemplateToComponentsMap.get(path).add(component);
  2737. }
  2738. this.componentToTemplateMap.set(component, templateResource);
  2739. }
  2740. getTemplate(component) {
  2741. if (!this.componentToTemplateMap.has(component)) {
  2742. return null;
  2743. }
  2744. return this.componentToTemplateMap.get(component);
  2745. }
  2746. registerStyle(styleResource, component) {
  2747. const { path } = styleResource;
  2748. if (!this.componentToStylesMap.has(component)) {
  2749. this.componentToStylesMap.set(component, new Set());
  2750. }
  2751. if (path !== null) {
  2752. if (!this.externalStyleToComponentsMap.has(path)) {
  2753. this.externalStyleToComponentsMap.set(path, new Set());
  2754. }
  2755. this.externalStyleToComponentsMap.get(path).add(component);
  2756. }
  2757. this.componentToStylesMap.get(component).add(styleResource);
  2758. }
  2759. getStyles(component) {
  2760. if (!this.componentToStylesMap.has(component)) {
  2761. return new Set();
  2762. }
  2763. return this.componentToStylesMap.get(component);
  2764. }
  2765. getComponentsWithStyle(styleUrl) {
  2766. if (!this.externalStyleToComponentsMap.has(styleUrl)) {
  2767. return new Set();
  2768. }
  2769. return this.externalStyleToComponentsMap.get(styleUrl);
  2770. }
  2771. }
  2772. /**
  2773. * Determines whether types may or may not export providers to NgModules, by transitively walking
  2774. * the NgModule & standalone import graph.
  2775. */
  2776. class ExportedProviderStatusResolver {
  2777. metaReader;
  2778. /**
  2779. * `ClassDeclaration`s that we are in the process of determining the provider status for.
  2780. *
  2781. * This is used to detect cycles in the import graph and avoid getting stuck in them.
  2782. */
  2783. calculating = new Set();
  2784. constructor(metaReader) {
  2785. this.metaReader = metaReader;
  2786. }
  2787. /**
  2788. * Determines whether `ref` may or may not export providers to NgModules which import it.
  2789. *
  2790. * NgModules export providers if any are declared, and standalone components export providers from
  2791. * their `imports` array (if any).
  2792. *
  2793. * If `true`, then `ref` should be assumed to export providers. In practice, this could mean
  2794. * either that `ref` is a local type that we _know_ exports providers, or it's imported from a
  2795. * .d.ts library and is declared in a way where the compiler cannot prove that it doesn't.
  2796. *
  2797. * If `false`, then `ref` is guaranteed not to export providers.
  2798. *
  2799. * @param `ref` the class for which the provider status should be determined
  2800. * @param `dependencyCallback` a callback that, if provided, will be called for every type
  2801. * which is used in the determination of provider status for `ref`
  2802. * @returns `true` if `ref` should be assumed to export providers, or `false` if the compiler can
  2803. * prove that it does not
  2804. */
  2805. mayExportProviders(ref, dependencyCallback) {
  2806. if (this.calculating.has(ref.node)) {
  2807. // For cycles, we treat the cyclic edge as not having providers.
  2808. return false;
  2809. }
  2810. this.calculating.add(ref.node);
  2811. if (dependencyCallback !== undefined) {
  2812. dependencyCallback(ref);
  2813. }
  2814. try {
  2815. const dirMeta = this.metaReader.getDirectiveMetadata(ref);
  2816. if (dirMeta !== null) {
  2817. if (!dirMeta.isComponent || !dirMeta.isStandalone) {
  2818. return false;
  2819. }
  2820. if (dirMeta.assumedToExportProviders) {
  2821. return true;
  2822. }
  2823. // If one of the imports contains providers, then so does this component.
  2824. return (dirMeta.imports ?? []).some((importRef) => this.mayExportProviders(importRef, dependencyCallback));
  2825. }
  2826. const pipeMeta = this.metaReader.getPipeMetadata(ref);
  2827. if (pipeMeta !== null) {
  2828. return false;
  2829. }
  2830. const ngModuleMeta = this.metaReader.getNgModuleMetadata(ref);
  2831. if (ngModuleMeta !== null) {
  2832. if (ngModuleMeta.mayDeclareProviders) {
  2833. return true;
  2834. }
  2835. // If one of the NgModule's imports may contain providers, then so does this NgModule.
  2836. return ngModuleMeta.imports.some((importRef) => this.mayExportProviders(importRef, dependencyCallback));
  2837. }
  2838. return false;
  2839. }
  2840. finally {
  2841. this.calculating.delete(ref.node);
  2842. }
  2843. }
  2844. }
  2845. /*!
  2846. * @license
  2847. * Copyright Google LLC All Rights Reserved.
  2848. *
  2849. * Use of this source code is governed by an MIT-style license that can be
  2850. * found in the LICENSE file at https://angular.dev/license
  2851. */
  2852. const EMPTY_ARRAY$1 = [];
  2853. /** Resolves the host directives of a directive to a flat array of matches. */
  2854. class HostDirectivesResolver {
  2855. metaReader;
  2856. cache = new Map();
  2857. constructor(metaReader) {
  2858. this.metaReader = metaReader;
  2859. }
  2860. /** Resolves all of the host directives that apply to a directive. */
  2861. resolve(metadata) {
  2862. if (this.cache.has(metadata.ref.node)) {
  2863. return this.cache.get(metadata.ref.node);
  2864. }
  2865. const results = metadata.hostDirectives && metadata.hostDirectives.length > 0
  2866. ? this.walkHostDirectives(metadata.hostDirectives, [])
  2867. : EMPTY_ARRAY$1;
  2868. this.cache.set(metadata.ref.node, results);
  2869. return results;
  2870. }
  2871. /**
  2872. * Traverses all of the host directive chains and produces a flat array of
  2873. * directive metadata representing the host directives that apply to the host.
  2874. */
  2875. walkHostDirectives(directives, results) {
  2876. for (const current of directives) {
  2877. if (!checker.isHostDirectiveMetaForGlobalMode(current)) {
  2878. throw new Error('Impossible state: resolving code path in local compilation mode');
  2879. }
  2880. const hostMeta = checker.flattenInheritedDirectiveMetadata(this.metaReader, current.directive);
  2881. // This case has been checked for already and produces a diagnostic
  2882. if (hostMeta === null) {
  2883. continue;
  2884. }
  2885. if (hostMeta.hostDirectives) {
  2886. this.walkHostDirectives(hostMeta.hostDirectives, results);
  2887. }
  2888. results.push({
  2889. ...hostMeta,
  2890. matchSource: checker.MatchSource.HostDirective,
  2891. inputs: checker.ClassPropertyMapping.fromMappedObject(this.filterMappings(hostMeta.inputs, current.inputs, resolveInput)),
  2892. outputs: checker.ClassPropertyMapping.fromMappedObject(this.filterMappings(hostMeta.outputs, current.outputs, resolveOutput)),
  2893. });
  2894. }
  2895. return results;
  2896. }
  2897. /**
  2898. * Filters the class property mappings so that only the allowed ones are present.
  2899. * @param source Property mappings that should be filtered.
  2900. * @param allowedProperties Property mappings that are allowed in the final results.
  2901. * @param valueResolver Function used to resolve the value that is assigned to the final mapping.
  2902. */
  2903. filterMappings(source, allowedProperties, valueResolver) {
  2904. const result = {};
  2905. if (allowedProperties !== null) {
  2906. for (const publicName in allowedProperties) {
  2907. if (allowedProperties.hasOwnProperty(publicName)) {
  2908. const bindings = source.getByBindingPropertyName(publicName);
  2909. if (bindings !== null) {
  2910. for (const binding of bindings) {
  2911. result[binding.classPropertyName] = valueResolver(allowedProperties[publicName], binding);
  2912. }
  2913. }
  2914. }
  2915. }
  2916. }
  2917. return result;
  2918. }
  2919. }
  2920. function resolveInput(bindingName, binding) {
  2921. return {
  2922. bindingPropertyName: bindingName,
  2923. classPropertyName: binding.classPropertyName,
  2924. required: binding.required,
  2925. transform: binding.transform,
  2926. isSignal: binding.isSignal,
  2927. };
  2928. }
  2929. function resolveOutput(bindingName) {
  2930. return bindingName;
  2931. }
  2932. class PartialEvaluator {
  2933. host;
  2934. checker;
  2935. dependencyTracker;
  2936. constructor(host, checker, dependencyTracker) {
  2937. this.host = host;
  2938. this.checker = checker;
  2939. this.dependencyTracker = dependencyTracker;
  2940. }
  2941. evaluate(expr, foreignFunctionResolver) {
  2942. const interpreter = new checker.StaticInterpreter(this.host, this.checker, this.dependencyTracker);
  2943. const sourceFile = expr.getSourceFile();
  2944. return interpreter.visit(expr, {
  2945. originatingFile: sourceFile,
  2946. absoluteModuleName: null,
  2947. resolutionContext: sourceFile.fileName,
  2948. scope: new Map(),
  2949. foreignFunctionResolver,
  2950. });
  2951. }
  2952. }
  2953. function aliasTransformFactory(exportStatements) {
  2954. return () => {
  2955. return (file) => {
  2956. if (ts.isBundle(file) || !exportStatements.has(file.fileName)) {
  2957. return file;
  2958. }
  2959. const statements = [...file.statements];
  2960. exportStatements.get(file.fileName).forEach(([moduleName, symbolName], aliasName) => {
  2961. const stmt = ts.factory.createExportDeclaration(
  2962. /* modifiers */ undefined,
  2963. /* isTypeOnly */ false,
  2964. /* exportClause */ ts.factory.createNamedExports([
  2965. ts.factory.createExportSpecifier(false, symbolName, aliasName),
  2966. ]),
  2967. /* moduleSpecifier */ ts.factory.createStringLiteral(moduleName));
  2968. statements.push(stmt);
  2969. });
  2970. return ts.factory.updateSourceFile(file, statements);
  2971. };
  2972. };
  2973. }
  2974. /// <reference types="node" />
  2975. function mark() {
  2976. return process.hrtime();
  2977. }
  2978. function timeSinceInMicros(mark) {
  2979. const delta = process.hrtime(mark);
  2980. return delta[0] * 1000000 + Math.floor(delta[1] / 1000);
  2981. }
  2982. /// <reference types="node" />
  2983. /**
  2984. * A `PerfRecorder` that actively tracks performance statistics.
  2985. */
  2986. class ActivePerfRecorder {
  2987. zeroTime;
  2988. counters;
  2989. phaseTime;
  2990. bytes;
  2991. currentPhase = checker.PerfPhase.Unaccounted;
  2992. currentPhaseEntered;
  2993. /**
  2994. * Creates an `ActivePerfRecorder` with its zero point set to the current time.
  2995. */
  2996. static zeroedToNow() {
  2997. return new ActivePerfRecorder(mark());
  2998. }
  2999. constructor(zeroTime) {
  3000. this.zeroTime = zeroTime;
  3001. this.currentPhaseEntered = this.zeroTime;
  3002. this.counters = Array(checker.PerfEvent.LAST).fill(0);
  3003. this.phaseTime = Array(checker.PerfPhase.LAST).fill(0);
  3004. this.bytes = Array(checker.PerfCheckpoint.LAST).fill(0);
  3005. // Take an initial memory snapshot before any other compilation work begins.
  3006. this.memory(checker.PerfCheckpoint.Initial);
  3007. }
  3008. reset() {
  3009. this.counters = Array(checker.PerfEvent.LAST).fill(0);
  3010. this.phaseTime = Array(checker.PerfPhase.LAST).fill(0);
  3011. this.bytes = Array(checker.PerfCheckpoint.LAST).fill(0);
  3012. this.zeroTime = mark();
  3013. this.currentPhase = checker.PerfPhase.Unaccounted;
  3014. this.currentPhaseEntered = this.zeroTime;
  3015. }
  3016. memory(after) {
  3017. this.bytes[after] = process.memoryUsage().heapUsed;
  3018. }
  3019. phase(phase) {
  3020. const previous = this.currentPhase;
  3021. this.phaseTime[this.currentPhase] += timeSinceInMicros(this.currentPhaseEntered);
  3022. this.currentPhase = phase;
  3023. this.currentPhaseEntered = mark();
  3024. return previous;
  3025. }
  3026. inPhase(phase, fn) {
  3027. const previousPhase = this.phase(phase);
  3028. try {
  3029. return fn();
  3030. }
  3031. finally {
  3032. this.phase(previousPhase);
  3033. }
  3034. }
  3035. eventCount(counter, incrementBy = 1) {
  3036. this.counters[counter] += incrementBy;
  3037. }
  3038. /**
  3039. * Return the current performance metrics as a serializable object.
  3040. */
  3041. finalize() {
  3042. // Track the last segment of time spent in `this.currentPhase` in the time array.
  3043. this.phase(checker.PerfPhase.Unaccounted);
  3044. const results = {
  3045. events: {},
  3046. phases: {},
  3047. memory: {},
  3048. };
  3049. for (let i = 0; i < this.phaseTime.length; i++) {
  3050. if (this.phaseTime[i] > 0) {
  3051. results.phases[checker.PerfPhase[i]] = this.phaseTime[i];
  3052. }
  3053. }
  3054. for (let i = 0; i < this.phaseTime.length; i++) {
  3055. if (this.counters[i] > 0) {
  3056. results.events[checker.PerfEvent[i]] = this.counters[i];
  3057. }
  3058. }
  3059. for (let i = 0; i < this.bytes.length; i++) {
  3060. if (this.bytes[i] > 0) {
  3061. results.memory[checker.PerfCheckpoint[i]] = this.bytes[i];
  3062. }
  3063. }
  3064. return results;
  3065. }
  3066. }
  3067. /**
  3068. * A `PerfRecorder` that delegates to a target `PerfRecorder` which can be updated later.
  3069. *
  3070. * `DelegatingPerfRecorder` is useful when a compiler class that needs a `PerfRecorder` can outlive
  3071. * the current compilation. This is true for most compiler classes as resource-only changes reuse
  3072. * the same `NgCompiler` for a new compilation.
  3073. */
  3074. class DelegatingPerfRecorder {
  3075. target;
  3076. constructor(target) {
  3077. this.target = target;
  3078. }
  3079. eventCount(counter, incrementBy) {
  3080. this.target.eventCount(counter, incrementBy);
  3081. }
  3082. phase(phase) {
  3083. return this.target.phase(phase);
  3084. }
  3085. inPhase(phase, fn) {
  3086. // Note: this doesn't delegate to `this.target.inPhase` but instead is implemented manually here
  3087. // to avoid adding an additional frame of noise to the stack when debugging.
  3088. const previousPhase = this.target.phase(phase);
  3089. try {
  3090. return fn();
  3091. }
  3092. finally {
  3093. this.target.phase(previousPhase);
  3094. }
  3095. }
  3096. memory(after) {
  3097. this.target.memory(after);
  3098. }
  3099. reset() {
  3100. this.target.reset();
  3101. }
  3102. }
  3103. /**
  3104. * The heart of Angular compilation.
  3105. *
  3106. * The `TraitCompiler` is responsible for processing all classes in the program. Any time a
  3107. * `DecoratorHandler` matches a class, a "trait" is created to represent that Angular aspect of the
  3108. * class (such as the class having a component definition).
  3109. *
  3110. * The `TraitCompiler` transitions each trait through the various phases of compilation, culminating
  3111. * in the production of `CompileResult`s instructing the compiler to apply various mutations to the
  3112. * class (like adding fields or type declarations).
  3113. */
  3114. class TraitCompiler {
  3115. handlers;
  3116. reflector;
  3117. perf;
  3118. incrementalBuild;
  3119. compileNonExportedClasses;
  3120. compilationMode;
  3121. dtsTransforms;
  3122. semanticDepGraphUpdater;
  3123. sourceFileTypeIdentifier;
  3124. /**
  3125. * Maps class declarations to their `ClassRecord`, which tracks the Ivy traits being applied to
  3126. * those classes.
  3127. */
  3128. classes = new Map();
  3129. /**
  3130. * Maps source files to any class declaration(s) within them which have been discovered to contain
  3131. * Ivy traits.
  3132. */
  3133. fileToClasses = new Map();
  3134. /**
  3135. * Tracks which source files have been analyzed but did not contain any traits. This set allows
  3136. * the compiler to skip analyzing these files in an incremental rebuild.
  3137. */
  3138. filesWithoutTraits = new Set();
  3139. reexportMap = new Map();
  3140. handlersByName = new Map();
  3141. constructor(handlers, reflector, perf, incrementalBuild, compileNonExportedClasses, compilationMode, dtsTransforms, semanticDepGraphUpdater, sourceFileTypeIdentifier) {
  3142. this.handlers = handlers;
  3143. this.reflector = reflector;
  3144. this.perf = perf;
  3145. this.incrementalBuild = incrementalBuild;
  3146. this.compileNonExportedClasses = compileNonExportedClasses;
  3147. this.compilationMode = compilationMode;
  3148. this.dtsTransforms = dtsTransforms;
  3149. this.semanticDepGraphUpdater = semanticDepGraphUpdater;
  3150. this.sourceFileTypeIdentifier = sourceFileTypeIdentifier;
  3151. for (const handler of handlers) {
  3152. this.handlersByName.set(handler.name, handler);
  3153. }
  3154. }
  3155. analyzeSync(sf) {
  3156. this.analyze(sf, false);
  3157. }
  3158. analyzeAsync(sf) {
  3159. return this.analyze(sf, true);
  3160. }
  3161. analyze(sf, preanalyze) {
  3162. // We shouldn't analyze declaration, shim, or resource files.
  3163. if (sf.isDeclarationFile ||
  3164. this.sourceFileTypeIdentifier.isShim(sf) ||
  3165. this.sourceFileTypeIdentifier.isResource(sf)) {
  3166. return undefined;
  3167. }
  3168. // analyze() really wants to return `Promise<void>|void`, but TypeScript cannot narrow a return
  3169. // type of 'void', so `undefined` is used instead.
  3170. const promises = [];
  3171. // Local compilation does not support incremental build.
  3172. const priorWork = this.compilationMode !== checker.CompilationMode.LOCAL
  3173. ? this.incrementalBuild.priorAnalysisFor(sf)
  3174. : null;
  3175. if (priorWork !== null) {
  3176. this.perf.eventCount(checker.PerfEvent.SourceFileReuseAnalysis);
  3177. if (priorWork.length > 0) {
  3178. for (const priorRecord of priorWork) {
  3179. this.adopt(priorRecord);
  3180. }
  3181. this.perf.eventCount(checker.PerfEvent.TraitReuseAnalysis, priorWork.length);
  3182. }
  3183. else {
  3184. this.filesWithoutTraits.add(sf);
  3185. }
  3186. // Skip the rest of analysis, as this file's prior traits are being reused.
  3187. return;
  3188. }
  3189. const visit = (node) => {
  3190. if (this.reflector.isClass(node)) {
  3191. this.analyzeClass(node, preanalyze ? promises : null);
  3192. }
  3193. ts.forEachChild(node, visit);
  3194. };
  3195. visit(sf);
  3196. if (!this.fileToClasses.has(sf)) {
  3197. // If no traits were detected in the source file we record the source file itself to not have
  3198. // any traits, such that analysis of the source file can be skipped during incremental
  3199. // rebuilds.
  3200. this.filesWithoutTraits.add(sf);
  3201. }
  3202. if (preanalyze && promises.length > 0) {
  3203. return Promise.all(promises).then(() => undefined);
  3204. }
  3205. else {
  3206. return undefined;
  3207. }
  3208. }
  3209. recordFor(clazz) {
  3210. if (this.classes.has(clazz)) {
  3211. return this.classes.get(clazz);
  3212. }
  3213. else {
  3214. return null;
  3215. }
  3216. }
  3217. getAnalyzedRecords() {
  3218. const result = new Map();
  3219. for (const [sf, classes] of this.fileToClasses) {
  3220. const records = [];
  3221. for (const clazz of classes) {
  3222. records.push(this.classes.get(clazz));
  3223. }
  3224. result.set(sf, records);
  3225. }
  3226. for (const sf of this.filesWithoutTraits) {
  3227. result.set(sf, []);
  3228. }
  3229. return result;
  3230. }
  3231. /**
  3232. * Import a `ClassRecord` from a previous compilation (only to be used in global compilation
  3233. * modes)
  3234. *
  3235. * Traits from the `ClassRecord` have accurate metadata, but the `handler` is from the old program
  3236. * and needs to be updated (matching is done by name). A new pending trait is created and then
  3237. * transitioned to analyzed using the previous analysis. If the trait is in the errored state,
  3238. * instead the errors are copied over.
  3239. */
  3240. adopt(priorRecord) {
  3241. const record = {
  3242. hasPrimaryHandler: priorRecord.hasPrimaryHandler,
  3243. hasWeakHandlers: priorRecord.hasWeakHandlers,
  3244. metaDiagnostics: priorRecord.metaDiagnostics,
  3245. node: priorRecord.node,
  3246. traits: [],
  3247. };
  3248. for (const priorTrait of priorRecord.traits) {
  3249. const handler = this.handlersByName.get(priorTrait.handler.name);
  3250. let trait = checker.Trait.pending(handler, priorTrait.detected);
  3251. if (priorTrait.state === checker.TraitState.Analyzed || priorTrait.state === checker.TraitState.Resolved) {
  3252. const symbol = this.makeSymbolForTrait(handler, record.node, priorTrait.analysis);
  3253. trait = trait.toAnalyzed(priorTrait.analysis, priorTrait.analysisDiagnostics, symbol);
  3254. if (trait.analysis !== null && trait.handler.register !== undefined) {
  3255. trait.handler.register(record.node, trait.analysis);
  3256. }
  3257. }
  3258. else if (priorTrait.state === checker.TraitState.Skipped) {
  3259. trait = trait.toSkipped();
  3260. }
  3261. record.traits.push(trait);
  3262. }
  3263. this.classes.set(record.node, record);
  3264. const sf = record.node.getSourceFile();
  3265. if (!this.fileToClasses.has(sf)) {
  3266. this.fileToClasses.set(sf, new Set());
  3267. }
  3268. this.fileToClasses.get(sf).add(record.node);
  3269. }
  3270. scanClassForTraits(clazz) {
  3271. if (!this.compileNonExportedClasses && !this.reflector.isStaticallyExported(clazz)) {
  3272. return null;
  3273. }
  3274. const decorators = this.reflector.getDecoratorsOfDeclaration(clazz);
  3275. return this.detectTraits(clazz, decorators);
  3276. }
  3277. detectTraits(clazz, decorators) {
  3278. let record = this.recordFor(clazz);
  3279. let foundTraits = [];
  3280. // A set to track the non-Angular decorators in local compilation mode. An error will be issued
  3281. // if non-Angular decorators is found in local compilation mode.
  3282. const nonNgDecoratorsInLocalMode = this.compilationMode === checker.CompilationMode.LOCAL ? new Set(decorators) : null;
  3283. for (const handler of this.handlers) {
  3284. const result = handler.detect(clazz, decorators);
  3285. if (result === undefined) {
  3286. continue;
  3287. }
  3288. if (nonNgDecoratorsInLocalMode !== null && result.decorator !== null) {
  3289. nonNgDecoratorsInLocalMode.delete(result.decorator);
  3290. }
  3291. const isPrimaryHandler = handler.precedence === checker.HandlerPrecedence.PRIMARY;
  3292. const isWeakHandler = handler.precedence === checker.HandlerPrecedence.WEAK;
  3293. const trait = checker.Trait.pending(handler, result);
  3294. foundTraits.push(trait);
  3295. if (record === null) {
  3296. // This is the first handler to match this class. This path is a fast path through which
  3297. // most classes will flow.
  3298. record = {
  3299. node: clazz,
  3300. traits: [trait],
  3301. metaDiagnostics: null,
  3302. hasPrimaryHandler: isPrimaryHandler,
  3303. hasWeakHandlers: isWeakHandler,
  3304. };
  3305. this.classes.set(clazz, record);
  3306. const sf = clazz.getSourceFile();
  3307. if (!this.fileToClasses.has(sf)) {
  3308. this.fileToClasses.set(sf, new Set());
  3309. }
  3310. this.fileToClasses.get(sf).add(clazz);
  3311. }
  3312. else {
  3313. // This is at least the second handler to match this class. This is a slower path that some
  3314. // classes will go through, which validates that the set of decorators applied to the class
  3315. // is valid.
  3316. // Validate according to rules as follows:
  3317. //
  3318. // * WEAK handlers are removed if a non-WEAK handler matches.
  3319. // * Only one PRIMARY handler can match at a time. Any other PRIMARY handler matching a
  3320. // class with an existing PRIMARY handler is an error.
  3321. if (!isWeakHandler && record.hasWeakHandlers) {
  3322. // The current handler is not a WEAK handler, but the class has other WEAK handlers.
  3323. // Remove them.
  3324. record.traits = record.traits.filter((field) => field.handler.precedence !== checker.HandlerPrecedence.WEAK);
  3325. record.hasWeakHandlers = false;
  3326. }
  3327. else if (isWeakHandler && !record.hasWeakHandlers) {
  3328. // The current handler is a WEAK handler, but the class has non-WEAK handlers already.
  3329. // Drop the current one.
  3330. continue;
  3331. }
  3332. if (isPrimaryHandler && record.hasPrimaryHandler) {
  3333. // The class already has a PRIMARY handler, and another one just matched.
  3334. record.metaDiagnostics = [
  3335. {
  3336. category: ts.DiagnosticCategory.Error,
  3337. code: Number('-99' + checker.ErrorCode.DECORATOR_COLLISION),
  3338. file: checker.getSourceFile(clazz),
  3339. start: clazz.getStart(undefined, false),
  3340. length: clazz.getWidth(),
  3341. messageText: 'Two incompatible decorators on class',
  3342. },
  3343. ];
  3344. record.traits = foundTraits = [];
  3345. break;
  3346. }
  3347. // Otherwise, it's safe to accept the multiple decorators here. Update some of the metadata
  3348. // regarding this class.
  3349. record.traits.push(trait);
  3350. record.hasPrimaryHandler = record.hasPrimaryHandler || isPrimaryHandler;
  3351. }
  3352. }
  3353. if (nonNgDecoratorsInLocalMode !== null &&
  3354. nonNgDecoratorsInLocalMode.size > 0 &&
  3355. record !== null &&
  3356. record.metaDiagnostics === null) {
  3357. // Custom decorators found in local compilation mode! In this mode we don't support custom
  3358. // decorators yet. But will eventually do (b/320536434). For now a temporary error is thrown.
  3359. record.metaDiagnostics = [...nonNgDecoratorsInLocalMode].map((decorator) => ({
  3360. category: ts.DiagnosticCategory.Error,
  3361. code: Number('-99' + checker.ErrorCode.DECORATOR_UNEXPECTED),
  3362. file: checker.getSourceFile(clazz),
  3363. start: decorator.node.getStart(),
  3364. length: decorator.node.getWidth(),
  3365. messageText: 'In local compilation mode, Angular does not support custom decorators. Ensure all class decorators are from Angular.',
  3366. }));
  3367. record.traits = foundTraits = [];
  3368. }
  3369. return foundTraits.length > 0 ? foundTraits : null;
  3370. }
  3371. makeSymbolForTrait(handler, decl, analysis) {
  3372. if (analysis === null) {
  3373. return null;
  3374. }
  3375. const symbol = handler.symbol(decl, analysis);
  3376. if (symbol !== null && this.semanticDepGraphUpdater !== null) {
  3377. const isPrimary = handler.precedence === checker.HandlerPrecedence.PRIMARY;
  3378. if (!isPrimary) {
  3379. throw new Error(`AssertionError: ${handler.name} returned a symbol but is not a primary handler.`);
  3380. }
  3381. this.semanticDepGraphUpdater.registerSymbol(symbol);
  3382. }
  3383. return symbol;
  3384. }
  3385. analyzeClass(clazz, preanalyzeQueue) {
  3386. const traits = this.scanClassForTraits(clazz);
  3387. if (traits === null) {
  3388. // There are no Ivy traits on the class, so it can safely be skipped.
  3389. return;
  3390. }
  3391. for (const trait of traits) {
  3392. const analyze = () => this.analyzeTrait(clazz, trait);
  3393. let preanalysis = null;
  3394. if (preanalyzeQueue !== null && trait.handler.preanalyze !== undefined) {
  3395. // Attempt to run preanalysis. This could fail with a `FatalDiagnosticError`; catch it if it
  3396. // does.
  3397. try {
  3398. preanalysis = trait.handler.preanalyze(clazz, trait.detected.metadata) || null;
  3399. }
  3400. catch (err) {
  3401. if (err instanceof checker.FatalDiagnosticError) {
  3402. trait.toAnalyzed(null, [err.toDiagnostic()], null);
  3403. return;
  3404. }
  3405. else {
  3406. throw err;
  3407. }
  3408. }
  3409. }
  3410. if (preanalysis !== null) {
  3411. preanalyzeQueue.push(preanalysis.then(analyze));
  3412. }
  3413. else {
  3414. analyze();
  3415. }
  3416. }
  3417. }
  3418. analyzeTrait(clazz, trait) {
  3419. if (trait.state !== checker.TraitState.Pending) {
  3420. throw new Error(`Attempt to analyze trait of ${clazz.name.text} in state ${checker.TraitState[trait.state]} (expected DETECTED)`);
  3421. }
  3422. this.perf.eventCount(checker.PerfEvent.TraitAnalyze);
  3423. // Attempt analysis. This could fail with a `FatalDiagnosticError`; catch it if it does.
  3424. let result;
  3425. try {
  3426. result = trait.handler.analyze(clazz, trait.detected.metadata);
  3427. }
  3428. catch (err) {
  3429. if (err instanceof checker.FatalDiagnosticError) {
  3430. trait.toAnalyzed(null, [err.toDiagnostic()], null);
  3431. return;
  3432. }
  3433. else {
  3434. throw err;
  3435. }
  3436. }
  3437. const symbol = this.makeSymbolForTrait(trait.handler, clazz, result.analysis ?? null);
  3438. if (result.analysis !== undefined && trait.handler.register !== undefined) {
  3439. trait.handler.register(clazz, result.analysis);
  3440. }
  3441. trait = trait.toAnalyzed(result.analysis ?? null, result.diagnostics ?? null, symbol);
  3442. }
  3443. resolve() {
  3444. const classes = this.classes.keys();
  3445. for (const clazz of classes) {
  3446. const record = this.classes.get(clazz);
  3447. for (let trait of record.traits) {
  3448. const handler = trait.handler;
  3449. switch (trait.state) {
  3450. case checker.TraitState.Skipped:
  3451. continue;
  3452. case checker.TraitState.Pending:
  3453. throw new Error(`Resolving a trait that hasn't been analyzed: ${clazz.name.text} / ${trait.handler.name}`);
  3454. case checker.TraitState.Resolved:
  3455. throw new Error(`Resolving an already resolved trait`);
  3456. }
  3457. if (trait.analysis === null) {
  3458. // No analysis results, cannot further process this trait.
  3459. continue;
  3460. }
  3461. if (handler.resolve === undefined) {
  3462. // No resolution of this trait needed - it's considered successful by default.
  3463. trait = trait.toResolved(null, null);
  3464. continue;
  3465. }
  3466. let result;
  3467. try {
  3468. result = handler.resolve(clazz, trait.analysis, trait.symbol);
  3469. }
  3470. catch (err) {
  3471. if (err instanceof checker.FatalDiagnosticError) {
  3472. trait = trait.toResolved(null, [err.toDiagnostic()]);
  3473. continue;
  3474. }
  3475. else {
  3476. throw err;
  3477. }
  3478. }
  3479. trait = trait.toResolved(result.data ?? null, result.diagnostics ?? null);
  3480. if (result.reexports !== undefined) {
  3481. const fileName = clazz.getSourceFile().fileName;
  3482. if (!this.reexportMap.has(fileName)) {
  3483. this.reexportMap.set(fileName, new Map());
  3484. }
  3485. const fileReexports = this.reexportMap.get(fileName);
  3486. for (const reexport of result.reexports) {
  3487. fileReexports.set(reexport.asAlias, [reexport.fromModule, reexport.symbolName]);
  3488. }
  3489. }
  3490. }
  3491. }
  3492. }
  3493. /**
  3494. * Generate type-checking code into the `TypeCheckContext` for any components within the given
  3495. * `ts.SourceFile`.
  3496. */
  3497. typeCheck(sf, ctx) {
  3498. if (!this.fileToClasses.has(sf) || this.compilationMode === checker.CompilationMode.LOCAL) {
  3499. return;
  3500. }
  3501. for (const clazz of this.fileToClasses.get(sf)) {
  3502. const record = this.classes.get(clazz);
  3503. for (const trait of record.traits) {
  3504. if (trait.state !== checker.TraitState.Resolved) {
  3505. continue;
  3506. }
  3507. else if (trait.handler.typeCheck === undefined) {
  3508. continue;
  3509. }
  3510. if (trait.resolution !== null) {
  3511. trait.handler.typeCheck(ctx, clazz, trait.analysis, trait.resolution);
  3512. }
  3513. }
  3514. }
  3515. }
  3516. runAdditionalChecks(sf, check) {
  3517. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  3518. return [];
  3519. }
  3520. const classes = this.fileToClasses.get(sf);
  3521. if (classes === undefined) {
  3522. return [];
  3523. }
  3524. const diagnostics = [];
  3525. for (const clazz of classes) {
  3526. if (!checker.isNamedClassDeclaration(clazz)) {
  3527. continue;
  3528. }
  3529. const record = this.classes.get(clazz);
  3530. for (const trait of record.traits) {
  3531. const result = check(clazz, trait.handler);
  3532. if (result !== null) {
  3533. diagnostics.push(...result);
  3534. }
  3535. }
  3536. }
  3537. return diagnostics;
  3538. }
  3539. index(ctx) {
  3540. for (const clazz of this.classes.keys()) {
  3541. const record = this.classes.get(clazz);
  3542. for (const trait of record.traits) {
  3543. if (trait.state !== checker.TraitState.Resolved) {
  3544. // Skip traits that haven't been resolved successfully.
  3545. continue;
  3546. }
  3547. else if (trait.handler.index === undefined) {
  3548. // Skip traits that don't affect indexing.
  3549. continue;
  3550. }
  3551. if (trait.resolution !== null) {
  3552. trait.handler.index(ctx, clazz, trait.analysis, trait.resolution);
  3553. }
  3554. }
  3555. }
  3556. }
  3557. xi18n(bundle) {
  3558. for (const clazz of this.classes.keys()) {
  3559. const record = this.classes.get(clazz);
  3560. for (const trait of record.traits) {
  3561. if (trait.state !== checker.TraitState.Analyzed && trait.state !== checker.TraitState.Resolved) {
  3562. // Skip traits that haven't been analyzed successfully.
  3563. continue;
  3564. }
  3565. else if (trait.handler.xi18n === undefined) {
  3566. // Skip traits that don't support xi18n.
  3567. continue;
  3568. }
  3569. if (trait.analysis !== null) {
  3570. trait.handler.xi18n(bundle, clazz, trait.analysis);
  3571. }
  3572. }
  3573. }
  3574. }
  3575. updateResources(clazz) {
  3576. // Local compilation does not support incremental
  3577. if (this.compilationMode === checker.CompilationMode.LOCAL ||
  3578. !this.reflector.isClass(clazz) ||
  3579. !this.classes.has(clazz)) {
  3580. return;
  3581. }
  3582. const record = this.classes.get(clazz);
  3583. for (const trait of record.traits) {
  3584. if (trait.state !== checker.TraitState.Resolved || trait.handler.updateResources === undefined) {
  3585. continue;
  3586. }
  3587. trait.handler.updateResources(clazz, trait.analysis, trait.resolution);
  3588. }
  3589. }
  3590. compile(clazz, constantPool) {
  3591. const original = ts.getOriginalNode(clazz);
  3592. if (!this.reflector.isClass(clazz) ||
  3593. !this.reflector.isClass(original) ||
  3594. !this.classes.has(original)) {
  3595. return null;
  3596. }
  3597. const record = this.classes.get(original);
  3598. let res = [];
  3599. for (const trait of record.traits) {
  3600. let compileRes;
  3601. if (trait.state !== checker.TraitState.Resolved ||
  3602. containsErrors(trait.analysisDiagnostics) ||
  3603. containsErrors(trait.resolveDiagnostics)) {
  3604. // Cannot compile a trait that is not resolved, or had any errors in its declaration.
  3605. continue;
  3606. }
  3607. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  3608. // `trait.analysis` is non-null asserted here because TypeScript does not recognize that
  3609. // `Readonly<unknown>` is nullable (as `unknown` itself is nullable) due to the way that
  3610. // `Readonly` works.
  3611. compileRes = trait.handler.compileLocal(clazz, trait.analysis, trait.resolution, constantPool);
  3612. }
  3613. else {
  3614. // `trait.resolution` is non-null asserted below because TypeScript does not recognize that
  3615. // `Readonly<unknown>` is nullable (as `unknown` itself is nullable) due to the way that
  3616. // `Readonly` works.
  3617. if (this.compilationMode === checker.CompilationMode.PARTIAL &&
  3618. trait.handler.compilePartial !== undefined) {
  3619. compileRes = trait.handler.compilePartial(clazz, trait.analysis, trait.resolution);
  3620. }
  3621. else {
  3622. compileRes = trait.handler.compileFull(clazz, trait.analysis, trait.resolution, constantPool);
  3623. }
  3624. }
  3625. const compileMatchRes = compileRes;
  3626. if (Array.isArray(compileMatchRes)) {
  3627. for (const result of compileMatchRes) {
  3628. if (!res.some((r) => r.name === result.name)) {
  3629. res.push(result);
  3630. }
  3631. }
  3632. }
  3633. else if (!res.some((result) => result.name === compileMatchRes.name)) {
  3634. res.push(compileMatchRes);
  3635. }
  3636. }
  3637. // Look up the .d.ts transformer for the input file and record that at least one field was
  3638. // generated, which will allow the .d.ts to be transformed later.
  3639. this.dtsTransforms
  3640. .getIvyDeclarationTransform(original.getSourceFile())
  3641. .addFields(original, res);
  3642. // Return the instruction to the transformer so the fields will be added.
  3643. return res.length > 0 ? res : null;
  3644. }
  3645. compileHmrUpdateCallback(clazz) {
  3646. const original = ts.getOriginalNode(clazz);
  3647. if (!this.reflector.isClass(clazz) ||
  3648. !this.reflector.isClass(original) ||
  3649. !this.classes.has(original)) {
  3650. return null;
  3651. }
  3652. const record = this.classes.get(original);
  3653. for (const trait of record.traits) {
  3654. // Cannot compile a trait that is not resolved, or had any errors in its declaration.
  3655. if (trait.state === checker.TraitState.Resolved &&
  3656. trait.handler.compileHmrUpdateDeclaration !== undefined &&
  3657. !containsErrors(trait.analysisDiagnostics) &&
  3658. !containsErrors(trait.resolveDiagnostics)) {
  3659. return trait.handler.compileHmrUpdateDeclaration(clazz, trait.analysis, trait.resolution);
  3660. }
  3661. }
  3662. return null;
  3663. }
  3664. decoratorsFor(node) {
  3665. const original = ts.getOriginalNode(node);
  3666. if (!this.reflector.isClass(original) || !this.classes.has(original)) {
  3667. return [];
  3668. }
  3669. const record = this.classes.get(original);
  3670. const decorators = [];
  3671. for (const trait of record.traits) {
  3672. // In global compilation mode skip the non-resolved traits.
  3673. if (this.compilationMode !== checker.CompilationMode.LOCAL && trait.state !== checker.TraitState.Resolved) {
  3674. continue;
  3675. }
  3676. if (trait.detected.trigger !== null && ts.isDecorator(trait.detected.trigger)) {
  3677. decorators.push(trait.detected.trigger);
  3678. }
  3679. }
  3680. return decorators;
  3681. }
  3682. get diagnostics() {
  3683. const diagnostics = [];
  3684. for (const clazz of this.classes.keys()) {
  3685. const record = this.classes.get(clazz);
  3686. if (record.metaDiagnostics !== null) {
  3687. diagnostics.push(...record.metaDiagnostics);
  3688. }
  3689. for (const trait of record.traits) {
  3690. if ((trait.state === checker.TraitState.Analyzed || trait.state === checker.TraitState.Resolved) &&
  3691. trait.analysisDiagnostics !== null) {
  3692. diagnostics.push(...trait.analysisDiagnostics);
  3693. }
  3694. if (trait.state === checker.TraitState.Resolved) {
  3695. diagnostics.push(...(trait.resolveDiagnostics ?? []));
  3696. }
  3697. }
  3698. }
  3699. return diagnostics;
  3700. }
  3701. get exportStatements() {
  3702. return this.reexportMap;
  3703. }
  3704. }
  3705. function containsErrors(diagnostics) {
  3706. return (diagnostics !== null &&
  3707. diagnostics.some((diag) => diag.category === ts.DiagnosticCategory.Error));
  3708. }
  3709. /**
  3710. * Keeps track of `DtsTransform`s per source file, so that it is known which source files need to
  3711. * have their declaration file transformed.
  3712. */
  3713. class DtsTransformRegistry {
  3714. ivyDeclarationTransforms = new Map();
  3715. getIvyDeclarationTransform(sf) {
  3716. if (!this.ivyDeclarationTransforms.has(sf)) {
  3717. this.ivyDeclarationTransforms.set(sf, new IvyDeclarationDtsTransform());
  3718. }
  3719. return this.ivyDeclarationTransforms.get(sf);
  3720. }
  3721. /**
  3722. * Gets the dts transforms to be applied for the given source file, or `null` if no transform is
  3723. * necessary.
  3724. */
  3725. getAllTransforms(sf) {
  3726. // No need to transform if it's not a declarations file, or if no changes have been requested
  3727. // to the input file. Due to the way TypeScript afterDeclarations transformers work, the
  3728. // `ts.SourceFile` path is the same as the original .ts. The only way we know it's actually a
  3729. // declaration file is via the `isDeclarationFile` property.
  3730. if (!sf.isDeclarationFile) {
  3731. return null;
  3732. }
  3733. const originalSf = ts.getOriginalNode(sf);
  3734. let transforms = null;
  3735. if (this.ivyDeclarationTransforms.has(originalSf)) {
  3736. transforms = [];
  3737. transforms.push(this.ivyDeclarationTransforms.get(originalSf));
  3738. }
  3739. return transforms;
  3740. }
  3741. }
  3742. function declarationTransformFactory(transformRegistry, reflector, refEmitter, importRewriter) {
  3743. return (context) => {
  3744. const transformer = new DtsTransformer(context, reflector, refEmitter, importRewriter);
  3745. return (fileOrBundle) => {
  3746. if (ts.isBundle(fileOrBundle)) {
  3747. // Only attempt to transform source files.
  3748. return fileOrBundle;
  3749. }
  3750. const transforms = transformRegistry.getAllTransforms(fileOrBundle);
  3751. if (transforms === null) {
  3752. return fileOrBundle;
  3753. }
  3754. return transformer.transform(fileOrBundle, transforms);
  3755. };
  3756. };
  3757. }
  3758. /**
  3759. * Processes .d.ts file text and adds static field declarations, with types.
  3760. */
  3761. class DtsTransformer {
  3762. ctx;
  3763. reflector;
  3764. refEmitter;
  3765. importRewriter;
  3766. constructor(ctx, reflector, refEmitter, importRewriter) {
  3767. this.ctx = ctx;
  3768. this.reflector = reflector;
  3769. this.refEmitter = refEmitter;
  3770. this.importRewriter = importRewriter;
  3771. }
  3772. /**
  3773. * Transform the declaration file and add any declarations which were recorded.
  3774. */
  3775. transform(sf, transforms) {
  3776. const imports = new checker.ImportManager({
  3777. ...checker.presetImportManagerForceNamespaceImports,
  3778. rewriter: this.importRewriter,
  3779. });
  3780. const visitor = (node) => {
  3781. if (ts.isClassDeclaration(node)) {
  3782. return this.transformClassDeclaration(node, transforms, imports);
  3783. }
  3784. else if (ts.isFunctionDeclaration(node)) {
  3785. return this.transformFunctionDeclaration(node, transforms, imports);
  3786. }
  3787. else {
  3788. // Otherwise return node as is.
  3789. return ts.visitEachChild(node, visitor, this.ctx);
  3790. }
  3791. };
  3792. // Recursively scan through the AST and process all nodes as desired.
  3793. sf = ts.visitNode(sf, visitor, ts.isSourceFile) || sf;
  3794. // Update/insert needed imports.
  3795. return imports.transformTsFile(this.ctx, sf);
  3796. }
  3797. transformClassDeclaration(clazz, transforms, imports) {
  3798. let elements = clazz.members;
  3799. let elementsChanged = false;
  3800. for (const transform of transforms) {
  3801. if (transform.transformClassElement !== undefined) {
  3802. for (let i = 0; i < elements.length; i++) {
  3803. const res = transform.transformClassElement(elements[i], imports);
  3804. if (res !== elements[i]) {
  3805. if (!elementsChanged) {
  3806. elements = [...elements];
  3807. elementsChanged = true;
  3808. }
  3809. elements[i] = res;
  3810. }
  3811. }
  3812. }
  3813. }
  3814. let newClazz = clazz;
  3815. for (const transform of transforms) {
  3816. if (transform.transformClass !== undefined) {
  3817. // If no DtsTransform has changed the class yet, then the (possibly mutated) elements have
  3818. // not yet been incorporated. Otherwise, `newClazz.members` holds the latest class members.
  3819. const inputMembers = clazz === newClazz ? elements : newClazz.members;
  3820. newClazz = transform.transformClass(newClazz, inputMembers, this.reflector, this.refEmitter, imports);
  3821. }
  3822. }
  3823. // If some elements have been transformed but the class itself has not been transformed, create
  3824. // an updated class declaration with the updated elements.
  3825. if (elementsChanged && clazz === newClazz) {
  3826. newClazz = ts.factory.updateClassDeclaration(
  3827. /* node */ clazz,
  3828. /* modifiers */ clazz.modifiers,
  3829. /* name */ clazz.name,
  3830. /* typeParameters */ clazz.typeParameters,
  3831. /* heritageClauses */ clazz.heritageClauses,
  3832. /* members */ elements);
  3833. }
  3834. return newClazz;
  3835. }
  3836. transformFunctionDeclaration(declaration, transforms, imports) {
  3837. let newDecl = declaration;
  3838. for (const transform of transforms) {
  3839. if (transform.transformFunctionDeclaration !== undefined) {
  3840. newDecl = transform.transformFunctionDeclaration(newDecl, imports);
  3841. }
  3842. }
  3843. return newDecl;
  3844. }
  3845. }
  3846. class IvyDeclarationDtsTransform {
  3847. declarationFields = new Map();
  3848. addFields(decl, fields) {
  3849. this.declarationFields.set(decl, fields);
  3850. }
  3851. transformClass(clazz, members, reflector, refEmitter, imports) {
  3852. const original = ts.getOriginalNode(clazz);
  3853. if (!this.declarationFields.has(original)) {
  3854. return clazz;
  3855. }
  3856. const fields = this.declarationFields.get(original);
  3857. const newMembers = fields.map((decl) => {
  3858. const modifiers = [ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)];
  3859. const typeRef = checker.translateType(decl.type, original.getSourceFile(), reflector, refEmitter, imports);
  3860. markForEmitAsSingleLine(typeRef);
  3861. return ts.factory.createPropertyDeclaration(
  3862. /* modifiers */ modifiers,
  3863. /* name */ decl.name,
  3864. /* questionOrExclamationToken */ undefined,
  3865. /* type */ typeRef,
  3866. /* initializer */ undefined);
  3867. });
  3868. return ts.factory.updateClassDeclaration(
  3869. /* node */ clazz,
  3870. /* modifiers */ clazz.modifiers,
  3871. /* name */ clazz.name,
  3872. /* typeParameters */ clazz.typeParameters,
  3873. /* heritageClauses */ clazz.heritageClauses,
  3874. /* members */ [...members, ...newMembers]);
  3875. }
  3876. }
  3877. function markForEmitAsSingleLine(node) {
  3878. ts.setEmitFlags(node, ts.EmitFlags.SingleLine);
  3879. ts.forEachChild(node, markForEmitAsSingleLine);
  3880. }
  3881. /**
  3882. * Visit a node with the given visitor and return a transformed copy.
  3883. */
  3884. function visit(node, visitor, context) {
  3885. return visitor._visit(node, context);
  3886. }
  3887. /**
  3888. * Abstract base class for visitors, which processes certain nodes specially to allow insertion
  3889. * of other nodes before them.
  3890. */
  3891. class Visitor {
  3892. /**
  3893. * Maps statements to an array of statements that should be inserted before them.
  3894. */
  3895. _before = new Map();
  3896. /**
  3897. * Maps statements to an array of statements that should be inserted after them.
  3898. */
  3899. _after = new Map();
  3900. _visitListEntryNode(node, visitor) {
  3901. const result = visitor(node);
  3902. if (result.before !== undefined) {
  3903. // Record that some nodes should be inserted before the given declaration. The declaration's
  3904. // parent's _visit call is responsible for performing this insertion.
  3905. this._before.set(result.node, result.before);
  3906. }
  3907. if (result.after !== undefined) {
  3908. // Same with nodes that should be inserted after.
  3909. this._after.set(result.node, result.after);
  3910. }
  3911. return result.node;
  3912. }
  3913. /**
  3914. * Visit types of nodes which don't have their own explicit visitor.
  3915. */
  3916. visitOtherNode(node) {
  3917. return node;
  3918. }
  3919. /**
  3920. * @internal
  3921. */
  3922. _visit(node, context) {
  3923. // First, visit the node. visitedNode starts off as `null` but should be set after visiting
  3924. // is completed.
  3925. let visitedNode = null;
  3926. node = ts.visitEachChild(node, (child) => child && this._visit(child, context), context);
  3927. if (ts.isClassDeclaration(node)) {
  3928. visitedNode = this._visitListEntryNode(node, (node) => this.visitClassDeclaration(node));
  3929. }
  3930. else {
  3931. visitedNode = this.visitOtherNode(node);
  3932. }
  3933. // If the visited node has a `statements` array then process them, maybe replacing the visited
  3934. // node and adding additional statements.
  3935. if (visitedNode && (ts.isBlock(visitedNode) || ts.isSourceFile(visitedNode))) {
  3936. visitedNode = this._maybeProcessStatements(visitedNode);
  3937. }
  3938. return visitedNode;
  3939. }
  3940. _maybeProcessStatements(node) {
  3941. // Shortcut - if every statement doesn't require nodes to be prepended or appended,
  3942. // this is a no-op.
  3943. if (node.statements.every((stmt) => !this._before.has(stmt) && !this._after.has(stmt))) {
  3944. return node;
  3945. }
  3946. // Build a new list of statements and patch it onto the clone.
  3947. const newStatements = [];
  3948. node.statements.forEach((stmt) => {
  3949. if (this._before.has(stmt)) {
  3950. newStatements.push(...this._before.get(stmt));
  3951. this._before.delete(stmt);
  3952. }
  3953. newStatements.push(stmt);
  3954. if (this._after.has(stmt)) {
  3955. newStatements.push(...this._after.get(stmt));
  3956. this._after.delete(stmt);
  3957. }
  3958. });
  3959. const statementsArray = ts.factory.createNodeArray(newStatements, node.statements.hasTrailingComma);
  3960. if (ts.isBlock(node)) {
  3961. return ts.factory.updateBlock(node, statementsArray);
  3962. }
  3963. else {
  3964. return ts.factory.updateSourceFile(node, statementsArray, node.isDeclarationFile, node.referencedFiles, node.typeReferenceDirectives, node.hasNoDefaultLib, node.libReferenceDirectives);
  3965. }
  3966. }
  3967. }
  3968. const NO_DECORATORS = new Set();
  3969. const CLOSURE_FILE_OVERVIEW_REGEXP = /\s+@fileoverview\s+/i;
  3970. function ivyTransformFactory(compilation, reflector, importRewriter, defaultImportTracker, localCompilationExtraImportsTracker, perf, isCore, isClosureCompilerEnabled) {
  3971. const recordWrappedNode = createRecorderFn(defaultImportTracker);
  3972. return (context) => {
  3973. return (file) => {
  3974. return perf.inPhase(checker.PerfPhase.Compile, () => transformIvySourceFile(compilation, context, reflector, importRewriter, localCompilationExtraImportsTracker, file, isCore, isClosureCompilerEnabled, recordWrappedNode));
  3975. };
  3976. };
  3977. }
  3978. /**
  3979. * Visits all classes, performs Ivy compilation where Angular decorators are present and collects
  3980. * result in a Map that associates a ts.ClassDeclaration with Ivy compilation results. This visitor
  3981. * does NOT perform any TS transformations.
  3982. */
  3983. class IvyCompilationVisitor extends Visitor {
  3984. compilation;
  3985. constantPool;
  3986. classCompilationMap = new Map();
  3987. deferrableImports = new Set();
  3988. constructor(compilation, constantPool) {
  3989. super();
  3990. this.compilation = compilation;
  3991. this.constantPool = constantPool;
  3992. }
  3993. visitClassDeclaration(node) {
  3994. // Determine if this class has an Ivy field that needs to be added, and compile the field
  3995. // to an expression if so.
  3996. const result = this.compilation.compile(node, this.constantPool);
  3997. if (result !== null) {
  3998. this.classCompilationMap.set(node, result);
  3999. // Collect all deferrable imports declarations into a single set,
  4000. // so that we can pass it to the transform visitor that will drop
  4001. // corresponding regular import declarations.
  4002. for (const classResult of result) {
  4003. if (classResult.deferrableImports !== null && classResult.deferrableImports.size > 0) {
  4004. classResult.deferrableImports.forEach((importDecl) => this.deferrableImports.add(importDecl));
  4005. }
  4006. }
  4007. }
  4008. return { node };
  4009. }
  4010. }
  4011. /**
  4012. * Visits all classes and performs transformation of corresponding TS nodes based on the Ivy
  4013. * compilation results (provided as an argument).
  4014. */
  4015. class IvyTransformationVisitor extends Visitor {
  4016. compilation;
  4017. classCompilationMap;
  4018. reflector;
  4019. importManager;
  4020. recordWrappedNodeExpr;
  4021. isClosureCompilerEnabled;
  4022. isCore;
  4023. deferrableImports;
  4024. constructor(compilation, classCompilationMap, reflector, importManager, recordWrappedNodeExpr, isClosureCompilerEnabled, isCore, deferrableImports) {
  4025. super();
  4026. this.compilation = compilation;
  4027. this.classCompilationMap = classCompilationMap;
  4028. this.reflector = reflector;
  4029. this.importManager = importManager;
  4030. this.recordWrappedNodeExpr = recordWrappedNodeExpr;
  4031. this.isClosureCompilerEnabled = isClosureCompilerEnabled;
  4032. this.isCore = isCore;
  4033. this.deferrableImports = deferrableImports;
  4034. }
  4035. visitClassDeclaration(node) {
  4036. // If this class is not registered in the map, it means that it doesn't have Angular decorators,
  4037. // thus no further processing is required.
  4038. if (!this.classCompilationMap.has(node)) {
  4039. return { node };
  4040. }
  4041. const translateOptions = {
  4042. recordWrappedNode: this.recordWrappedNodeExpr,
  4043. annotateForClosureCompiler: this.isClosureCompilerEnabled,
  4044. };
  4045. // There is at least one field to add.
  4046. const statements = [];
  4047. const members = [...node.members];
  4048. // Note: Class may be already transformed by e.g. Tsickle and
  4049. // not have a direct reference to the source file.
  4050. const sourceFile = ts.getOriginalNode(node).getSourceFile();
  4051. for (const field of this.classCompilationMap.get(node)) {
  4052. // Type-only member.
  4053. if (field.initializer === null) {
  4054. continue;
  4055. }
  4056. // Translate the initializer for the field into TS nodes.
  4057. const exprNode = checker.translateExpression(sourceFile, field.initializer, this.importManager, translateOptions);
  4058. // Create a static property declaration for the new field.
  4059. const property = ts.factory.createPropertyDeclaration([ts.factory.createToken(ts.SyntaxKind.StaticKeyword)], field.name, undefined, undefined, exprNode);
  4060. if (this.isClosureCompilerEnabled) {
  4061. // Closure compiler transforms the form `Service.ɵprov = X` into `Service$ɵprov = X`. To
  4062. // prevent this transformation, such assignments need to be annotated with @nocollapse.
  4063. // Note that tsickle is typically responsible for adding such annotations, however it
  4064. // doesn't yet handle synthetic fields added during other transformations.
  4065. ts.addSyntheticLeadingComment(property, ts.SyntaxKind.MultiLineCommentTrivia, '* @nocollapse ',
  4066. /* hasTrailingNewLine */ false);
  4067. }
  4068. field.statements
  4069. .map((stmt) => checker.translateStatement(sourceFile, stmt, this.importManager, translateOptions))
  4070. .forEach((stmt) => statements.push(stmt));
  4071. members.push(property);
  4072. }
  4073. const filteredDecorators =
  4074. // Remove the decorator which triggered this compilation, leaving the others alone.
  4075. maybeFilterDecorator(ts.getDecorators(node), this.compilation.decoratorsFor(node));
  4076. const nodeModifiers = ts.getModifiers(node);
  4077. let updatedModifiers;
  4078. if (filteredDecorators?.length || nodeModifiers?.length) {
  4079. updatedModifiers = [...(filteredDecorators || []), ...(nodeModifiers || [])];
  4080. }
  4081. // Replace the class declaration with an updated version.
  4082. node = ts.factory.updateClassDeclaration(node, updatedModifiers, node.name, node.typeParameters, node.heritageClauses || [],
  4083. // Map over the class members and remove any Angular decorators from them.
  4084. members.map((member) => this._stripAngularDecorators(member)));
  4085. return { node, after: statements };
  4086. }
  4087. visitOtherNode(node) {
  4088. if (ts.isImportDeclaration(node) && this.deferrableImports.has(node)) {
  4089. // Return `null` as an indication that this node should not be present
  4090. // in the final AST. Symbols from this import would be imported via
  4091. // dynamic imports.
  4092. return null;
  4093. }
  4094. return node;
  4095. }
  4096. /**
  4097. * Return all decorators on a `Declaration` which are from @angular/core, or an empty set if none
  4098. * are.
  4099. */
  4100. _angularCoreDecorators(decl) {
  4101. const decorators = this.reflector.getDecoratorsOfDeclaration(decl);
  4102. if (decorators === null) {
  4103. return NO_DECORATORS;
  4104. }
  4105. const coreDecorators = decorators
  4106. .filter((dec) => this.isCore || isFromAngularCore(dec))
  4107. .map((dec) => dec.node);
  4108. if (coreDecorators.length > 0) {
  4109. return new Set(coreDecorators);
  4110. }
  4111. else {
  4112. return NO_DECORATORS;
  4113. }
  4114. }
  4115. _nonCoreDecoratorsOnly(node) {
  4116. const decorators = ts.getDecorators(node);
  4117. // Shortcut if the node has no decorators.
  4118. if (decorators === undefined) {
  4119. return undefined;
  4120. }
  4121. // Build a Set of the decorators on this node from @angular/core.
  4122. const coreDecorators = this._angularCoreDecorators(node);
  4123. if (coreDecorators.size === decorators.length) {
  4124. // If all decorators are to be removed, return `undefined`.
  4125. return undefined;
  4126. }
  4127. else if (coreDecorators.size === 0) {
  4128. // If no decorators need to be removed, return the original decorators array.
  4129. return nodeArrayFromDecoratorsArray(decorators);
  4130. }
  4131. // Filter out the core decorators.
  4132. const filtered = decorators.filter((dec) => !coreDecorators.has(dec));
  4133. // If no decorators survive, return `undefined`. This can only happen if a core decorator is
  4134. // repeated on the node.
  4135. if (filtered.length === 0) {
  4136. return undefined;
  4137. }
  4138. // Create a new `NodeArray` with the filtered decorators that sourcemaps back to the original.
  4139. return nodeArrayFromDecoratorsArray(filtered);
  4140. }
  4141. /**
  4142. * Remove Angular decorators from a `ts.Node` in a shallow manner.
  4143. *
  4144. * This will remove decorators from class elements (getters, setters, properties, methods) as well
  4145. * as parameters of constructors.
  4146. */
  4147. _stripAngularDecorators(node) {
  4148. const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
  4149. const nonCoreDecorators = ts.canHaveDecorators(node)
  4150. ? this._nonCoreDecoratorsOnly(node)
  4151. : undefined;
  4152. const combinedModifiers = [...(nonCoreDecorators || []), ...(modifiers || [])];
  4153. if (ts.isParameter(node)) {
  4154. // Strip decorators from parameters (probably of the constructor).
  4155. node = ts.factory.updateParameterDeclaration(node, combinedModifiers, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer);
  4156. }
  4157. else if (ts.isMethodDeclaration(node)) {
  4158. // Strip decorators of methods.
  4159. node = ts.factory.updateMethodDeclaration(node, combinedModifiers, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body);
  4160. }
  4161. else if (ts.isPropertyDeclaration(node)) {
  4162. // Strip decorators of properties.
  4163. node = ts.factory.updatePropertyDeclaration(node, combinedModifiers, node.name, node.questionToken, node.type, node.initializer);
  4164. }
  4165. else if (ts.isGetAccessor(node)) {
  4166. // Strip decorators of getters.
  4167. node = ts.factory.updateGetAccessorDeclaration(node, combinedModifiers, node.name, node.parameters, node.type, node.body);
  4168. }
  4169. else if (ts.isSetAccessor(node)) {
  4170. // Strip decorators of setters.
  4171. node = ts.factory.updateSetAccessorDeclaration(node, combinedModifiers, node.name, node.parameters, node.body);
  4172. }
  4173. else if (ts.isConstructorDeclaration(node)) {
  4174. // For constructors, strip decorators of the parameters.
  4175. const parameters = node.parameters.map((param) => this._stripAngularDecorators(param));
  4176. node = ts.factory.updateConstructorDeclaration(node, modifiers, parameters, node.body);
  4177. }
  4178. return node;
  4179. }
  4180. }
  4181. /**
  4182. * A transformer which operates on ts.SourceFiles and applies changes from an `IvyCompilation`.
  4183. */
  4184. function transformIvySourceFile(compilation, context, reflector, importRewriter, localCompilationExtraImportsTracker, file, isCore, isClosureCompilerEnabled, recordWrappedNode) {
  4185. const constantPool = new checker.ConstantPool(isClosureCompilerEnabled);
  4186. const importManager = new checker.ImportManager({
  4187. ...checker.presetImportManagerForceNamespaceImports,
  4188. rewriter: importRewriter,
  4189. });
  4190. // The transformation process consists of 2 steps:
  4191. //
  4192. // 1. Visit all classes, perform compilation and collect the results.
  4193. // 2. Perform actual transformation of required TS nodes using compilation results from the first
  4194. // step.
  4195. //
  4196. // This is needed to have all `o.Expression`s generated before any TS transforms happen. This
  4197. // allows `ConstantPool` to properly identify expressions that can be shared across multiple
  4198. // components declared in the same file.
  4199. // Step 1. Go though all classes in AST, perform compilation and collect the results.
  4200. const compilationVisitor = new IvyCompilationVisitor(compilation, constantPool);
  4201. visit(file, compilationVisitor, context);
  4202. // Step 2. Scan through the AST again and perform transformations based on Ivy compilation
  4203. // results obtained at Step 1.
  4204. const transformationVisitor = new IvyTransformationVisitor(compilation, compilationVisitor.classCompilationMap, reflector, importManager, recordWrappedNode, isClosureCompilerEnabled, isCore, compilationVisitor.deferrableImports);
  4205. let sf = visit(file, transformationVisitor, context);
  4206. // Generate the constant statements first, as they may involve adding additional imports
  4207. // to the ImportManager.
  4208. const downlevelTranslatedCode = getLocalizeCompileTarget(context) < ts.ScriptTarget.ES2015;
  4209. const constants = constantPool.statements.map((stmt) => checker.translateStatement(file, stmt, importManager, {
  4210. recordWrappedNode,
  4211. downlevelTaggedTemplates: downlevelTranslatedCode,
  4212. downlevelVariableDeclarations: downlevelTranslatedCode,
  4213. annotateForClosureCompiler: isClosureCompilerEnabled,
  4214. }));
  4215. // Preserve @fileoverview comments required by Closure, since the location might change as a
  4216. // result of adding extra imports and constant pool statements.
  4217. const fileOverviewMeta = isClosureCompilerEnabled ? getFileOverviewComment(sf.statements) : null;
  4218. // Add extra imports.
  4219. if (localCompilationExtraImportsTracker !== null) {
  4220. for (const moduleName of localCompilationExtraImportsTracker.getImportsForFile(sf)) {
  4221. importManager.addSideEffectImport(sf, moduleName);
  4222. }
  4223. }
  4224. // Add new imports for this file.
  4225. sf = importManager.transformTsFile(context, sf, constants);
  4226. if (fileOverviewMeta !== null) {
  4227. sf = insertFileOverviewComment(sf, fileOverviewMeta);
  4228. }
  4229. return sf;
  4230. }
  4231. /**
  4232. * Compute the correct target output for `$localize` messages generated by Angular
  4233. *
  4234. * In some versions of TypeScript, the transformation of synthetic `$localize` tagged template
  4235. * literals is broken. See https://github.com/microsoft/TypeScript/issues/38485
  4236. *
  4237. * Here we compute what the expected final output target of the compilation will
  4238. * be so that we can generate ES5 compliant `$localize` calls instead of relying upon TS to do the
  4239. * downleveling for us.
  4240. */
  4241. function getLocalizeCompileTarget(context) {
  4242. const target = context.getCompilerOptions().target || ts.ScriptTarget.ES2015;
  4243. return target !== ts.ScriptTarget.JSON ? target : ts.ScriptTarget.ES2015;
  4244. }
  4245. function getFileOverviewComment(statements) {
  4246. if (statements.length > 0) {
  4247. const host = statements[0];
  4248. let trailing = false;
  4249. let comments = ts.getSyntheticLeadingComments(host);
  4250. // If @fileoverview tag is not found in source file, tsickle produces fake node with trailing
  4251. // comment and inject it at the very beginning of the generated file. So we need to check for
  4252. // leading as well as trailing comments.
  4253. if (!comments || comments.length === 0) {
  4254. trailing = true;
  4255. comments = ts.getSyntheticTrailingComments(host);
  4256. }
  4257. if (comments && comments.length > 0 && CLOSURE_FILE_OVERVIEW_REGEXP.test(comments[0].text)) {
  4258. return { comments, host, trailing };
  4259. }
  4260. }
  4261. return null;
  4262. }
  4263. function insertFileOverviewComment(sf, fileoverview) {
  4264. const { comments, host, trailing } = fileoverview;
  4265. // If host statement is no longer the first one, it means that extra statements were added at the
  4266. // very beginning, so we need to relocate @fileoverview comment and cleanup the original statement
  4267. // that hosted it.
  4268. if (sf.statements.length > 0 && host !== sf.statements[0]) {
  4269. if (trailing) {
  4270. ts.setSyntheticTrailingComments(host, undefined);
  4271. }
  4272. else {
  4273. ts.setSyntheticLeadingComments(host, undefined);
  4274. }
  4275. // Note: Do not use the first statement as it may be elided at runtime.
  4276. // E.g. an import declaration that is type only.
  4277. const commentNode = ts.factory.createNotEmittedStatement(sf);
  4278. ts.setSyntheticLeadingComments(commentNode, comments);
  4279. return ts.factory.updateSourceFile(sf, [commentNode, ...sf.statements], sf.isDeclarationFile, sf.referencedFiles, sf.typeReferenceDirectives, sf.hasNoDefaultLib, sf.libReferenceDirectives);
  4280. }
  4281. return sf;
  4282. }
  4283. function maybeFilterDecorator(decorators, toRemove) {
  4284. if (decorators === undefined) {
  4285. return undefined;
  4286. }
  4287. const filtered = decorators.filter((dec) => toRemove.find((decToRemove) => ts.getOriginalNode(dec) === decToRemove) === undefined);
  4288. if (filtered.length === 0) {
  4289. return undefined;
  4290. }
  4291. return ts.factory.createNodeArray(filtered);
  4292. }
  4293. function isFromAngularCore(decorator) {
  4294. return decorator.import !== null && decorator.import.from === '@angular/core';
  4295. }
  4296. function createRecorderFn(defaultImportTracker) {
  4297. return (node) => {
  4298. const importDecl = checker.getDefaultImportDeclaration(node);
  4299. if (importDecl !== null) {
  4300. defaultImportTracker.recordUsedImport(importDecl);
  4301. }
  4302. };
  4303. }
  4304. /** Creates a `NodeArray` with the correct offsets from an array of decorators. */
  4305. function nodeArrayFromDecoratorsArray(decorators) {
  4306. const array = ts.factory.createNodeArray(decorators);
  4307. if (array.length > 0) {
  4308. array.pos = decorators[0].pos;
  4309. array.end = decorators[decorators.length - 1].end;
  4310. }
  4311. return array;
  4312. }
  4313. function resolveEnumValue(evaluator, metadata, field, enumSymbolName, isCore) {
  4314. let resolved = null;
  4315. if (metadata.has(field)) {
  4316. const expr = metadata.get(field);
  4317. const value = evaluator.evaluate(expr);
  4318. if (value instanceof checker.EnumValue &&
  4319. checker.isAngularCoreReferenceWithPotentialAliasing(value.enumRef, enumSymbolName, isCore)) {
  4320. resolved = value.resolved;
  4321. }
  4322. else {
  4323. throw checker.createValueHasWrongTypeError(expr, value, `${field} must be a member of ${enumSymbolName} enum from @angular/core`);
  4324. }
  4325. }
  4326. return resolved;
  4327. }
  4328. /**
  4329. * Resolves a EncapsulationEnum expression locally on best effort without having to calculate the
  4330. * reference. This suites local compilation mode where each file is compiled individually.
  4331. *
  4332. * The static analysis is still needed in local compilation mode since the value of this enum will
  4333. * be used later to decide the generated code for styles.
  4334. */
  4335. function resolveEncapsulationEnumValueLocally(expr) {
  4336. if (!expr) {
  4337. return null;
  4338. }
  4339. const exprText = expr.getText().trim();
  4340. for (const key in checker.ViewEncapsulation) {
  4341. if (!Number.isNaN(Number(key))) {
  4342. continue;
  4343. }
  4344. const suffix = `ViewEncapsulation.${key}`;
  4345. // Check whether the enum is imported by name or used by import namespace (e.g.,
  4346. // core.ViewEncapsulation.None)
  4347. if (exprText === suffix || exprText.endsWith(`.${suffix}`)) {
  4348. const ans = Number(checker.ViewEncapsulation[key]);
  4349. return ans;
  4350. }
  4351. }
  4352. return null;
  4353. }
  4354. /** Determines if the result of an evaluation is a string array. */
  4355. function isStringArray(resolvedValue) {
  4356. return Array.isArray(resolvedValue) && resolvedValue.every((elem) => typeof elem === 'string');
  4357. }
  4358. function resolveLiteral(decorator, literalCache) {
  4359. if (literalCache.has(decorator)) {
  4360. return literalCache.get(decorator);
  4361. }
  4362. if (decorator.args === null || decorator.args.length !== 1) {
  4363. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_ARITY_WRONG, decorator.node, `Incorrect number of arguments to @${decorator.name} decorator`);
  4364. }
  4365. const meta = checker.unwrapExpression(decorator.args[0]);
  4366. if (!ts.isObjectLiteralExpression(meta)) {
  4367. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, `Decorator argument must be literal.`);
  4368. }
  4369. literalCache.set(decorator, meta);
  4370. return meta;
  4371. }
  4372. function compileNgFactoryDefField(metadata) {
  4373. const res = checker.compileFactoryFunction(metadata);
  4374. return {
  4375. name: 'ɵfac',
  4376. initializer: res.expression,
  4377. statements: res.statements,
  4378. type: res.type,
  4379. deferrableImports: null,
  4380. };
  4381. }
  4382. function compileDeclareFactory(metadata) {
  4383. const res = compileDeclareFactoryFunction(metadata);
  4384. return {
  4385. name: 'ɵfac',
  4386. initializer: res.expression,
  4387. statements: res.statements,
  4388. type: res.type,
  4389. deferrableImports: null,
  4390. };
  4391. }
  4392. /**
  4393. * Registry that keeps track of classes that can be constructed via dependency injection (e.g.
  4394. * injectables, directives, pipes).
  4395. */
  4396. class InjectableClassRegistry {
  4397. host;
  4398. isCore;
  4399. classes = new Map();
  4400. constructor(host, isCore) {
  4401. this.host = host;
  4402. this.isCore = isCore;
  4403. }
  4404. registerInjectable(declaration, meta) {
  4405. this.classes.set(declaration, meta);
  4406. }
  4407. getInjectableMeta(declaration) {
  4408. // Figure out whether the class is injectable based on the registered classes, otherwise
  4409. // fall back to looking at its members since we might not have been able to register the class
  4410. // if it was compiled in another compilation unit.
  4411. if (this.classes.has(declaration)) {
  4412. return this.classes.get(declaration);
  4413. }
  4414. if (!checker.hasInjectableFields(declaration, this.host)) {
  4415. return null;
  4416. }
  4417. const ctorDeps = checker.getConstructorDependencies(declaration, this.host, this.isCore);
  4418. const meta = {
  4419. ctorDeps: checker.unwrapConstructorDependencies(ctorDeps),
  4420. };
  4421. this.classes.set(declaration, meta);
  4422. return meta;
  4423. }
  4424. }
  4425. /**
  4426. * Given a class declaration, generate a call to `setClassMetadata` with the Angular metadata
  4427. * present on the class or its member fields. An ngDevMode guard is used to allow the call to be
  4428. * tree-shaken away, as the `setClassMetadata` invocation is only needed for testing purposes.
  4429. *
  4430. * If no such metadata is present, this function returns `null`. Otherwise, the call is returned
  4431. * as a `Statement` for inclusion along with the class.
  4432. */
  4433. function extractClassMetadata(clazz, reflection, isCore, annotateForClosureCompiler, angularDecoratorTransform = (dec) => dec) {
  4434. if (!reflection.isClass(clazz)) {
  4435. return null;
  4436. }
  4437. const id = clazz.name;
  4438. // Reflect over the class decorators. If none are present, or those that are aren't from
  4439. // Angular, then return null. Otherwise, turn them into metadata.
  4440. const classDecorators = reflection.getDecoratorsOfDeclaration(clazz);
  4441. if (classDecorators === null) {
  4442. return null;
  4443. }
  4444. const ngClassDecorators = classDecorators
  4445. .filter((dec) => isAngularDecorator$1(dec, isCore))
  4446. .map((decorator) => decoratorToMetadata(angularDecoratorTransform(decorator), annotateForClosureCompiler))
  4447. // Since the `setClassMetadata` call is intended to be emitted after the class
  4448. // declaration, we have to strip references to the existing identifiers or
  4449. // TypeScript might generate invalid code when it emits to JS. In particular
  4450. // this can break when emitting a class to ES5 which has a custom decorator
  4451. // and is referenced inside of its own metadata (see #39509 for more information).
  4452. .map((decorator) => removeIdentifierReferences(decorator, id.text));
  4453. if (ngClassDecorators.length === 0) {
  4454. return null;
  4455. }
  4456. const metaDecorators = new checker.WrappedNodeExpr(ts.factory.createArrayLiteralExpression(ngClassDecorators));
  4457. // Convert the constructor parameters to metadata, passing null if none are present.
  4458. let metaCtorParameters = null;
  4459. const classCtorParameters = reflection.getConstructorParameters(clazz);
  4460. if (classCtorParameters !== null) {
  4461. const ctorParameters = classCtorParameters.map((param) => ctorParameterToMetadata(param, isCore));
  4462. metaCtorParameters = new checker.ArrowFunctionExpr([], new checker.LiteralArrayExpr(ctorParameters));
  4463. }
  4464. // Do the same for property decorators.
  4465. let metaPropDecorators = null;
  4466. const classMembers = reflection.getMembersOfClass(clazz).filter((member) => !member.isStatic &&
  4467. member.decorators !== null &&
  4468. member.decorators.length > 0 &&
  4469. // Private fields are not supported in the metadata emit
  4470. member.accessLevel !== checker.ClassMemberAccessLevel.EcmaScriptPrivate);
  4471. const duplicateDecoratedMembers = classMembers.filter((member, i, arr) => arr.findIndex((arrayMember) => arrayMember.name === member.name) < i);
  4472. if (duplicateDecoratedMembers.length > 0) {
  4473. // This should theoretically never happen, because the only way to have duplicate instance
  4474. // member names is getter/setter pairs and decorators cannot appear in both a getter and the
  4475. // corresponding setter.
  4476. throw new checker.FatalDiagnosticError(checker.ErrorCode.DUPLICATE_DECORATED_PROPERTIES, duplicateDecoratedMembers[0].nameNode ?? clazz, `Duplicate decorated properties found on class '${clazz.name.text}': ` +
  4477. duplicateDecoratedMembers.map((member) => member.name).join(', '));
  4478. }
  4479. const decoratedMembers = classMembers.map((member) => classMemberToMetadata(member.nameNode ?? member.name, member.decorators, isCore));
  4480. if (decoratedMembers.length > 0) {
  4481. metaPropDecorators = new checker.WrappedNodeExpr(ts.factory.createObjectLiteralExpression(decoratedMembers));
  4482. }
  4483. return {
  4484. type: new checker.WrappedNodeExpr(id),
  4485. decorators: metaDecorators,
  4486. ctorParameters: metaCtorParameters,
  4487. propDecorators: metaPropDecorators,
  4488. };
  4489. }
  4490. /**
  4491. * Convert a reflected constructor parameter to metadata.
  4492. */
  4493. function ctorParameterToMetadata(param, isCore) {
  4494. // Parameters sometimes have a type that can be referenced. If so, then use it, otherwise
  4495. // its type is undefined.
  4496. const type = param.typeValueReference.kind !== 2 /* TypeValueReferenceKind.UNAVAILABLE */
  4497. ? checker.valueReferenceToExpression(param.typeValueReference)
  4498. : new checker.LiteralExpr(undefined);
  4499. const mapEntries = [
  4500. { key: 'type', value: type, quoted: false },
  4501. ];
  4502. // If the parameter has decorators, include the ones from Angular.
  4503. if (param.decorators !== null) {
  4504. const ngDecorators = param.decorators
  4505. .filter((dec) => isAngularDecorator$1(dec, isCore))
  4506. .map((decorator) => decoratorToMetadata(decorator));
  4507. const value = new checker.WrappedNodeExpr(ts.factory.createArrayLiteralExpression(ngDecorators));
  4508. mapEntries.push({ key: 'decorators', value, quoted: false });
  4509. }
  4510. return checker.literalMap(mapEntries);
  4511. }
  4512. /**
  4513. * Convert a reflected class member to metadata.
  4514. */
  4515. function classMemberToMetadata(name, decorators, isCore) {
  4516. const ngDecorators = decorators
  4517. .filter((dec) => isAngularDecorator$1(dec, isCore))
  4518. .map((decorator) => decoratorToMetadata(decorator));
  4519. const decoratorMeta = ts.factory.createArrayLiteralExpression(ngDecorators);
  4520. return ts.factory.createPropertyAssignment(name, decoratorMeta);
  4521. }
  4522. /**
  4523. * Convert a reflected decorator to metadata.
  4524. */
  4525. function decoratorToMetadata(decorator, wrapFunctionsInParens) {
  4526. if (decorator.identifier === null) {
  4527. throw new Error('Illegal state: synthesized decorator cannot be emitted in class metadata.');
  4528. }
  4529. // Decorators have a type.
  4530. const properties = [
  4531. ts.factory.createPropertyAssignment('type', decorator.identifier),
  4532. ];
  4533. // Sometimes they have arguments.
  4534. if (decorator.args !== null && decorator.args.length > 0) {
  4535. const args = decorator.args.map((arg) => {
  4536. return wrapFunctionsInParens ? checker.wrapFunctionExpressionsInParens(arg) : arg;
  4537. });
  4538. properties.push(ts.factory.createPropertyAssignment('args', ts.factory.createArrayLiteralExpression(args)));
  4539. }
  4540. return ts.factory.createObjectLiteralExpression(properties, true);
  4541. }
  4542. /**
  4543. * Whether a given decorator should be treated as an Angular decorator.
  4544. *
  4545. * Either it's used in @angular/core, or it's imported from there.
  4546. */
  4547. function isAngularDecorator$1(decorator, isCore) {
  4548. return isCore || (decorator.import !== null && decorator.import.from === '@angular/core');
  4549. }
  4550. /**
  4551. * Recursively recreates all of the `Identifier` descendant nodes with a particular name inside
  4552. * of an AST node, thus removing any references to them. Useful if a particular node has to be
  4553. * taken from one place any emitted to another one exactly as it has been written.
  4554. */
  4555. function removeIdentifierReferences(node, names) {
  4556. const result = ts.transform(node, [
  4557. (context) => (root) => ts.visitNode(root, function walk(current) {
  4558. return (ts.isIdentifier(current) &&
  4559. (typeof names === 'string' ? current.text === names : names.has(current.text))
  4560. ? ts.factory.createIdentifier(current.text)
  4561. : ts.visitEachChild(current, walk, context));
  4562. }),
  4563. ]);
  4564. return result.transformed[0];
  4565. }
  4566. function extractClassDebugInfo(clazz, reflection, compilerHost, rootDirs, forbidOrphanRendering) {
  4567. if (!reflection.isClass(clazz)) {
  4568. return null;
  4569. }
  4570. const srcFile = clazz.getSourceFile();
  4571. const srcFileMaybeRelativePath = getProjectRelativePath(srcFile.fileName, rootDirs, compilerHost);
  4572. return {
  4573. type: new checker.WrappedNodeExpr(clazz.name),
  4574. className: checker.literal(clazz.name.getText()),
  4575. filePath: srcFileMaybeRelativePath ? checker.literal(srcFileMaybeRelativePath) : null,
  4576. lineNumber: checker.literal(srcFile.getLineAndCharacterOfPosition(clazz.name.pos).line + 1),
  4577. forbidOrphanRendering,
  4578. };
  4579. }
  4580. /**
  4581. * This registry does nothing.
  4582. */
  4583. class NoopReferencesRegistry {
  4584. add(source, ...references) { }
  4585. }
  4586. function extractSchemas(rawExpr, evaluator, context) {
  4587. const schemas = [];
  4588. const result = evaluator.evaluate(rawExpr);
  4589. if (!Array.isArray(result)) {
  4590. throw checker.createValueHasWrongTypeError(rawExpr, result, `${context}.schemas must be an array`);
  4591. }
  4592. for (const schemaRef of result) {
  4593. if (!(schemaRef instanceof checker.Reference)) {
  4594. throw checker.createValueHasWrongTypeError(rawExpr, result, `${context}.schemas must be an array of schemas`);
  4595. }
  4596. const id = schemaRef.getIdentityIn(schemaRef.node.getSourceFile());
  4597. if (id === null || schemaRef.ownedByModuleGuess !== '@angular/core') {
  4598. throw checker.createValueHasWrongTypeError(rawExpr, result, `${context}.schemas must be an array of schemas`);
  4599. }
  4600. // Since `id` is the `ts.Identifier` within the schema ref's declaration file, it's safe to
  4601. // use `id.text` here to figure out which schema is in use. Even if the actual reference was
  4602. // renamed when the user imported it, these names will match.
  4603. switch (id.text) {
  4604. case 'CUSTOM_ELEMENTS_SCHEMA':
  4605. schemas.push(checker.CUSTOM_ELEMENTS_SCHEMA);
  4606. break;
  4607. case 'NO_ERRORS_SCHEMA':
  4608. schemas.push(checker.NO_ERRORS_SCHEMA);
  4609. break;
  4610. default:
  4611. throw checker.createValueHasWrongTypeError(rawExpr, schemaRef, `'${schemaRef.debugName}' is not a valid ${context} schema`);
  4612. }
  4613. }
  4614. return schemas;
  4615. }
  4616. /*!
  4617. * @license
  4618. * Copyright Google LLC All Rights Reserved.
  4619. *
  4620. * Use of this source code is governed by an MIT-style license that can be
  4621. * found in the LICENSE file at https://angular.dev/license
  4622. */
  4623. /** Generates additional fields to be added to a class that has inputs with transform functions. */
  4624. function compileInputTransformFields(inputs) {
  4625. const extraFields = [];
  4626. for (const input of inputs) {
  4627. // Note: Signal inputs capture their transform `WriteT` as part of the `InputSignal`.
  4628. // Such inputs will not have a `transform` captured and not generate coercion members.
  4629. if (input.transform) {
  4630. extraFields.push({
  4631. name: `ngAcceptInputType_${input.classPropertyName}`,
  4632. type: checker.transplantedType(input.transform.type),
  4633. statements: [],
  4634. initializer: null,
  4635. deferrableImports: null,
  4636. });
  4637. }
  4638. }
  4639. return extraFields;
  4640. }
  4641. /**
  4642. * Registry that keeps track of Angular declarations that are explicitly
  4643. * marked for JIT compilation and are skipping compilation by trait handlers.
  4644. */
  4645. class JitDeclarationRegistry {
  4646. jitDeclarations = new Set();
  4647. }
  4648. /**
  4649. * Represents a symbol that is recognizable across incremental rebuilds, which enables the captured
  4650. * metadata to be compared to the prior compilation. This allows for semantic understanding of
  4651. * the changes that have been made in a rebuild, which potentially enables more reuse of work
  4652. * from the prior compilation.
  4653. */
  4654. class SemanticSymbol {
  4655. decl;
  4656. /**
  4657. * The path of the file that declares this symbol.
  4658. */
  4659. path;
  4660. /**
  4661. * The identifier of this symbol, or null if no identifier could be determined. It should
  4662. * uniquely identify the symbol relative to `file`. This is typically just the name of a
  4663. * top-level class declaration, as that uniquely identifies the class within the file.
  4664. *
  4665. * If the identifier is null, then this symbol cannot be recognized across rebuilds. In that
  4666. * case, the symbol is always assumed to have semantically changed to guarantee a proper
  4667. * rebuild.
  4668. */
  4669. identifier;
  4670. constructor(
  4671. /**
  4672. * The declaration for this symbol.
  4673. */
  4674. decl) {
  4675. this.decl = decl;
  4676. this.path = checker.absoluteFromSourceFile(decl.getSourceFile());
  4677. this.identifier = getSymbolIdentifier(decl);
  4678. }
  4679. }
  4680. function getSymbolIdentifier(decl) {
  4681. if (!ts.isSourceFile(decl.parent)) {
  4682. return null;
  4683. }
  4684. // If this is a top-level class declaration, the class name is used as unique identifier.
  4685. // Other scenarios are currently not supported and causes the symbol not to be identified
  4686. // across rebuilds, unless the declaration node has not changed.
  4687. return decl.name.text;
  4688. }
  4689. /**
  4690. * Represents a declaration for which no semantic symbol has been registered. For example,
  4691. * declarations from external dependencies have not been explicitly registered and are represented
  4692. * by this symbol. This allows the unresolved symbol to still be compared to a symbol from a prior
  4693. * compilation.
  4694. */
  4695. class OpaqueSymbol extends SemanticSymbol {
  4696. isPublicApiAffected() {
  4697. return false;
  4698. }
  4699. isTypeCheckApiAffected() {
  4700. return false;
  4701. }
  4702. }
  4703. /**
  4704. * The semantic dependency graph of a single compilation.
  4705. */
  4706. class SemanticDepGraph {
  4707. files = new Map();
  4708. // Note: the explicit type annotation is used to work around a CI failure on Windows:
  4709. // error TS2742: The inferred type of 'symbolByDecl' cannot be named without a reference to
  4710. // '../../../../../../../external/npm/node_modules/typescript/lib/typescript'. This is likely
  4711. // not portable. A type annotation is necessary.
  4712. symbolByDecl = new Map();
  4713. /**
  4714. * Registers a symbol in the graph. The symbol is given a unique identifier if possible, such that
  4715. * its equivalent symbol can be obtained from a prior graph even if its declaration node has
  4716. * changed across rebuilds. Symbols without an identifier are only able to find themselves in a
  4717. * prior graph if their declaration node is identical.
  4718. */
  4719. registerSymbol(symbol) {
  4720. this.symbolByDecl.set(symbol.decl, symbol);
  4721. if (symbol.identifier !== null) {
  4722. // If the symbol has a unique identifier, record it in the file that declares it. This enables
  4723. // the symbol to be requested by its unique name.
  4724. if (!this.files.has(symbol.path)) {
  4725. this.files.set(symbol.path, new Map());
  4726. }
  4727. this.files.get(symbol.path).set(symbol.identifier, symbol);
  4728. }
  4729. }
  4730. /**
  4731. * Attempts to resolve a symbol in this graph that represents the given symbol from another graph.
  4732. * If no matching symbol could be found, null is returned.
  4733. *
  4734. * @param symbol The symbol from another graph for which its equivalent in this graph should be
  4735. * found.
  4736. */
  4737. getEquivalentSymbol(symbol) {
  4738. // First lookup the symbol by its declaration. It is typical for the declaration to not have
  4739. // changed across rebuilds, so this is likely to find the symbol. Using the declaration also
  4740. // allows to diff symbols for which no unique identifier could be determined.
  4741. let previousSymbol = this.getSymbolByDecl(symbol.decl);
  4742. if (previousSymbol === null && symbol.identifier !== null) {
  4743. // The declaration could not be resolved to a symbol in a prior compilation, which may
  4744. // happen because the file containing the declaration has changed. In that case we want to
  4745. // lookup the symbol based on its unique identifier, as that allows us to still compare the
  4746. // changed declaration to the prior compilation.
  4747. previousSymbol = this.getSymbolByName(symbol.path, symbol.identifier);
  4748. }
  4749. return previousSymbol;
  4750. }
  4751. /**
  4752. * Attempts to find the symbol by its identifier.
  4753. */
  4754. getSymbolByName(path, identifier) {
  4755. if (!this.files.has(path)) {
  4756. return null;
  4757. }
  4758. const file = this.files.get(path);
  4759. if (!file.has(identifier)) {
  4760. return null;
  4761. }
  4762. return file.get(identifier);
  4763. }
  4764. /**
  4765. * Attempts to resolve the declaration to its semantic symbol.
  4766. */
  4767. getSymbolByDecl(decl) {
  4768. if (!this.symbolByDecl.has(decl)) {
  4769. return null;
  4770. }
  4771. return this.symbolByDecl.get(decl);
  4772. }
  4773. }
  4774. /**
  4775. * Implements the logic to go from a previous dependency graph to a new one, along with information
  4776. * on which files have been affected.
  4777. */
  4778. class SemanticDepGraphUpdater {
  4779. priorGraph;
  4780. newGraph = new SemanticDepGraph();
  4781. /**
  4782. * Contains opaque symbols that were created for declarations for which there was no symbol
  4783. * registered, which happens for e.g. external declarations.
  4784. */
  4785. opaqueSymbols = new Map();
  4786. constructor(
  4787. /**
  4788. * The semantic dependency graph of the most recently succeeded compilation, or null if this
  4789. * is the initial build.
  4790. */
  4791. priorGraph) {
  4792. this.priorGraph = priorGraph;
  4793. }
  4794. /**
  4795. * Registers the symbol in the new graph that is being created.
  4796. */
  4797. registerSymbol(symbol) {
  4798. this.newGraph.registerSymbol(symbol);
  4799. }
  4800. /**
  4801. * Takes all facts that have been gathered to create a new semantic dependency graph. In this
  4802. * process, the semantic impact of the changes is determined which results in a set of files that
  4803. * need to be emitted and/or type-checked.
  4804. */
  4805. finalize() {
  4806. if (this.priorGraph === null) {
  4807. // If no prior dependency graph is available then this was the initial build, in which case
  4808. // we don't need to determine the semantic impact as everything is already considered
  4809. // logically changed.
  4810. return {
  4811. needsEmit: new Set(),
  4812. needsTypeCheckEmit: new Set(),
  4813. newGraph: this.newGraph,
  4814. };
  4815. }
  4816. const needsEmit = this.determineInvalidatedFiles(this.priorGraph);
  4817. const needsTypeCheckEmit = this.determineInvalidatedTypeCheckFiles(this.priorGraph);
  4818. return {
  4819. needsEmit,
  4820. needsTypeCheckEmit,
  4821. newGraph: this.newGraph,
  4822. };
  4823. }
  4824. determineInvalidatedFiles(priorGraph) {
  4825. const isPublicApiAffected = new Set();
  4826. // The first phase is to collect all symbols which have their public API affected. Any symbols
  4827. // that cannot be matched up with a symbol from the prior graph are considered affected.
  4828. for (const symbol of this.newGraph.symbolByDecl.values()) {
  4829. const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
  4830. if (previousSymbol === null || symbol.isPublicApiAffected(previousSymbol)) {
  4831. isPublicApiAffected.add(symbol);
  4832. }
  4833. }
  4834. // The second phase is to find all symbols for which the emit result is affected, either because
  4835. // their used declarations have changed or any of those used declarations has had its public API
  4836. // affected as determined in the first phase.
  4837. const needsEmit = new Set();
  4838. for (const symbol of this.newGraph.symbolByDecl.values()) {
  4839. if (symbol.isEmitAffected === undefined) {
  4840. continue;
  4841. }
  4842. const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
  4843. if (previousSymbol === null || symbol.isEmitAffected(previousSymbol, isPublicApiAffected)) {
  4844. needsEmit.add(symbol.path);
  4845. }
  4846. }
  4847. return needsEmit;
  4848. }
  4849. determineInvalidatedTypeCheckFiles(priorGraph) {
  4850. const isTypeCheckApiAffected = new Set();
  4851. // The first phase is to collect all symbols which have their public API affected. Any symbols
  4852. // that cannot be matched up with a symbol from the prior graph are considered affected.
  4853. for (const symbol of this.newGraph.symbolByDecl.values()) {
  4854. const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
  4855. if (previousSymbol === null || symbol.isTypeCheckApiAffected(previousSymbol)) {
  4856. isTypeCheckApiAffected.add(symbol);
  4857. }
  4858. }
  4859. // The second phase is to find all symbols for which the emit result is affected, either because
  4860. // their used declarations have changed or any of those used declarations has had its public API
  4861. // affected as determined in the first phase.
  4862. const needsTypeCheckEmit = new Set();
  4863. for (const symbol of this.newGraph.symbolByDecl.values()) {
  4864. if (symbol.isTypeCheckBlockAffected === undefined) {
  4865. continue;
  4866. }
  4867. const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
  4868. if (previousSymbol === null ||
  4869. symbol.isTypeCheckBlockAffected(previousSymbol, isTypeCheckApiAffected)) {
  4870. needsTypeCheckEmit.add(symbol.path);
  4871. }
  4872. }
  4873. return needsTypeCheckEmit;
  4874. }
  4875. /**
  4876. * Creates a `SemanticReference` for the reference to `decl` using the expression `expr`. See
  4877. * the documentation of `SemanticReference` for details.
  4878. */
  4879. getSemanticReference(decl, expr) {
  4880. return {
  4881. symbol: this.getSymbol(decl),
  4882. importPath: getImportPath(expr),
  4883. };
  4884. }
  4885. /**
  4886. * Gets the `SemanticSymbol` that was registered for `decl` during the current compilation, or
  4887. * returns an opaque symbol that represents `decl`.
  4888. */
  4889. getSymbol(decl) {
  4890. const symbol = this.newGraph.getSymbolByDecl(decl);
  4891. if (symbol === null) {
  4892. // No symbol has been recorded for the provided declaration, which would be the case if the
  4893. // declaration is external. Return an opaque symbol in that case, to allow the external
  4894. // declaration to be compared to a prior compilation.
  4895. return this.getOpaqueSymbol(decl);
  4896. }
  4897. return symbol;
  4898. }
  4899. /**
  4900. * Gets or creates an `OpaqueSymbol` for the provided class declaration.
  4901. */
  4902. getOpaqueSymbol(decl) {
  4903. if (this.opaqueSymbols.has(decl)) {
  4904. return this.opaqueSymbols.get(decl);
  4905. }
  4906. const symbol = new OpaqueSymbol(decl);
  4907. this.opaqueSymbols.set(decl, symbol);
  4908. return symbol;
  4909. }
  4910. }
  4911. function getImportPath(expr) {
  4912. if (expr instanceof checker.ExternalExpr) {
  4913. return `${expr.value.moduleName}\$${expr.value.name}`;
  4914. }
  4915. else {
  4916. return null;
  4917. }
  4918. }
  4919. /**
  4920. * Determines whether the provided symbols represent the same declaration.
  4921. */
  4922. function isSymbolEqual(a, b) {
  4923. if (a.decl === b.decl) {
  4924. // If the declaration is identical then it must represent the same symbol.
  4925. return true;
  4926. }
  4927. if (a.identifier === null || b.identifier === null) {
  4928. // Unidentifiable symbols are assumed to be different.
  4929. return false;
  4930. }
  4931. return a.path === b.path && a.identifier === b.identifier;
  4932. }
  4933. /**
  4934. * Determines whether the provided references to a semantic symbol are still equal, i.e. represent
  4935. * the same symbol and are imported by the same path.
  4936. */
  4937. function isReferenceEqual(a, b) {
  4938. if (!isSymbolEqual(a.symbol, b.symbol)) {
  4939. // If the reference's target symbols are different, the reference itself is different.
  4940. return false;
  4941. }
  4942. // The reference still corresponds with the same symbol, now check that the path by which it is
  4943. // imported has not changed.
  4944. return a.importPath === b.importPath;
  4945. }
  4946. function referenceEquality(a, b) {
  4947. return a === b;
  4948. }
  4949. /**
  4950. * Determines if the provided arrays are equal to each other, using the provided equality tester
  4951. * that is called for all entries in the array.
  4952. */
  4953. function isArrayEqual(a, b, equalityTester = referenceEquality) {
  4954. if (a === null || b === null) {
  4955. return a === b;
  4956. }
  4957. if (a.length !== b.length) {
  4958. return false;
  4959. }
  4960. return !a.some((item, index) => !equalityTester(item, b[index]));
  4961. }
  4962. /**
  4963. * Determines if the provided sets are equal to each other, using the provided equality tester.
  4964. * Sets that only differ in ordering are considered equal.
  4965. */
  4966. function isSetEqual(a, b, equalityTester = referenceEquality) {
  4967. if (a === null || b === null) {
  4968. return a === b;
  4969. }
  4970. if (a.size !== b.size) {
  4971. return false;
  4972. }
  4973. for (const itemA of a) {
  4974. let found = false;
  4975. for (const itemB of b) {
  4976. if (equalityTester(itemA, itemB)) {
  4977. found = true;
  4978. break;
  4979. }
  4980. }
  4981. if (!found) {
  4982. return false;
  4983. }
  4984. }
  4985. return true;
  4986. }
  4987. /**
  4988. * Converts the type parameters of the given class into their semantic representation. If the class
  4989. * does not have any type parameters, then `null` is returned.
  4990. */
  4991. function extractSemanticTypeParameters(node) {
  4992. if (!ts.isClassDeclaration(node) || node.typeParameters === undefined) {
  4993. return null;
  4994. }
  4995. return node.typeParameters.map((typeParam) => ({
  4996. hasGenericTypeBound: typeParam.constraint !== undefined,
  4997. }));
  4998. }
  4999. /**
  5000. * Compares the list of type parameters to determine if they can be considered equal.
  5001. */
  5002. function areTypeParametersEqual(current, previous) {
  5003. // First compare all type parameters one-to-one; any differences mean that the list of type
  5004. // parameters has changed.
  5005. if (!isArrayEqual(current, previous, isTypeParameterEqual)) {
  5006. return false;
  5007. }
  5008. // If there is a current list of type parameters and if any of them has a generic type constraint,
  5009. // then the meaning of that type parameter may have changed without us being aware; as such we
  5010. // have to assume that the type parameters have in fact changed.
  5011. if (current !== null && current.some((typeParam) => typeParam.hasGenericTypeBound)) {
  5012. return false;
  5013. }
  5014. return true;
  5015. }
  5016. function isTypeParameterEqual(a, b) {
  5017. return a.hasGenericTypeBound === b.hasGenericTypeBound;
  5018. }
  5019. /**
  5020. * A `ComponentScopeReader` that reads from an ordered set of child readers until it obtains the
  5021. * requested scope.
  5022. *
  5023. * This is used to combine `ComponentScopeReader`s that read from different sources (e.g. from a
  5024. * registry and from the incremental state).
  5025. */
  5026. class CompoundComponentScopeReader {
  5027. readers;
  5028. constructor(readers) {
  5029. this.readers = readers;
  5030. }
  5031. getScopeForComponent(clazz) {
  5032. for (const reader of this.readers) {
  5033. const meta = reader.getScopeForComponent(clazz);
  5034. if (meta !== null) {
  5035. return meta;
  5036. }
  5037. }
  5038. return null;
  5039. }
  5040. getRemoteScope(clazz) {
  5041. for (const reader of this.readers) {
  5042. const remoteScope = reader.getRemoteScope(clazz);
  5043. if (remoteScope !== null) {
  5044. return remoteScope;
  5045. }
  5046. }
  5047. return null;
  5048. }
  5049. }
  5050. /**
  5051. * Reads Angular metadata from classes declared in .d.ts files and computes an `ExportScope`.
  5052. *
  5053. * Given an NgModule declared in a .d.ts file, this resolver can produce a transitive `ExportScope`
  5054. * of all of the directives/pipes it exports. It does this by reading metadata off of Ivy static
  5055. * fields on directives, components, pipes, and NgModules.
  5056. */
  5057. class MetadataDtsModuleScopeResolver {
  5058. dtsMetaReader;
  5059. aliasingHost;
  5060. /**
  5061. * Cache which holds fully resolved scopes for NgModule classes from .d.ts files.
  5062. */
  5063. cache = new Map();
  5064. /**
  5065. * @param dtsMetaReader a `MetadataReader` which can read metadata from `.d.ts` files.
  5066. */
  5067. constructor(dtsMetaReader, aliasingHost) {
  5068. this.dtsMetaReader = dtsMetaReader;
  5069. this.aliasingHost = aliasingHost;
  5070. }
  5071. /**
  5072. * Resolve a `Reference`'d NgModule from a .d.ts file and produce a transitive `ExportScope`
  5073. * listing the directives and pipes which that NgModule exports to others.
  5074. *
  5075. * This operation relies on a `Reference` instead of a direct TypeScript node as the `Reference`s
  5076. * produced depend on how the original NgModule was imported.
  5077. */
  5078. resolve(ref) {
  5079. const clazz = ref.node;
  5080. const sourceFile = clazz.getSourceFile();
  5081. if (!sourceFile.isDeclarationFile) {
  5082. throw new Error(`Debug error: DtsModuleScopeResolver.read(${ref.debugName} from ${sourceFile.fileName}), but not a .d.ts file`);
  5083. }
  5084. if (this.cache.has(clazz)) {
  5085. return this.cache.get(clazz);
  5086. }
  5087. // Build up the export scope - those directives and pipes made visible by this module.
  5088. const dependencies = [];
  5089. const meta = this.dtsMetaReader.getNgModuleMetadata(ref);
  5090. if (meta === null) {
  5091. this.cache.set(clazz, null);
  5092. return null;
  5093. }
  5094. const declarations = new Set();
  5095. for (const declRef of meta.declarations) {
  5096. declarations.add(declRef.node);
  5097. }
  5098. // Only the 'exports' field of the NgModule's metadata is important. Imports and declarations
  5099. // don't affect the export scope.
  5100. for (const exportRef of meta.exports) {
  5101. // Attempt to process the export as a directive.
  5102. const directive = this.dtsMetaReader.getDirectiveMetadata(exportRef);
  5103. if (directive !== null) {
  5104. const isReExport = !declarations.has(exportRef.node);
  5105. dependencies.push(this.maybeAlias(directive, sourceFile, isReExport));
  5106. continue;
  5107. }
  5108. // Attempt to process the export as a pipe.
  5109. const pipe = this.dtsMetaReader.getPipeMetadata(exportRef);
  5110. if (pipe !== null) {
  5111. const isReExport = !declarations.has(exportRef.node);
  5112. dependencies.push(this.maybeAlias(pipe, sourceFile, isReExport));
  5113. continue;
  5114. }
  5115. // Attempt to process the export as a module.
  5116. const exportScope = this.resolve(exportRef);
  5117. if (exportScope !== null) {
  5118. // It is a module. Add exported directives and pipes to the current scope. This might
  5119. // involve rewriting the `Reference`s to those types to have an alias expression if one is
  5120. // required.
  5121. if (this.aliasingHost === null) {
  5122. // Fast path when aliases aren't required.
  5123. dependencies.push(...exportScope.exported.dependencies);
  5124. }
  5125. else {
  5126. // It's necessary to rewrite the `Reference`s to add alias expressions. This way, imports
  5127. // generated to these directives and pipes will use a shallow import to `sourceFile`
  5128. // instead of a deep import directly to the directive or pipe class.
  5129. //
  5130. // One important check here is whether the directive/pipe is declared in the same
  5131. // source file as the re-exporting NgModule. This can happen if both a directive, its
  5132. // NgModule, and the re-exporting NgModule are all in the same file. In this case,
  5133. // no import alias is needed as it would go to the same file anyway.
  5134. for (const dep of exportScope.exported.dependencies) {
  5135. dependencies.push(this.maybeAlias(dep, sourceFile, /* isReExport */ true));
  5136. }
  5137. }
  5138. }
  5139. continue;
  5140. // The export was not a directive, a pipe, or a module. This is an error.
  5141. // TODO(alxhub): produce a ts.Diagnostic
  5142. }
  5143. const exportScope = {
  5144. exported: {
  5145. dependencies,
  5146. isPoisoned: meta.isPoisoned,
  5147. },
  5148. };
  5149. this.cache.set(clazz, exportScope);
  5150. return exportScope;
  5151. }
  5152. maybeAlias(dirOrPipe, maybeAliasFrom, isReExport) {
  5153. const ref = dirOrPipe.ref;
  5154. if (this.aliasingHost === null || ref.node.getSourceFile() === maybeAliasFrom) {
  5155. return dirOrPipe;
  5156. }
  5157. const alias = this.aliasingHost.getAliasIn(ref.node, maybeAliasFrom, isReExport);
  5158. if (alias === null) {
  5159. return dirOrPipe;
  5160. }
  5161. return {
  5162. ...dirOrPipe,
  5163. ref: ref.cloneWithAlias(alias),
  5164. };
  5165. }
  5166. }
  5167. function getDiagnosticNode(ref, rawExpr) {
  5168. // Show the diagnostic on the node within `rawExpr` which references the declaration
  5169. // in question. `rawExpr` represents the raw expression from which `ref` was partially evaluated,
  5170. // so use that to find the right node. Note that by the type system, `rawExpr` might be `null`, so
  5171. // fall back on the declaration identifier in that case (even though in practice this should never
  5172. // happen since local NgModules always have associated expressions).
  5173. return rawExpr !== null ? ref.getOriginForDiagnostics(rawExpr) : ref.node.name;
  5174. }
  5175. function makeNotStandaloneDiagnostic(scopeReader, ref, rawExpr, kind) {
  5176. const scope = scopeReader.getScopeForComponent(ref.node);
  5177. let message = `The ${kind} '${ref.node.name.text}' appears in 'imports', but is not standalone and cannot be imported directly.`;
  5178. let relatedInformation = undefined;
  5179. if (scope !== null && scope.kind === checker.ComponentScopeKind.NgModule) {
  5180. // The directive/pipe in question is declared in an NgModule. Check if it's also exported.
  5181. const isExported = scope.exported.dependencies.some((dep) => dep.ref.node === ref.node);
  5182. const relatedInfoMessageText = isExported
  5183. ? `It can be imported using its '${scope.ngModule.name.text}' NgModule instead.`
  5184. : `It's declared in the '${scope.ngModule.name.text}' NgModule, but is not exported. ` +
  5185. 'Consider exporting it and importing the NgModule instead.';
  5186. relatedInformation = [checker.makeRelatedInformation(scope.ngModule.name, relatedInfoMessageText)];
  5187. }
  5188. if (relatedInformation === undefined) {
  5189. // If no contextual pointers can be provided to suggest a specific remedy, then at least tell
  5190. // the user broadly what they need to do.
  5191. message += ' It must be imported via an NgModule.';
  5192. }
  5193. return checker.makeDiagnostic(checker.ErrorCode.COMPONENT_IMPORT_NOT_STANDALONE, getDiagnosticNode(ref, rawExpr), message, relatedInformation);
  5194. }
  5195. function makeUnknownComponentImportDiagnostic(ref, rawExpr) {
  5196. return checker.makeDiagnostic(checker.ErrorCode.COMPONENT_UNKNOWN_IMPORT, getDiagnosticNode(ref, rawExpr), `Component imports must be standalone components, directives, pipes, or must be NgModules.`);
  5197. }
  5198. function makeUnknownComponentDeferredImportDiagnostic(ref, rawExpr) {
  5199. return checker.makeDiagnostic(checker.ErrorCode.COMPONENT_UNKNOWN_DEFERRED_IMPORT, getDiagnosticNode(ref, rawExpr), `Component deferred imports must be standalone components, directives or pipes.`);
  5200. }
  5201. /** Value used to mark a module whose scope is in the process of being resolved. */
  5202. const IN_PROGRESS_RESOLUTION = {};
  5203. /**
  5204. * A registry which collects information about NgModules, Directives, Components, and Pipes which
  5205. * are local (declared in the ts.Program being compiled), and can produce `LocalModuleScope`s
  5206. * which summarize the compilation scope of a component.
  5207. *
  5208. * This class implements the logic of NgModule declarations, imports, and exports and can produce,
  5209. * for a given component, the set of directives and pipes which are "visible" in that component's
  5210. * template.
  5211. *
  5212. * The `LocalModuleScopeRegistry` has two "modes" of operation. During analysis, data for each
  5213. * individual NgModule, Directive, Component, and Pipe is added to the registry. No attempt is made
  5214. * to traverse or validate the NgModule graph (imports, exports, etc). After analysis, one of
  5215. * `getScopeOfModule` or `getScopeForComponent` can be called, which traverses the NgModule graph
  5216. * and applies the NgModule logic to generate a `LocalModuleScope`, the full scope for the given
  5217. * module or component.
  5218. *
  5219. * The `LocalModuleScopeRegistry` is also capable of producing `ts.Diagnostic` errors when Angular
  5220. * semantics are violated.
  5221. */
  5222. class LocalModuleScopeRegistry {
  5223. localReader;
  5224. fullReader;
  5225. dependencyScopeReader;
  5226. refEmitter;
  5227. aliasingHost;
  5228. /**
  5229. * Tracks whether the registry has been asked to produce scopes for a module or component. Once
  5230. * this is true, the registry cannot accept registrations of new directives/pipes/modules as it
  5231. * would invalidate the cached scope data.
  5232. */
  5233. sealed = false;
  5234. /**
  5235. * A map of components from the current compilation unit to the NgModule which declared them.
  5236. *
  5237. * As components and directives are not distinguished at the NgModule level, this map may also
  5238. * contain directives. This doesn't cause any problems but isn't useful as there is no concept of
  5239. * a directive's compilation scope.
  5240. */
  5241. declarationToModule = new Map();
  5242. /**
  5243. * This maps from the directive/pipe class to a map of data for each NgModule that declares the
  5244. * directive/pipe. This data is needed to produce an error for the given class.
  5245. */
  5246. duplicateDeclarations = new Map();
  5247. moduleToRef = new Map();
  5248. /**
  5249. * A cache of calculated `LocalModuleScope`s for each NgModule declared in the current program.
  5250. */
  5251. cache = new Map();
  5252. /**
  5253. * Tracks the `RemoteScope` for components requiring "remote scoping".
  5254. *
  5255. * Remote scoping is when the set of directives which apply to a given component is set in the
  5256. * NgModule's file instead of directly on the component def (which is sometimes needed to get
  5257. * around cyclic import issues). This is not used in calculation of `LocalModuleScope`s, but is
  5258. * tracked here for convenience.
  5259. */
  5260. remoteScoping = new Map();
  5261. /**
  5262. * Tracks errors accumulated in the processing of scopes for each module declaration.
  5263. */
  5264. scopeErrors = new Map();
  5265. /**
  5266. * Tracks which NgModules have directives/pipes that are declared in more than one module.
  5267. */
  5268. modulesWithStructuralErrors = new Set();
  5269. constructor(localReader, fullReader, dependencyScopeReader, refEmitter, aliasingHost) {
  5270. this.localReader = localReader;
  5271. this.fullReader = fullReader;
  5272. this.dependencyScopeReader = dependencyScopeReader;
  5273. this.refEmitter = refEmitter;
  5274. this.aliasingHost = aliasingHost;
  5275. }
  5276. /**
  5277. * Add an NgModule's data to the registry.
  5278. */
  5279. registerNgModuleMetadata(data) {
  5280. this.assertCollecting();
  5281. const ngModule = data.ref.node;
  5282. this.moduleToRef.set(data.ref.node, data.ref);
  5283. // Iterate over the module's declarations, and add them to declarationToModule. If duplicates
  5284. // are found, they're instead tracked in duplicateDeclarations.
  5285. for (const decl of data.declarations) {
  5286. this.registerDeclarationOfModule(ngModule, decl, data.rawDeclarations);
  5287. }
  5288. }
  5289. registerDirectiveMetadata(directive) { }
  5290. registerPipeMetadata(pipe) { }
  5291. getScopeForComponent(clazz) {
  5292. const scope = !this.declarationToModule.has(clazz)
  5293. ? null
  5294. : this.getScopeOfModule(this.declarationToModule.get(clazz).ngModule);
  5295. return scope;
  5296. }
  5297. /**
  5298. * If `node` is declared in more than one NgModule (duplicate declaration), then get the
  5299. * `DeclarationData` for each offending declaration.
  5300. *
  5301. * Ordinarily a class is only declared in one NgModule, in which case this function returns
  5302. * `null`.
  5303. */
  5304. getDuplicateDeclarations(node) {
  5305. if (!this.duplicateDeclarations.has(node)) {
  5306. return null;
  5307. }
  5308. return Array.from(this.duplicateDeclarations.get(node).values());
  5309. }
  5310. /**
  5311. * Collects registered data for a module and its directives/pipes and convert it into a full
  5312. * `LocalModuleScope`.
  5313. *
  5314. * This method implements the logic of NgModule imports and exports. It returns the
  5315. * `LocalModuleScope` for the given NgModule if one can be produced, `null` if no scope was ever
  5316. * defined, or the string `'error'` if the scope contained errors.
  5317. */
  5318. getScopeOfModule(clazz) {
  5319. return this.moduleToRef.has(clazz)
  5320. ? this.getScopeOfModuleReference(this.moduleToRef.get(clazz))
  5321. : null;
  5322. }
  5323. /**
  5324. * Retrieves any `ts.Diagnostic`s produced during the calculation of the `LocalModuleScope` for
  5325. * the given NgModule, or `null` if no errors were present.
  5326. */
  5327. getDiagnosticsOfModule(clazz) {
  5328. // Required to ensure the errors are populated for the given class. If it has been processed
  5329. // before, this will be a no-op due to the scope cache.
  5330. this.getScopeOfModule(clazz);
  5331. if (this.scopeErrors.has(clazz)) {
  5332. return this.scopeErrors.get(clazz);
  5333. }
  5334. else {
  5335. return null;
  5336. }
  5337. }
  5338. registerDeclarationOfModule(ngModule, decl, rawDeclarations) {
  5339. const declData = {
  5340. ngModule,
  5341. ref: decl,
  5342. rawDeclarations,
  5343. };
  5344. // First, check for duplicate declarations of the same directive/pipe.
  5345. if (this.duplicateDeclarations.has(decl.node)) {
  5346. // This directive/pipe has already been identified as being duplicated. Add this module to the
  5347. // map of modules for which a duplicate declaration exists.
  5348. this.duplicateDeclarations.get(decl.node).set(ngModule, declData);
  5349. }
  5350. else if (this.declarationToModule.has(decl.node) &&
  5351. this.declarationToModule.get(decl.node).ngModule !== ngModule) {
  5352. // This directive/pipe is already registered as declared in another module. Mark it as a
  5353. // duplicate instead.
  5354. const duplicateDeclMap = new Map();
  5355. const firstDeclData = this.declarationToModule.get(decl.node);
  5356. // Mark both modules as having duplicate declarations.
  5357. this.modulesWithStructuralErrors.add(firstDeclData.ngModule);
  5358. this.modulesWithStructuralErrors.add(ngModule);
  5359. // Being detected as a duplicate means there are two NgModules (for now) which declare this
  5360. // directive/pipe. Add both of them to the duplicate tracking map.
  5361. duplicateDeclMap.set(firstDeclData.ngModule, firstDeclData);
  5362. duplicateDeclMap.set(ngModule, declData);
  5363. this.duplicateDeclarations.set(decl.node, duplicateDeclMap);
  5364. // Remove the directive/pipe from `declarationToModule` as it's a duplicate declaration, and
  5365. // therefore not valid.
  5366. this.declarationToModule.delete(decl.node);
  5367. }
  5368. else {
  5369. // This is the first declaration of this directive/pipe, so map it.
  5370. this.declarationToModule.set(decl.node, declData);
  5371. }
  5372. }
  5373. /**
  5374. * Implementation of `getScopeOfModule` which accepts a reference to a class.
  5375. */
  5376. getScopeOfModuleReference(ref) {
  5377. if (this.cache.has(ref.node)) {
  5378. const cachedValue = this.cache.get(ref.node);
  5379. if (cachedValue !== IN_PROGRESS_RESOLUTION) {
  5380. return cachedValue;
  5381. }
  5382. }
  5383. this.cache.set(ref.node, IN_PROGRESS_RESOLUTION);
  5384. // Seal the registry to protect the integrity of the `LocalModuleScope` cache.
  5385. this.sealed = true;
  5386. // `ref` should be an NgModule previously added to the registry. If not, a scope for it
  5387. // cannot be produced.
  5388. const ngModule = this.localReader.getNgModuleMetadata(ref);
  5389. if (ngModule === null) {
  5390. this.cache.set(ref.node, null);
  5391. return null;
  5392. }
  5393. // Errors produced during computation of the scope are recorded here. At the end, if this array
  5394. // isn't empty then `undefined` will be cached and returned to indicate this scope is invalid.
  5395. const diagnostics = [];
  5396. // At this point, the goal is to produce two distinct transitive sets:
  5397. // - the directives and pipes which are visible to components declared in the NgModule.
  5398. // - the directives and pipes which are exported to any NgModules which import this one.
  5399. // Directives and pipes in the compilation scope.
  5400. const compilationDirectives = new Map();
  5401. const compilationPipes = new Map();
  5402. const declared = new Set();
  5403. // Directives and pipes exported to any importing NgModules.
  5404. const exportDirectives = new Map();
  5405. const exportPipes = new Map();
  5406. // The algorithm is as follows:
  5407. // 1) Add all of the directives/pipes from each NgModule imported into the current one to the
  5408. // compilation scope.
  5409. // 2) Add directives/pipes declared in the NgModule to the compilation scope. At this point, the
  5410. // compilation scope is complete.
  5411. // 3) For each entry in the NgModule's exports:
  5412. // a) Attempt to resolve it as an NgModule with its own exported directives/pipes. If it is
  5413. // one, add them to the export scope of this NgModule.
  5414. // b) Otherwise, it should be a class in the compilation scope of this NgModule. If it is,
  5415. // add it to the export scope.
  5416. // c) If it's neither an NgModule nor a directive/pipe in the compilation scope, then this
  5417. // is an error.
  5418. //
  5419. let isPoisoned = false;
  5420. if (this.modulesWithStructuralErrors.has(ngModule.ref.node)) {
  5421. // If the module contains declarations that are duplicates, then it's considered poisoned.
  5422. isPoisoned = true;
  5423. }
  5424. // 1) process imports.
  5425. for (const decl of ngModule.imports) {
  5426. const importScope = this.getExportedScope(decl, diagnostics, ref.node, 'import');
  5427. if (importScope !== null) {
  5428. if (importScope === 'invalid' ||
  5429. importScope === 'cycle' ||
  5430. importScope.exported.isPoisoned) {
  5431. // An import was an NgModule but contained errors of its own. Record this as an error too,
  5432. // because this scope is always going to be incorrect if one of its imports could not be
  5433. // read.
  5434. isPoisoned = true;
  5435. // Prevent the module from reporting a diagnostic about itself when there's a cycle.
  5436. if (importScope !== 'cycle') {
  5437. diagnostics.push(invalidTransitiveNgModuleRef(decl, ngModule.rawImports, 'import'));
  5438. }
  5439. if (importScope === 'invalid' || importScope === 'cycle') {
  5440. continue;
  5441. }
  5442. }
  5443. for (const dep of importScope.exported.dependencies) {
  5444. if (dep.kind === checker.MetaKind.Directive) {
  5445. compilationDirectives.set(dep.ref.node, dep);
  5446. }
  5447. else if (dep.kind === checker.MetaKind.Pipe) {
  5448. compilationPipes.set(dep.ref.node, dep);
  5449. }
  5450. }
  5451. // Successfully processed the import as an NgModule (even if it had errors).
  5452. continue;
  5453. }
  5454. // The import wasn't an NgModule. Maybe it's a standalone entity?
  5455. const directive = this.fullReader.getDirectiveMetadata(decl);
  5456. if (directive !== null) {
  5457. if (directive.isStandalone) {
  5458. compilationDirectives.set(directive.ref.node, directive);
  5459. }
  5460. else {
  5461. // Error: can't import a non-standalone component/directive.
  5462. diagnostics.push(makeNotStandaloneDiagnostic(this, decl, ngModule.rawImports, directive.isComponent ? 'component' : 'directive'));
  5463. isPoisoned = true;
  5464. }
  5465. continue;
  5466. }
  5467. // It wasn't a directive (standalone or otherwise). Maybe a pipe?
  5468. const pipe = this.fullReader.getPipeMetadata(decl);
  5469. if (pipe !== null) {
  5470. if (pipe.isStandalone) {
  5471. compilationPipes.set(pipe.ref.node, pipe);
  5472. }
  5473. else {
  5474. diagnostics.push(makeNotStandaloneDiagnostic(this, decl, ngModule.rawImports, 'pipe'));
  5475. isPoisoned = true;
  5476. }
  5477. continue;
  5478. }
  5479. // This reference was neither another NgModule nor a standalone entity. Report it as invalid.
  5480. diagnostics.push(invalidRef(decl, ngModule.rawImports, 'import'));
  5481. isPoisoned = true;
  5482. }
  5483. // 2) add declarations.
  5484. for (const decl of ngModule.declarations) {
  5485. const directive = this.localReader.getDirectiveMetadata(decl);
  5486. const pipe = this.localReader.getPipeMetadata(decl);
  5487. if (directive !== null) {
  5488. if (directive.isStandalone) {
  5489. const refType = directive.isComponent ? 'Component' : 'Directive';
  5490. diagnostics.push(checker.makeDiagnostic(checker.ErrorCode.NGMODULE_DECLARATION_IS_STANDALONE, decl.getOriginForDiagnostics(ngModule.rawDeclarations), `${refType} ${decl.node.name.text} is standalone, and cannot be declared in an NgModule. Did you mean to import it instead?`));
  5491. isPoisoned = true;
  5492. continue;
  5493. }
  5494. compilationDirectives.set(decl.node, { ...directive, ref: decl });
  5495. if (directive.isPoisoned) {
  5496. isPoisoned = true;
  5497. }
  5498. }
  5499. else if (pipe !== null) {
  5500. if (pipe.isStandalone) {
  5501. diagnostics.push(checker.makeDiagnostic(checker.ErrorCode.NGMODULE_DECLARATION_IS_STANDALONE, decl.getOriginForDiagnostics(ngModule.rawDeclarations), `Pipe ${decl.node.name.text} is standalone, and cannot be declared in an NgModule. Did you mean to import it instead?`));
  5502. isPoisoned = true;
  5503. continue;
  5504. }
  5505. compilationPipes.set(decl.node, { ...pipe, ref: decl });
  5506. }
  5507. else {
  5508. const errorNode = decl.getOriginForDiagnostics(ngModule.rawDeclarations);
  5509. diagnostics.push(checker.makeDiagnostic(checker.ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode, `The class '${decl.node.name.text}' is listed in the declarations ` +
  5510. `of the NgModule '${ngModule.ref.node.name.text}', but is not a directive, a component, or a pipe. ` +
  5511. `Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`, [checker.makeRelatedInformation(decl.node.name, `'${decl.node.name.text}' is declared here.`)]));
  5512. isPoisoned = true;
  5513. continue;
  5514. }
  5515. declared.add(decl.node);
  5516. }
  5517. // 3) process exports.
  5518. // Exports can contain modules, components, or directives. They're processed differently.
  5519. // Modules are straightforward. Directives and pipes from exported modules are added to the
  5520. // export maps. Directives/pipes are different - they might be exports of declared types or
  5521. // imported types.
  5522. for (const decl of ngModule.exports) {
  5523. // Attempt to resolve decl as an NgModule.
  5524. const exportScope = this.getExportedScope(decl, diagnostics, ref.node, 'export');
  5525. if (exportScope === 'invalid' ||
  5526. exportScope === 'cycle' ||
  5527. (exportScope !== null && exportScope.exported.isPoisoned)) {
  5528. // An export was an NgModule but contained errors of its own. Record this as an error too,
  5529. // because this scope is always going to be incorrect if one of its exports could not be
  5530. // read.
  5531. isPoisoned = true;
  5532. // Prevent the module from reporting a diagnostic about itself when there's a cycle.
  5533. if (exportScope !== 'cycle') {
  5534. diagnostics.push(invalidTransitiveNgModuleRef(decl, ngModule.rawExports, 'export'));
  5535. }
  5536. if (exportScope === 'invalid' || exportScope === 'cycle') {
  5537. continue;
  5538. }
  5539. }
  5540. else if (exportScope !== null) {
  5541. // decl is an NgModule.
  5542. for (const dep of exportScope.exported.dependencies) {
  5543. if (dep.kind == checker.MetaKind.Directive) {
  5544. exportDirectives.set(dep.ref.node, dep);
  5545. }
  5546. else if (dep.kind === checker.MetaKind.Pipe) {
  5547. exportPipes.set(dep.ref.node, dep);
  5548. }
  5549. }
  5550. }
  5551. else if (compilationDirectives.has(decl.node)) {
  5552. // decl is a directive or component in the compilation scope of this NgModule.
  5553. const directive = compilationDirectives.get(decl.node);
  5554. exportDirectives.set(decl.node, directive);
  5555. }
  5556. else if (compilationPipes.has(decl.node)) {
  5557. // decl is a pipe in the compilation scope of this NgModule.
  5558. const pipe = compilationPipes.get(decl.node);
  5559. exportPipes.set(decl.node, pipe);
  5560. }
  5561. else {
  5562. // decl is an unknown export.
  5563. const dirMeta = this.fullReader.getDirectiveMetadata(decl);
  5564. const pipeMeta = this.fullReader.getPipeMetadata(decl);
  5565. if (dirMeta !== null || pipeMeta !== null) {
  5566. const isStandalone = dirMeta !== null ? dirMeta.isStandalone : pipeMeta.isStandalone;
  5567. diagnostics.push(invalidReexport(decl, ngModule.rawExports, isStandalone));
  5568. }
  5569. else {
  5570. diagnostics.push(invalidRef(decl, ngModule.rawExports, 'export'));
  5571. }
  5572. isPoisoned = true;
  5573. continue;
  5574. }
  5575. }
  5576. const exported = {
  5577. dependencies: [...exportDirectives.values(), ...exportPipes.values()],
  5578. isPoisoned,
  5579. };
  5580. const reexports = this.getReexports(ngModule, ref, declared, exported.dependencies, diagnostics);
  5581. // Finally, produce the `LocalModuleScope` with both the compilation and export scopes.
  5582. const scope = {
  5583. kind: checker.ComponentScopeKind.NgModule,
  5584. ngModule: ngModule.ref.node,
  5585. compilation: {
  5586. dependencies: [...compilationDirectives.values(), ...compilationPipes.values()],
  5587. isPoisoned,
  5588. },
  5589. exported,
  5590. reexports,
  5591. schemas: ngModule.schemas,
  5592. };
  5593. // Check if this scope had any errors during production.
  5594. if (diagnostics.length > 0) {
  5595. // Save the errors for retrieval.
  5596. this.scopeErrors.set(ref.node, diagnostics);
  5597. // Mark this module as being tainted.
  5598. this.modulesWithStructuralErrors.add(ref.node);
  5599. }
  5600. this.cache.set(ref.node, scope);
  5601. return scope;
  5602. }
  5603. /**
  5604. * Check whether a component requires remote scoping.
  5605. */
  5606. getRemoteScope(node) {
  5607. return this.remoteScoping.has(node) ? this.remoteScoping.get(node) : null;
  5608. }
  5609. /**
  5610. * Set a component as requiring remote scoping, with the given directives and pipes to be
  5611. * registered remotely.
  5612. */
  5613. setComponentRemoteScope(node, directives, pipes) {
  5614. this.remoteScoping.set(node, { directives, pipes });
  5615. }
  5616. /**
  5617. * Look up the `ExportScope` of a given `Reference` to an NgModule.
  5618. *
  5619. * The NgModule in question may be declared locally in the current ts.Program, or it may be
  5620. * declared in a .d.ts file.
  5621. *
  5622. * @returns `null` if no scope could be found, or `'invalid'` if the `Reference` is not a valid
  5623. * NgModule.
  5624. *
  5625. * May also contribute diagnostics of its own by adding to the given `diagnostics`
  5626. * array parameter.
  5627. */
  5628. getExportedScope(ref, diagnostics, ownerForErrors, type) {
  5629. if (ref.node.getSourceFile().isDeclarationFile) {
  5630. // The NgModule is declared in a .d.ts file. Resolve it with the `DependencyScopeReader`.
  5631. if (!ts.isClassDeclaration(ref.node)) {
  5632. // The NgModule is in a .d.ts file but is not declared as a ts.ClassDeclaration. This is an
  5633. // error in the .d.ts metadata.
  5634. const code = type === 'import' ? checker.ErrorCode.NGMODULE_INVALID_IMPORT : checker.ErrorCode.NGMODULE_INVALID_EXPORT;
  5635. diagnostics.push(checker.makeDiagnostic(code, checker.identifierOfNode(ref.node) || ref.node, `Appears in the NgModule.${type}s of ${checker.nodeNameForError(ownerForErrors)}, but could not be resolved to an NgModule`));
  5636. return 'invalid';
  5637. }
  5638. return this.dependencyScopeReader.resolve(ref);
  5639. }
  5640. else {
  5641. if (this.cache.get(ref.node) === IN_PROGRESS_RESOLUTION) {
  5642. diagnostics.push(checker.makeDiagnostic(type === 'import'
  5643. ? checker.ErrorCode.NGMODULE_INVALID_IMPORT
  5644. : checker.ErrorCode.NGMODULE_INVALID_EXPORT, checker.identifierOfNode(ref.node) || ref.node, `NgModule "${type}" field contains a cycle`));
  5645. return 'cycle';
  5646. }
  5647. // The NgModule is declared locally in the current program. Resolve it from the registry.
  5648. return this.getScopeOfModuleReference(ref);
  5649. }
  5650. }
  5651. getReexports(ngModule, ref, declared, exported, diagnostics) {
  5652. let reexports = null;
  5653. const sourceFile = ref.node.getSourceFile();
  5654. if (this.aliasingHost === null) {
  5655. return null;
  5656. }
  5657. reexports = [];
  5658. // Track re-exports by symbol name, to produce diagnostics if two alias re-exports would share
  5659. // the same name.
  5660. const reexportMap = new Map();
  5661. // Alias ngModuleRef added for readability below.
  5662. const ngModuleRef = ref;
  5663. const addReexport = (exportRef) => {
  5664. if (exportRef.node.getSourceFile() === sourceFile) {
  5665. return;
  5666. }
  5667. const isReExport = !declared.has(exportRef.node);
  5668. const exportName = this.aliasingHost.maybeAliasSymbolAs(exportRef, sourceFile, ngModule.ref.node.name.text, isReExport);
  5669. if (exportName === null) {
  5670. return;
  5671. }
  5672. if (!reexportMap.has(exportName)) {
  5673. if (exportRef.alias && exportRef.alias instanceof checker.ExternalExpr) {
  5674. reexports.push({
  5675. fromModule: exportRef.alias.value.moduleName,
  5676. symbolName: exportRef.alias.value.name,
  5677. asAlias: exportName,
  5678. });
  5679. }
  5680. else {
  5681. const emittedRef = this.refEmitter.emit(exportRef.cloneWithNoIdentifiers(), sourceFile);
  5682. checker.assertSuccessfulReferenceEmit(emittedRef, ngModuleRef.node.name, 'class');
  5683. const expr = emittedRef.expression;
  5684. if (!(expr instanceof checker.ExternalExpr) ||
  5685. expr.value.moduleName === null ||
  5686. expr.value.name === null) {
  5687. throw new Error('Expected ExternalExpr');
  5688. }
  5689. reexports.push({
  5690. fromModule: expr.value.moduleName,
  5691. symbolName: expr.value.name,
  5692. asAlias: exportName,
  5693. });
  5694. }
  5695. reexportMap.set(exportName, exportRef);
  5696. }
  5697. else {
  5698. // Another re-export already used this name. Produce a diagnostic.
  5699. const prevRef = reexportMap.get(exportName);
  5700. diagnostics.push(reexportCollision(ngModuleRef.node, prevRef, exportRef));
  5701. }
  5702. };
  5703. for (const { ref } of exported) {
  5704. addReexport(ref);
  5705. }
  5706. return reexports;
  5707. }
  5708. assertCollecting() {
  5709. if (this.sealed) {
  5710. throw new Error(`Assertion: LocalModuleScopeRegistry is not COLLECTING`);
  5711. }
  5712. }
  5713. }
  5714. /**
  5715. * Produce a `ts.Diagnostic` for an invalid import or export from an NgModule.
  5716. */
  5717. function invalidRef(decl, rawExpr, type) {
  5718. const code = type === 'import' ? checker.ErrorCode.NGMODULE_INVALID_IMPORT : checker.ErrorCode.NGMODULE_INVALID_EXPORT;
  5719. const resolveTarget = type === 'import' ? 'NgModule' : 'NgModule, Component, Directive, or Pipe';
  5720. const message = `'${decl.node.name.text}' does not appear to be an ${resolveTarget} class.`;
  5721. const library = decl.ownedByModuleGuess !== null ? ` (${decl.ownedByModuleGuess})` : '';
  5722. const sf = decl.node.getSourceFile();
  5723. let relatedMessage;
  5724. // Provide extra context to the error for the user.
  5725. if (!sf.isDeclarationFile) {
  5726. // This is a file in the user's program. Highlight the class as undecorated.
  5727. const annotationType = type === 'import' ? '@NgModule' : 'Angular';
  5728. relatedMessage = `Is it missing an ${annotationType} annotation?`;
  5729. }
  5730. else if (sf.fileName.indexOf('node_modules') !== -1) {
  5731. // This file comes from a third-party library in node_modules.
  5732. relatedMessage =
  5733. `This likely means that the library${library} which declares ${decl.debugName} is not ` +
  5734. 'compatible with Angular Ivy. Check if a newer version of the library is available, ' +
  5735. "and update if so. Also consider checking with the library's authors to see if the " +
  5736. 'library is expected to be compatible with Ivy.';
  5737. }
  5738. else {
  5739. // This is a monorepo style local dependency. Unfortunately these are too different to really
  5740. // offer much more advice than this.
  5741. relatedMessage = `This likely means that the dependency${library} which declares ${decl.debugName} is not compatible with Angular Ivy.`;
  5742. }
  5743. return checker.makeDiagnostic(code, getDiagnosticNode(decl, rawExpr), message, [
  5744. checker.makeRelatedInformation(decl.node.name, relatedMessage),
  5745. ]);
  5746. }
  5747. /**
  5748. * Produce a `ts.Diagnostic` for an import or export which itself has errors.
  5749. */
  5750. function invalidTransitiveNgModuleRef(decl, rawExpr, type) {
  5751. const code = type === 'import' ? checker.ErrorCode.NGMODULE_INVALID_IMPORT : checker.ErrorCode.NGMODULE_INVALID_EXPORT;
  5752. return checker.makeDiagnostic(code, getDiagnosticNode(decl, rawExpr), `This ${type} contains errors, which may affect components that depend on this NgModule.`);
  5753. }
  5754. /**
  5755. * Produce a `ts.Diagnostic` for an exported directive or pipe which was not declared or imported
  5756. * by the NgModule in question.
  5757. */
  5758. function invalidReexport(decl, rawExpr, isStandalone) {
  5759. // The root error is the same here - this export is not valid. Give a helpful error message based
  5760. // on the specific circumstance.
  5761. let message = `Can't be exported from this NgModule, as `;
  5762. if (isStandalone) {
  5763. // Standalone types need to be imported into an NgModule before they can be re-exported.
  5764. message += 'it must be imported first';
  5765. }
  5766. else if (decl.node.getSourceFile().isDeclarationFile) {
  5767. // Non-standalone types can be re-exported, but need to be imported into the NgModule first.
  5768. // This requires importing their own NgModule.
  5769. message += 'it must be imported via its NgModule first';
  5770. }
  5771. else {
  5772. // Local non-standalone types must either be declared directly by this NgModule, or imported as
  5773. // above.
  5774. message +=
  5775. 'it must be either declared by this NgModule, or imported here via its NgModule first';
  5776. }
  5777. return checker.makeDiagnostic(checker.ErrorCode.NGMODULE_INVALID_REEXPORT, getDiagnosticNode(decl, rawExpr), message);
  5778. }
  5779. /**
  5780. * Produce a `ts.Diagnostic` for a collision in re-export names between two directives/pipes.
  5781. */
  5782. function reexportCollision(module, refA, refB) {
  5783. const childMessageText = `This directive/pipe is part of the exports of '${module.name.text}' and shares the same name as another exported directive/pipe.`;
  5784. return checker.makeDiagnostic(checker.ErrorCode.NGMODULE_REEXPORT_NAME_COLLISION, module.name, `
  5785. There was a name collision between two classes named '${refA.node.name.text}', which are both part of the exports of '${module.name.text}'.
  5786. Angular generates re-exports of an NgModule's exported directives/pipes from the module's source file in certain cases, using the declared name of the class. If two classes of the same name are exported, this automatic naming does not work.
  5787. To fix this problem please re-export one or both classes directly from this file.
  5788. `.trim(), [
  5789. checker.makeRelatedInformation(refA.node.name, childMessageText),
  5790. checker.makeRelatedInformation(refB.node.name, childMessageText),
  5791. ]);
  5792. }
  5793. /**
  5794. * Computes scope information to be used in template type checking.
  5795. */
  5796. class TypeCheckScopeRegistry {
  5797. scopeReader;
  5798. metaReader;
  5799. hostDirectivesResolver;
  5800. /**
  5801. * Cache of flattened directive metadata. Because flattened metadata is scope-invariant it's
  5802. * cached individually, such that all scopes refer to the same flattened metadata.
  5803. */
  5804. flattenedDirectiveMetaCache = new Map();
  5805. /**
  5806. * Cache of the computed type check scope per NgModule declaration.
  5807. */
  5808. scopeCache = new Map();
  5809. constructor(scopeReader, metaReader, hostDirectivesResolver) {
  5810. this.scopeReader = scopeReader;
  5811. this.metaReader = metaReader;
  5812. this.hostDirectivesResolver = hostDirectivesResolver;
  5813. }
  5814. /**
  5815. * Computes the type-check scope information for the component declaration. If the NgModule
  5816. * contains an error, then 'error' is returned. If the component is not declared in any NgModule,
  5817. * an empty type-check scope is returned.
  5818. */
  5819. getTypeCheckScope(node) {
  5820. const matcher = new checker.SelectorMatcher();
  5821. const directives = [];
  5822. const pipes = new Map();
  5823. const scope = this.scopeReader.getScopeForComponent(node);
  5824. if (scope === null) {
  5825. return {
  5826. matcher,
  5827. directives,
  5828. pipes,
  5829. schemas: [],
  5830. isPoisoned: false,
  5831. };
  5832. }
  5833. const isNgModuleScope = scope.kind === checker.ComponentScopeKind.NgModule;
  5834. const cacheKey = isNgModuleScope ? scope.ngModule : scope.component;
  5835. const dependencies = isNgModuleScope ? scope.compilation.dependencies : scope.dependencies;
  5836. if (this.scopeCache.has(cacheKey)) {
  5837. return this.scopeCache.get(cacheKey);
  5838. }
  5839. let allDependencies = dependencies;
  5840. if (!isNgModuleScope &&
  5841. Array.isArray(scope.deferredDependencies) &&
  5842. scope.deferredDependencies.length > 0) {
  5843. allDependencies = [...allDependencies, ...scope.deferredDependencies];
  5844. }
  5845. for (const meta of allDependencies) {
  5846. if (meta.kind === checker.MetaKind.Directive && meta.selector !== null) {
  5847. const extMeta = this.getTypeCheckDirectiveMetadata(meta.ref);
  5848. if (extMeta === null) {
  5849. continue;
  5850. }
  5851. // Carry over the `isExplicitlyDeferred` flag from the dependency info.
  5852. const directiveMeta = this.applyExplicitlyDeferredFlag(extMeta, meta.isExplicitlyDeferred);
  5853. matcher.addSelectables(checker.CssSelector.parse(meta.selector), [
  5854. ...this.hostDirectivesResolver.resolve(directiveMeta),
  5855. directiveMeta,
  5856. ]);
  5857. directives.push(directiveMeta);
  5858. }
  5859. else if (meta.kind === checker.MetaKind.Pipe) {
  5860. if (!ts.isClassDeclaration(meta.ref.node)) {
  5861. throw new Error(`Unexpected non-class declaration ${ts.SyntaxKind[meta.ref.node.kind]} for pipe ${meta.ref.debugName}`);
  5862. }
  5863. pipes.set(meta.name, meta);
  5864. }
  5865. }
  5866. const typeCheckScope = {
  5867. matcher,
  5868. directives,
  5869. pipes,
  5870. schemas: scope.schemas,
  5871. isPoisoned: scope.kind === checker.ComponentScopeKind.NgModule
  5872. ? scope.compilation.isPoisoned || scope.exported.isPoisoned
  5873. : scope.isPoisoned,
  5874. };
  5875. this.scopeCache.set(cacheKey, typeCheckScope);
  5876. return typeCheckScope;
  5877. }
  5878. getTypeCheckDirectiveMetadata(ref) {
  5879. const clazz = ref.node;
  5880. if (this.flattenedDirectiveMetaCache.has(clazz)) {
  5881. return this.flattenedDirectiveMetaCache.get(clazz);
  5882. }
  5883. const meta = checker.flattenInheritedDirectiveMetadata(this.metaReader, ref);
  5884. if (meta === null) {
  5885. return null;
  5886. }
  5887. this.flattenedDirectiveMetaCache.set(clazz, meta);
  5888. return meta;
  5889. }
  5890. applyExplicitlyDeferredFlag(meta, isExplicitlyDeferred) {
  5891. return isExplicitlyDeferred === true ? { ...meta, isExplicitlyDeferred } : meta;
  5892. }
  5893. }
  5894. /**
  5895. * Represents an Angular directive. Components are represented by `ComponentSymbol`, which inherits
  5896. * from this symbol.
  5897. */
  5898. class DirectiveSymbol extends SemanticSymbol {
  5899. selector;
  5900. inputs;
  5901. outputs;
  5902. exportAs;
  5903. typeCheckMeta;
  5904. typeParameters;
  5905. baseClass = null;
  5906. constructor(decl, selector, inputs, outputs, exportAs, typeCheckMeta, typeParameters) {
  5907. super(decl);
  5908. this.selector = selector;
  5909. this.inputs = inputs;
  5910. this.outputs = outputs;
  5911. this.exportAs = exportAs;
  5912. this.typeCheckMeta = typeCheckMeta;
  5913. this.typeParameters = typeParameters;
  5914. }
  5915. isPublicApiAffected(previousSymbol) {
  5916. // Note: since components and directives have exactly the same items contributing to their
  5917. // public API, it is okay for a directive to change into a component and vice versa without
  5918. // the API being affected.
  5919. if (!(previousSymbol instanceof DirectiveSymbol)) {
  5920. return true;
  5921. }
  5922. // Directives and components have a public API of:
  5923. // 1. Their selector.
  5924. // 2. The binding names of their inputs and outputs; a change in ordering is also considered
  5925. // to be a change in public API.
  5926. // 3. The list of exportAs names and its ordering.
  5927. return (this.selector !== previousSymbol.selector ||
  5928. !isArrayEqual(this.inputs.propertyNames, previousSymbol.inputs.propertyNames) ||
  5929. !isArrayEqual(this.outputs.propertyNames, previousSymbol.outputs.propertyNames) ||
  5930. !isArrayEqual(this.exportAs, previousSymbol.exportAs));
  5931. }
  5932. isTypeCheckApiAffected(previousSymbol) {
  5933. // If the public API of the directive has changed, then so has its type-check API.
  5934. if (this.isPublicApiAffected(previousSymbol)) {
  5935. return true;
  5936. }
  5937. if (!(previousSymbol instanceof DirectiveSymbol)) {
  5938. return true;
  5939. }
  5940. // The type-check block also depends on the class property names, as writes property bindings
  5941. // directly into the backing fields.
  5942. if (!isArrayEqual(Array.from(this.inputs), Array.from(previousSymbol.inputs), isInputMappingEqual) ||
  5943. !isArrayEqual(Array.from(this.outputs), Array.from(previousSymbol.outputs), isInputOrOutputEqual)) {
  5944. return true;
  5945. }
  5946. // The type parameters of a directive are emitted into the type constructors in the type-check
  5947. // block of a component, so if the type parameters are not considered equal then consider the
  5948. // type-check API of this directive to be affected.
  5949. if (!areTypeParametersEqual(this.typeParameters, previousSymbol.typeParameters)) {
  5950. return true;
  5951. }
  5952. // The type-check metadata is used during TCB code generation, so any changes should invalidate
  5953. // prior type-check files.
  5954. if (!isTypeCheckMetaEqual(this.typeCheckMeta, previousSymbol.typeCheckMeta)) {
  5955. return true;
  5956. }
  5957. // Changing the base class of a directive means that its inputs/outputs etc may have changed,
  5958. // so the type-check block of components that use this directive needs to be regenerated.
  5959. if (!isBaseClassEqual(this.baseClass, previousSymbol.baseClass)) {
  5960. return true;
  5961. }
  5962. return false;
  5963. }
  5964. }
  5965. function isInputMappingEqual(current, previous) {
  5966. return isInputOrOutputEqual(current, previous) && current.required === previous.required;
  5967. }
  5968. function isInputOrOutputEqual(current, previous) {
  5969. return (current.classPropertyName === previous.classPropertyName &&
  5970. current.bindingPropertyName === previous.bindingPropertyName &&
  5971. current.isSignal === previous.isSignal);
  5972. }
  5973. function isTypeCheckMetaEqual(current, previous) {
  5974. if (current.hasNgTemplateContextGuard !== previous.hasNgTemplateContextGuard) {
  5975. return false;
  5976. }
  5977. if (current.isGeneric !== previous.isGeneric) {
  5978. // Note: changes in the number of type parameters is also considered in
  5979. // `areTypeParametersEqual` so this check is technically not needed; it is done anyway for
  5980. // completeness in terms of whether the `DirectiveTypeCheckMeta` struct itself compares
  5981. // equal or not.
  5982. return false;
  5983. }
  5984. if (!isArrayEqual(current.ngTemplateGuards, previous.ngTemplateGuards, isTemplateGuardEqual)) {
  5985. return false;
  5986. }
  5987. if (!isSetEqual(current.coercedInputFields, previous.coercedInputFields)) {
  5988. return false;
  5989. }
  5990. if (!isSetEqual(current.restrictedInputFields, previous.restrictedInputFields)) {
  5991. return false;
  5992. }
  5993. if (!isSetEqual(current.stringLiteralInputFields, previous.stringLiteralInputFields)) {
  5994. return false;
  5995. }
  5996. if (!isSetEqual(current.undeclaredInputFields, previous.undeclaredInputFields)) {
  5997. return false;
  5998. }
  5999. return true;
  6000. }
  6001. function isTemplateGuardEqual(current, previous) {
  6002. return current.inputName === previous.inputName && current.type === previous.type;
  6003. }
  6004. function isBaseClassEqual(current, previous) {
  6005. if (current === null || previous === null) {
  6006. return current === previous;
  6007. }
  6008. return isSymbolEqual(current, previous);
  6009. }
  6010. const FIELD_DECORATORS = [
  6011. 'Input',
  6012. 'Output',
  6013. 'ViewChild',
  6014. 'ViewChildren',
  6015. 'ContentChild',
  6016. 'ContentChildren',
  6017. 'HostBinding',
  6018. 'HostListener',
  6019. ];
  6020. const LIFECYCLE_HOOKS = new Set([
  6021. 'ngOnChanges',
  6022. 'ngOnInit',
  6023. 'ngOnDestroy',
  6024. 'ngDoCheck',
  6025. 'ngAfterViewInit',
  6026. 'ngAfterViewChecked',
  6027. 'ngAfterContentInit',
  6028. 'ngAfterContentChecked',
  6029. ]);
  6030. class DirectiveDecoratorHandler {
  6031. reflector;
  6032. evaluator;
  6033. metaRegistry;
  6034. scopeRegistry;
  6035. metaReader;
  6036. injectableRegistry;
  6037. refEmitter;
  6038. referencesRegistry;
  6039. isCore;
  6040. strictCtorDeps;
  6041. semanticDepGraphUpdater;
  6042. annotateForClosureCompiler;
  6043. perf;
  6044. importTracker;
  6045. includeClassMetadata;
  6046. compilationMode;
  6047. jitDeclarationRegistry;
  6048. strictStandalone;
  6049. implicitStandaloneValue;
  6050. constructor(reflector, evaluator, metaRegistry, scopeRegistry, metaReader, injectableRegistry, refEmitter, referencesRegistry, isCore, strictCtorDeps, semanticDepGraphUpdater, annotateForClosureCompiler, perf, importTracker, includeClassMetadata, compilationMode, jitDeclarationRegistry, strictStandalone, implicitStandaloneValue) {
  6051. this.reflector = reflector;
  6052. this.evaluator = evaluator;
  6053. this.metaRegistry = metaRegistry;
  6054. this.scopeRegistry = scopeRegistry;
  6055. this.metaReader = metaReader;
  6056. this.injectableRegistry = injectableRegistry;
  6057. this.refEmitter = refEmitter;
  6058. this.referencesRegistry = referencesRegistry;
  6059. this.isCore = isCore;
  6060. this.strictCtorDeps = strictCtorDeps;
  6061. this.semanticDepGraphUpdater = semanticDepGraphUpdater;
  6062. this.annotateForClosureCompiler = annotateForClosureCompiler;
  6063. this.perf = perf;
  6064. this.importTracker = importTracker;
  6065. this.includeClassMetadata = includeClassMetadata;
  6066. this.compilationMode = compilationMode;
  6067. this.jitDeclarationRegistry = jitDeclarationRegistry;
  6068. this.strictStandalone = strictStandalone;
  6069. this.implicitStandaloneValue = implicitStandaloneValue;
  6070. }
  6071. precedence = checker.HandlerPrecedence.PRIMARY;
  6072. name = 'DirectiveDecoratorHandler';
  6073. detect(node, decorators) {
  6074. // If a class is undecorated but uses Angular features, we detect it as an
  6075. // abstract directive. This is an unsupported pattern as of v10, but we want
  6076. // to still detect these patterns so that we can report diagnostics.
  6077. if (!decorators) {
  6078. const angularField = this.findClassFieldWithAngularFeatures(node);
  6079. return angularField
  6080. ? { trigger: angularField.node, decorator: null, metadata: null }
  6081. : undefined;
  6082. }
  6083. else {
  6084. const decorator = checker.findAngularDecorator(decorators, 'Directive', this.isCore);
  6085. return decorator ? { trigger: decorator.node, decorator, metadata: decorator } : undefined;
  6086. }
  6087. }
  6088. analyze(node, decorator) {
  6089. // Skip processing of the class declaration if compilation of undecorated classes
  6090. // with Angular features is disabled. Previously in ngtsc, such classes have always
  6091. // been processed, but we want to enforce a consistent decorator mental model.
  6092. // See: https://v9.angular.io/guide/migration-undecorated-classes.
  6093. if (decorator === null) {
  6094. // If compiling @angular/core, skip the diagnostic as core occasionally hand-writes
  6095. // definitions.
  6096. if (this.isCore) {
  6097. return {};
  6098. }
  6099. return { diagnostics: [checker.getUndecoratedClassWithAngularFeaturesDiagnostic(node)] };
  6100. }
  6101. this.perf.eventCount(checker.PerfEvent.AnalyzeDirective);
  6102. const directiveResult = checker.extractDirectiveMetadata(node, decorator, this.reflector, this.importTracker, this.evaluator, this.refEmitter, this.referencesRegistry, this.isCore, this.annotateForClosureCompiler, this.compilationMode,
  6103. /* defaultSelector */ null, this.strictStandalone, this.implicitStandaloneValue);
  6104. // `extractDirectiveMetadata` returns `jitForced = true` when the `@Directive` has
  6105. // set `jit: true`. In this case, compilation of the decorator is skipped. Returning
  6106. // an empty object signifies that no analysis was produced.
  6107. if (directiveResult.jitForced) {
  6108. this.jitDeclarationRegistry.jitDeclarations.add(node);
  6109. return {};
  6110. }
  6111. const analysis = directiveResult.metadata;
  6112. let providersRequiringFactory = null;
  6113. if (directiveResult !== undefined && directiveResult.decorator.has('providers')) {
  6114. providersRequiringFactory = checker.resolveProvidersRequiringFactory(directiveResult.decorator.get('providers'), this.reflector, this.evaluator);
  6115. }
  6116. return {
  6117. analysis: {
  6118. inputs: directiveResult.inputs,
  6119. inputFieldNamesFromMetadataArray: directiveResult.inputFieldNamesFromMetadataArray,
  6120. outputs: directiveResult.outputs,
  6121. meta: analysis,
  6122. hostDirectives: directiveResult.hostDirectives,
  6123. rawHostDirectives: directiveResult.rawHostDirectives,
  6124. classMetadata: this.includeClassMetadata
  6125. ? extractClassMetadata(node, this.reflector, this.isCore, this.annotateForClosureCompiler)
  6126. : null,
  6127. baseClass: checker.readBaseClass(node, this.reflector, this.evaluator),
  6128. typeCheckMeta: checker.extractDirectiveTypeCheckMeta(node, directiveResult.inputs, this.reflector),
  6129. providersRequiringFactory,
  6130. isPoisoned: false,
  6131. isStructural: directiveResult.isStructural,
  6132. decorator: decorator?.node ?? null,
  6133. },
  6134. };
  6135. }
  6136. symbol(node, analysis) {
  6137. const typeParameters = extractSemanticTypeParameters(node);
  6138. return new DirectiveSymbol(node, analysis.meta.selector, analysis.inputs, analysis.outputs, analysis.meta.exportAs, analysis.typeCheckMeta, typeParameters);
  6139. }
  6140. register(node, analysis) {
  6141. // Register this directive's information with the `MetadataRegistry`. This ensures that
  6142. // the information about the directive is available during the compile() phase.
  6143. const ref = new checker.Reference(node);
  6144. this.metaRegistry.registerDirectiveMetadata({
  6145. kind: checker.MetaKind.Directive,
  6146. matchSource: checker.MatchSource.Selector,
  6147. ref,
  6148. name: node.name.text,
  6149. selector: analysis.meta.selector,
  6150. exportAs: analysis.meta.exportAs,
  6151. inputs: analysis.inputs,
  6152. inputFieldNamesFromMetadataArray: analysis.inputFieldNamesFromMetadataArray,
  6153. outputs: analysis.outputs,
  6154. queries: analysis.meta.queries.map((query) => query.propertyName),
  6155. isComponent: false,
  6156. baseClass: analysis.baseClass,
  6157. hostDirectives: analysis.hostDirectives,
  6158. ...analysis.typeCheckMeta,
  6159. isPoisoned: analysis.isPoisoned,
  6160. isStructural: analysis.isStructural,
  6161. animationTriggerNames: null,
  6162. isStandalone: analysis.meta.isStandalone,
  6163. isSignal: analysis.meta.isSignal,
  6164. imports: null,
  6165. rawImports: null,
  6166. deferredImports: null,
  6167. schemas: null,
  6168. ngContentSelectors: null,
  6169. decorator: analysis.decorator,
  6170. preserveWhitespaces: false,
  6171. // Directives analyzed within our own compilation are not _assumed_ to export providers.
  6172. // Instead, we statically analyze their imports to make a direct determination.
  6173. assumedToExportProviders: false,
  6174. isExplicitlyDeferred: false,
  6175. });
  6176. this.injectableRegistry.registerInjectable(node, {
  6177. ctorDeps: analysis.meta.deps,
  6178. });
  6179. }
  6180. resolve(node, analysis, symbol) {
  6181. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  6182. return {};
  6183. }
  6184. if (this.semanticDepGraphUpdater !== null && analysis.baseClass instanceof checker.Reference) {
  6185. symbol.baseClass = this.semanticDepGraphUpdater.getSymbol(analysis.baseClass.node);
  6186. }
  6187. const diagnostics = [];
  6188. if (analysis.providersRequiringFactory !== null &&
  6189. analysis.meta.providers instanceof checker.WrappedNodeExpr) {
  6190. const providerDiagnostics = checker.getProviderDiagnostics(analysis.providersRequiringFactory, analysis.meta.providers.node, this.injectableRegistry);
  6191. diagnostics.push(...providerDiagnostics);
  6192. }
  6193. const directiveDiagnostics = checker.getDirectiveDiagnostics(node, this.injectableRegistry, this.evaluator, this.reflector, this.scopeRegistry, this.strictCtorDeps, 'Directive');
  6194. if (directiveDiagnostics !== null) {
  6195. diagnostics.push(...directiveDiagnostics);
  6196. }
  6197. const hostDirectivesDiagnotics = analysis.hostDirectives && analysis.rawHostDirectives
  6198. ? checker.validateHostDirectives(analysis.rawHostDirectives, analysis.hostDirectives, this.metaReader)
  6199. : null;
  6200. if (hostDirectivesDiagnotics !== null) {
  6201. diagnostics.push(...hostDirectivesDiagnotics);
  6202. }
  6203. return { diagnostics: diagnostics.length > 0 ? diagnostics : undefined };
  6204. }
  6205. compileFull(node, analysis, resolution, pool) {
  6206. const fac = compileNgFactoryDefField(checker.toFactoryMetadata(analysis.meta, checker.FactoryTarget.Directive));
  6207. const def = checker.compileDirectiveFromMetadata(analysis.meta, pool, checker.makeBindingParser());
  6208. const inputTransformFields = compileInputTransformFields(analysis.inputs);
  6209. const classMetadata = analysis.classMetadata !== null
  6210. ? compileClassMetadata(analysis.classMetadata).toStmt()
  6211. : null;
  6212. return checker.compileResults(fac, def, classMetadata, 'ɵdir', inputTransformFields, null /* deferrableImports */);
  6213. }
  6214. compilePartial(node, analysis, resolution) {
  6215. const fac = compileDeclareFactory(checker.toFactoryMetadata(analysis.meta, checker.FactoryTarget.Directive));
  6216. const def = compileDeclareDirectiveFromMetadata(analysis.meta);
  6217. const inputTransformFields = compileInputTransformFields(analysis.inputs);
  6218. const classMetadata = analysis.classMetadata !== null
  6219. ? compileDeclareClassMetadata(analysis.classMetadata).toStmt()
  6220. : null;
  6221. return checker.compileResults(fac, def, classMetadata, 'ɵdir', inputTransformFields, null /* deferrableImports */);
  6222. }
  6223. compileLocal(node, analysis, resolution, pool) {
  6224. const fac = compileNgFactoryDefField(checker.toFactoryMetadata(analysis.meta, checker.FactoryTarget.Directive));
  6225. const def = checker.compileDirectiveFromMetadata(analysis.meta, pool, checker.makeBindingParser());
  6226. const inputTransformFields = compileInputTransformFields(analysis.inputs);
  6227. const classMetadata = analysis.classMetadata !== null
  6228. ? compileClassMetadata(analysis.classMetadata).toStmt()
  6229. : null;
  6230. return checker.compileResults(fac, def, classMetadata, 'ɵdir', inputTransformFields, null /* deferrableImports */);
  6231. }
  6232. /**
  6233. * Checks if a given class uses Angular features and returns the TypeScript node
  6234. * that indicated the usage. Classes are considered using Angular features if they
  6235. * contain class members that are either decorated with a known Angular decorator,
  6236. * or if they correspond to a known Angular lifecycle hook.
  6237. */
  6238. findClassFieldWithAngularFeatures(node) {
  6239. return this.reflector.getMembersOfClass(node).find((member) => {
  6240. if (!member.isStatic &&
  6241. member.kind === checker.ClassMemberKind.Method &&
  6242. LIFECYCLE_HOOKS.has(member.name)) {
  6243. return true;
  6244. }
  6245. if (member.decorators) {
  6246. return member.decorators.some((decorator) => FIELD_DECORATORS.some((decoratorName) => checker.isAngularDecorator(decorator, decoratorName, this.isCore)));
  6247. }
  6248. return false;
  6249. });
  6250. }
  6251. }
  6252. /**
  6253. * Creates a foreign function resolver to detect a `ModuleWithProviders<T>` type in a return type
  6254. * position of a function or method declaration. A `SyntheticValue` is produced if such a return
  6255. * type is recognized.
  6256. *
  6257. * @param reflector The reflection host to use for analyzing the syntax.
  6258. * @param isCore Whether the @angular/core package is being compiled.
  6259. */
  6260. function createModuleWithProvidersResolver(reflector, isCore) {
  6261. /**
  6262. * Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
  6263. * `ModuleWithProviders<T>`
  6264. * @param type The type to reflect on.
  6265. * @returns the identifier of the NgModule type if found, or null otherwise.
  6266. */
  6267. function _reflectModuleFromTypeParam(type, node) {
  6268. // Examine the type of the function to see if it's a ModuleWithProviders reference.
  6269. if (!ts.isTypeReferenceNode(type)) {
  6270. return null;
  6271. }
  6272. const typeName = (type &&
  6273. ((ts.isIdentifier(type.typeName) && type.typeName) ||
  6274. (ts.isQualifiedName(type.typeName) && type.typeName.right))) ||
  6275. null;
  6276. if (typeName === null) {
  6277. return null;
  6278. }
  6279. // Look at the type itself to see where it comes from.
  6280. const id = reflector.getImportOfIdentifier(typeName);
  6281. // If it's not named ModuleWithProviders, bail.
  6282. if (id === null || id.name !== 'ModuleWithProviders') {
  6283. return null;
  6284. }
  6285. // If it's not from @angular/core, bail.
  6286. if (!isCore && id.from !== '@angular/core') {
  6287. return null;
  6288. }
  6289. // If there's no type parameter specified, bail.
  6290. if (type.typeArguments === undefined || type.typeArguments.length !== 1) {
  6291. const parent = ts.isMethodDeclaration(node) && ts.isClassDeclaration(node.parent) ? node.parent : null;
  6292. const symbolName = (parent && parent.name ? parent.name.getText() + '.' : '') +
  6293. (node.name ? node.name.getText() : 'anonymous');
  6294. throw new checker.FatalDiagnosticError(checker.ErrorCode.NGMODULE_MODULE_WITH_PROVIDERS_MISSING_GENERIC, type, `${symbolName} returns a ModuleWithProviders type without a generic type argument. ` +
  6295. `Please add a generic type argument to the ModuleWithProviders type. If this ` +
  6296. `occurrence is in library code you don't control, please contact the library authors.`);
  6297. }
  6298. const arg = type.typeArguments[0];
  6299. return checker.typeNodeToValueExpr(arg);
  6300. }
  6301. /**
  6302. * Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
  6303. * `A|B|{ngModule: T}|C`.
  6304. * @param type The type to reflect on.
  6305. * @returns the identifier of the NgModule type if found, or null otherwise.
  6306. */
  6307. function _reflectModuleFromLiteralType(type) {
  6308. if (!ts.isIntersectionTypeNode(type)) {
  6309. return null;
  6310. }
  6311. for (const t of type.types) {
  6312. if (ts.isTypeLiteralNode(t)) {
  6313. for (const m of t.members) {
  6314. const ngModuleType = (ts.isPropertySignature(m) &&
  6315. ts.isIdentifier(m.name) &&
  6316. m.name.text === 'ngModule' &&
  6317. m.type) ||
  6318. null;
  6319. let ngModuleExpression = null;
  6320. // Handle `: typeof X` or `: X` cases.
  6321. if (ngModuleType !== null && ts.isTypeQueryNode(ngModuleType)) {
  6322. ngModuleExpression = checker.entityNameToValue(ngModuleType.exprName);
  6323. }
  6324. else if (ngModuleType !== null) {
  6325. ngModuleExpression = checker.typeNodeToValueExpr(ngModuleType);
  6326. }
  6327. if (ngModuleExpression) {
  6328. return ngModuleExpression;
  6329. }
  6330. }
  6331. }
  6332. }
  6333. return null;
  6334. }
  6335. return (fn, callExpr, resolve, unresolvable) => {
  6336. const rawType = fn.node.type;
  6337. if (rawType === undefined) {
  6338. return unresolvable;
  6339. }
  6340. const type = _reflectModuleFromTypeParam(rawType, fn.node) ?? _reflectModuleFromLiteralType(rawType);
  6341. if (type === null) {
  6342. return unresolvable;
  6343. }
  6344. const ngModule = resolve(type);
  6345. if (!(ngModule instanceof checker.Reference) || !checker.isNamedClassDeclaration(ngModule.node)) {
  6346. return unresolvable;
  6347. }
  6348. return new checker.SyntheticValue({
  6349. ngModule: ngModule,
  6350. mwpCall: callExpr,
  6351. });
  6352. };
  6353. }
  6354. function isResolvedModuleWithProviders(sv) {
  6355. return (typeof sv.value === 'object' &&
  6356. sv.value != null &&
  6357. sv.value.hasOwnProperty('ngModule') &&
  6358. sv.value.hasOwnProperty('mwpCall'));
  6359. }
  6360. /**
  6361. * Represents an Angular NgModule.
  6362. */
  6363. class NgModuleSymbol extends SemanticSymbol {
  6364. hasProviders;
  6365. remotelyScopedComponents = [];
  6366. /**
  6367. * `SemanticSymbol`s of the transitive imports of this NgModule which came from imported
  6368. * standalone components.
  6369. *
  6370. * Standalone components are excluded/included in the `InjectorDef` emit output of the NgModule
  6371. * based on whether the compiler can prove that their transitive imports may contain exported
  6372. * providers, so a change in this set of symbols may affect the compilation output of this
  6373. * NgModule.
  6374. */
  6375. transitiveImportsFromStandaloneComponents = new Set();
  6376. constructor(decl, hasProviders) {
  6377. super(decl);
  6378. this.hasProviders = hasProviders;
  6379. }
  6380. isPublicApiAffected(previousSymbol) {
  6381. if (!(previousSymbol instanceof NgModuleSymbol)) {
  6382. return true;
  6383. }
  6384. // Changes in the provider status of this NgModule affect downstream dependencies, which may
  6385. // consider provider status in their own emits.
  6386. if (previousSymbol.hasProviders !== this.hasProviders) {
  6387. return true;
  6388. }
  6389. return false;
  6390. }
  6391. isEmitAffected(previousSymbol) {
  6392. if (!(previousSymbol instanceof NgModuleSymbol)) {
  6393. return true;
  6394. }
  6395. // compare our remotelyScopedComponents to the previous symbol
  6396. if (previousSymbol.remotelyScopedComponents.length !== this.remotelyScopedComponents.length) {
  6397. return true;
  6398. }
  6399. for (const currEntry of this.remotelyScopedComponents) {
  6400. const prevEntry = previousSymbol.remotelyScopedComponents.find((prevEntry) => {
  6401. return isSymbolEqual(prevEntry.component, currEntry.component);
  6402. });
  6403. if (prevEntry === undefined) {
  6404. // No previous entry was found, which means that this component became remotely scoped and
  6405. // hence this NgModule needs to be re-emitted.
  6406. return true;
  6407. }
  6408. if (!isArrayEqual(currEntry.usedDirectives, prevEntry.usedDirectives, isReferenceEqual)) {
  6409. // The list of used directives or their order has changed. Since this NgModule emits
  6410. // references to the list of used directives, it should be re-emitted to update this list.
  6411. // Note: the NgModule does not have to be re-emitted when any of the directives has had
  6412. // their public API changed, as the NgModule only emits a reference to the symbol by its
  6413. // name. Therefore, testing for symbol equality is sufficient.
  6414. return true;
  6415. }
  6416. if (!isArrayEqual(currEntry.usedPipes, prevEntry.usedPipes, isReferenceEqual)) {
  6417. return true;
  6418. }
  6419. }
  6420. if (previousSymbol.transitiveImportsFromStandaloneComponents.size !==
  6421. this.transitiveImportsFromStandaloneComponents.size) {
  6422. return true;
  6423. }
  6424. const previousImports = Array.from(previousSymbol.transitiveImportsFromStandaloneComponents);
  6425. for (const transitiveImport of this.transitiveImportsFromStandaloneComponents) {
  6426. const prevEntry = previousImports.find((prevEntry) => isSymbolEqual(prevEntry, transitiveImport));
  6427. if (prevEntry === undefined) {
  6428. return true;
  6429. }
  6430. if (transitiveImport.isPublicApiAffected(prevEntry)) {
  6431. return true;
  6432. }
  6433. }
  6434. return false;
  6435. }
  6436. isTypeCheckApiAffected(previousSymbol) {
  6437. if (!(previousSymbol instanceof NgModuleSymbol)) {
  6438. return true;
  6439. }
  6440. return false;
  6441. }
  6442. addRemotelyScopedComponent(component, usedDirectives, usedPipes) {
  6443. this.remotelyScopedComponents.push({ component, usedDirectives, usedPipes });
  6444. }
  6445. addTransitiveImportFromStandaloneComponent(importedSymbol) {
  6446. this.transitiveImportsFromStandaloneComponents.add(importedSymbol);
  6447. }
  6448. }
  6449. /**
  6450. * Compiles @NgModule annotations to ngModuleDef fields.
  6451. */
  6452. class NgModuleDecoratorHandler {
  6453. reflector;
  6454. evaluator;
  6455. metaReader;
  6456. metaRegistry;
  6457. scopeRegistry;
  6458. referencesRegistry;
  6459. exportedProviderStatusResolver;
  6460. semanticDepGraphUpdater;
  6461. isCore;
  6462. refEmitter;
  6463. annotateForClosureCompiler;
  6464. onlyPublishPublicTypings;
  6465. injectableRegistry;
  6466. perf;
  6467. includeClassMetadata;
  6468. includeSelectorScope;
  6469. compilationMode;
  6470. localCompilationExtraImportsTracker;
  6471. jitDeclarationRegistry;
  6472. constructor(reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, exportedProviderStatusResolver, semanticDepGraphUpdater, isCore, refEmitter, annotateForClosureCompiler, onlyPublishPublicTypings, injectableRegistry, perf, includeClassMetadata, includeSelectorScope, compilationMode, localCompilationExtraImportsTracker, jitDeclarationRegistry) {
  6473. this.reflector = reflector;
  6474. this.evaluator = evaluator;
  6475. this.metaReader = metaReader;
  6476. this.metaRegistry = metaRegistry;
  6477. this.scopeRegistry = scopeRegistry;
  6478. this.referencesRegistry = referencesRegistry;
  6479. this.exportedProviderStatusResolver = exportedProviderStatusResolver;
  6480. this.semanticDepGraphUpdater = semanticDepGraphUpdater;
  6481. this.isCore = isCore;
  6482. this.refEmitter = refEmitter;
  6483. this.annotateForClosureCompiler = annotateForClosureCompiler;
  6484. this.onlyPublishPublicTypings = onlyPublishPublicTypings;
  6485. this.injectableRegistry = injectableRegistry;
  6486. this.perf = perf;
  6487. this.includeClassMetadata = includeClassMetadata;
  6488. this.includeSelectorScope = includeSelectorScope;
  6489. this.compilationMode = compilationMode;
  6490. this.localCompilationExtraImportsTracker = localCompilationExtraImportsTracker;
  6491. this.jitDeclarationRegistry = jitDeclarationRegistry;
  6492. }
  6493. precedence = checker.HandlerPrecedence.PRIMARY;
  6494. name = 'NgModuleDecoratorHandler';
  6495. detect(node, decorators) {
  6496. if (!decorators) {
  6497. return undefined;
  6498. }
  6499. const decorator = checker.findAngularDecorator(decorators, 'NgModule', this.isCore);
  6500. if (decorator !== undefined) {
  6501. return {
  6502. trigger: decorator.node,
  6503. decorator: decorator,
  6504. metadata: decorator,
  6505. };
  6506. }
  6507. else {
  6508. return undefined;
  6509. }
  6510. }
  6511. analyze(node, decorator) {
  6512. this.perf.eventCount(checker.PerfEvent.AnalyzeNgModule);
  6513. const name = node.name.text;
  6514. if (decorator.args === null || decorator.args.length > 1) {
  6515. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_ARITY_WRONG, decorator.node, `Incorrect number of arguments to @NgModule decorator`);
  6516. }
  6517. // @NgModule can be invoked without arguments. In case it is, pretend as if a blank object
  6518. // literal was specified. This simplifies the code below.
  6519. const meta = decorator.args.length === 1
  6520. ? checker.unwrapExpression(decorator.args[0])
  6521. : ts.factory.createObjectLiteralExpression([]);
  6522. if (!ts.isObjectLiteralExpression(meta)) {
  6523. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, '@NgModule argument must be an object literal');
  6524. }
  6525. const ngModule = checker.reflectObjectLiteral(meta);
  6526. if (ngModule.has('jit')) {
  6527. this.jitDeclarationRegistry.jitDeclarations.add(node);
  6528. // The only allowed value is true, so there's no need to expand further.
  6529. return {};
  6530. }
  6531. const forwardRefResolver = checker.createForwardRefResolver(this.isCore);
  6532. const moduleResolvers = checker.combineResolvers([
  6533. createModuleWithProvidersResolver(this.reflector, this.isCore),
  6534. forwardRefResolver,
  6535. ]);
  6536. const diagnostics = [];
  6537. // Resolving declarations
  6538. let declarationRefs = [];
  6539. const rawDeclarations = ngModule.get('declarations') ?? null;
  6540. if (rawDeclarations !== null) {
  6541. const declarationMeta = this.evaluator.evaluate(rawDeclarations, forwardRefResolver);
  6542. declarationRefs = this.resolveTypeList(rawDeclarations, declarationMeta, name, 'declarations', 0, this.compilationMode === checker.CompilationMode.LOCAL).references;
  6543. // Look through the declarations to make sure they're all a part of the current compilation.
  6544. for (const ref of declarationRefs) {
  6545. if (ref.node.getSourceFile().isDeclarationFile) {
  6546. const errorNode = ref.getOriginForDiagnostics(rawDeclarations);
  6547. diagnostics.push(checker.makeDiagnostic(checker.ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode, `Cannot declare '${ref.node.name.text}' in an NgModule as it's not a part of the current compilation.`, [checker.makeRelatedInformation(ref.node.name, `'${ref.node.name.text}' is declared here.`)]));
  6548. }
  6549. }
  6550. }
  6551. if (diagnostics.length > 0) {
  6552. return { diagnostics };
  6553. }
  6554. // Resolving imports
  6555. let importRefs = [];
  6556. let rawImports = ngModule.get('imports') ?? null;
  6557. if (rawImports !== null) {
  6558. const importsMeta = this.evaluator.evaluate(rawImports, moduleResolvers);
  6559. const result = this.resolveTypeList(rawImports, importsMeta, name, 'imports', 0, this.compilationMode === checker.CompilationMode.LOCAL);
  6560. if (this.compilationMode === checker.CompilationMode.LOCAL &&
  6561. this.localCompilationExtraImportsTracker !== null) {
  6562. // For generating extra imports in local mode, the NgModule imports that are from external
  6563. // files (i.e., outside of the compilation unit) are to be added to all the files in the
  6564. // compilation unit. This is because any external component that is a dependency of some
  6565. // component in the compilation unit must be imported by one of these NgModule's external
  6566. // imports (or the external component cannot be a dependency of that internal component).
  6567. // This approach can be further optimized by adding these NgModule external imports to a
  6568. // subset of files in the compilation unit and not all. See comments in {@link
  6569. // LocalCompilationExtraImportsTracker} and {@link
  6570. // LocalCompilationExtraImportsTracker#addGlobalImportFromIdentifier} for more details.
  6571. for (const d of result.dynamicValues) {
  6572. this.localCompilationExtraImportsTracker.addGlobalImportFromIdentifier(d.node);
  6573. }
  6574. }
  6575. importRefs = result.references;
  6576. }
  6577. // Resolving exports
  6578. let exportRefs = [];
  6579. const rawExports = ngModule.get('exports') ?? null;
  6580. if (rawExports !== null) {
  6581. const exportsMeta = this.evaluator.evaluate(rawExports, moduleResolvers);
  6582. exportRefs = this.resolveTypeList(rawExports, exportsMeta, name, 'exports', 0, this.compilationMode === checker.CompilationMode.LOCAL).references;
  6583. this.referencesRegistry.add(node, ...exportRefs);
  6584. }
  6585. // Resolving bootstrap
  6586. let bootstrapRefs = [];
  6587. const rawBootstrap = ngModule.get('bootstrap') ?? null;
  6588. if (this.compilationMode !== checker.CompilationMode.LOCAL && rawBootstrap !== null) {
  6589. const bootstrapMeta = this.evaluator.evaluate(rawBootstrap, forwardRefResolver);
  6590. bootstrapRefs = this.resolveTypeList(rawBootstrap, bootstrapMeta, name, 'bootstrap', 0,
  6591. /* allowUnresolvedReferences */ false).references;
  6592. // Verify that the `@NgModule.bootstrap` list doesn't have Standalone Components.
  6593. for (const ref of bootstrapRefs) {
  6594. const dirMeta = this.metaReader.getDirectiveMetadata(ref);
  6595. if (dirMeta?.isStandalone) {
  6596. diagnostics.push(makeStandaloneBootstrapDiagnostic(node, ref, rawBootstrap));
  6597. }
  6598. }
  6599. }
  6600. const schemas = this.compilationMode !== checker.CompilationMode.LOCAL && ngModule.has('schemas')
  6601. ? extractSchemas(ngModule.get('schemas'), this.evaluator, 'NgModule')
  6602. : [];
  6603. let id = null;
  6604. if (ngModule.has('id')) {
  6605. const idExpr = ngModule.get('id');
  6606. if (!isModuleIdExpression(idExpr)) {
  6607. id = new checker.WrappedNodeExpr(idExpr);
  6608. }
  6609. else {
  6610. const diag = checker.makeDiagnostic(checker.ErrorCode.WARN_NGMODULE_ID_UNNECESSARY, idExpr, `Using 'module.id' for NgModule.id is a common anti-pattern that is ignored by the Angular compiler.`);
  6611. diag.category = ts.DiagnosticCategory.Warning;
  6612. diagnostics.push(diag);
  6613. }
  6614. }
  6615. const valueContext = node.getSourceFile();
  6616. const exportedNodes = new Set(exportRefs.map((ref) => ref.node));
  6617. const declarations = [];
  6618. const exportedDeclarations = [];
  6619. const bootstrap = bootstrapRefs.map((bootstrap) => this._toR3Reference(bootstrap.getOriginForDiagnostics(meta, node.name), bootstrap, valueContext));
  6620. for (const ref of declarationRefs) {
  6621. const decl = this._toR3Reference(ref.getOriginForDiagnostics(meta, node.name), ref, valueContext);
  6622. declarations.push(decl);
  6623. if (exportedNodes.has(ref.node)) {
  6624. exportedDeclarations.push(decl.type);
  6625. }
  6626. }
  6627. const imports = importRefs.map((imp) => this._toR3Reference(imp.getOriginForDiagnostics(meta, node.name), imp, valueContext));
  6628. const exports = exportRefs.map((exp) => this._toR3Reference(exp.getOriginForDiagnostics(meta, node.name), exp, valueContext));
  6629. const isForwardReference = (ref) => checker.isExpressionForwardReference(ref.value, node.name, valueContext);
  6630. const containsForwardDecls = bootstrap.some(isForwardReference) ||
  6631. declarations.some(isForwardReference) ||
  6632. imports.some(isForwardReference) ||
  6633. exports.some(isForwardReference);
  6634. const type = checker.wrapTypeReference(this.reflector, node);
  6635. let ngModuleMetadata;
  6636. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  6637. ngModuleMetadata = {
  6638. kind: checker.R3NgModuleMetadataKind.Local,
  6639. type,
  6640. bootstrapExpression: rawBootstrap ? new checker.WrappedNodeExpr(rawBootstrap) : null,
  6641. declarationsExpression: rawDeclarations ? new checker.WrappedNodeExpr(rawDeclarations) : null,
  6642. exportsExpression: rawExports ? new checker.WrappedNodeExpr(rawExports) : null,
  6643. importsExpression: rawImports ? new checker.WrappedNodeExpr(rawImports) : null,
  6644. id,
  6645. // Use `ɵɵsetNgModuleScope` to patch selector scopes onto the generated definition in a
  6646. // tree-shakeable way.
  6647. selectorScopeMode: checker.R3SelectorScopeMode.SideEffect,
  6648. // TODO: to be implemented as a part of FW-1004.
  6649. schemas: [],
  6650. };
  6651. }
  6652. else {
  6653. ngModuleMetadata = {
  6654. kind: checker.R3NgModuleMetadataKind.Global,
  6655. type,
  6656. bootstrap,
  6657. declarations,
  6658. publicDeclarationTypes: this.onlyPublishPublicTypings ? exportedDeclarations : null,
  6659. exports,
  6660. imports,
  6661. // Imported types are generally private, so include them unless restricting the .d.ts emit
  6662. // to only public types.
  6663. includeImportTypes: !this.onlyPublishPublicTypings,
  6664. containsForwardDecls,
  6665. id,
  6666. // Use `ɵɵsetNgModuleScope` to patch selector scopes onto the generated definition in a
  6667. // tree-shakeable way.
  6668. selectorScopeMode: this.includeSelectorScope
  6669. ? checker.R3SelectorScopeMode.SideEffect
  6670. : checker.R3SelectorScopeMode.Omit,
  6671. // TODO: to be implemented as a part of FW-1004.
  6672. schemas: [],
  6673. };
  6674. }
  6675. const rawProviders = ngModule.has('providers') ? ngModule.get('providers') : null;
  6676. let wrappedProviders = null;
  6677. // In most cases the providers will be an array literal. Check if it has any elements
  6678. // and don't include the providers if it doesn't which saves us a few bytes.
  6679. if (rawProviders !== null &&
  6680. (!ts.isArrayLiteralExpression(rawProviders) || rawProviders.elements.length > 0)) {
  6681. wrappedProviders = new checker.WrappedNodeExpr(this.annotateForClosureCompiler
  6682. ? checker.wrapFunctionExpressionsInParens(rawProviders)
  6683. : rawProviders);
  6684. }
  6685. const topLevelImports = [];
  6686. if (this.compilationMode !== checker.CompilationMode.LOCAL && ngModule.has('imports')) {
  6687. const rawImports = checker.unwrapExpression(ngModule.get('imports'));
  6688. let topLevelExpressions = [];
  6689. if (ts.isArrayLiteralExpression(rawImports)) {
  6690. for (const element of rawImports.elements) {
  6691. if (ts.isSpreadElement(element)) {
  6692. // Because `imports` allows nested arrays anyway, a spread expression (`...foo`) can be
  6693. // treated the same as a direct reference to `foo`.
  6694. topLevelExpressions.push(element.expression);
  6695. continue;
  6696. }
  6697. topLevelExpressions.push(element);
  6698. }
  6699. }
  6700. else {
  6701. // Treat the whole `imports` expression as top-level.
  6702. topLevelExpressions.push(rawImports);
  6703. }
  6704. let absoluteIndex = 0;
  6705. for (const importExpr of topLevelExpressions) {
  6706. const resolved = this.evaluator.evaluate(importExpr, moduleResolvers);
  6707. const { references, hasModuleWithProviders } = this.resolveTypeList(importExpr, [resolved], node.name.text, 'imports', absoluteIndex,
  6708. /* allowUnresolvedReferences */ false);
  6709. absoluteIndex += references.length;
  6710. topLevelImports.push({
  6711. expression: importExpr,
  6712. resolvedReferences: references,
  6713. hasModuleWithProviders,
  6714. });
  6715. }
  6716. }
  6717. const injectorMetadata = {
  6718. name,
  6719. type,
  6720. providers: wrappedProviders,
  6721. imports: [],
  6722. };
  6723. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  6724. // Adding NgModule's raw imports/exports to the injector's imports field in local compilation
  6725. // mode.
  6726. for (const exp of [rawImports, rawExports]) {
  6727. if (exp === null) {
  6728. continue;
  6729. }
  6730. if (ts.isArrayLiteralExpression(exp)) {
  6731. // If array expression then add it entry-by-entry to the injector imports
  6732. if (exp.elements) {
  6733. injectorMetadata.imports.push(...exp.elements.map((n) => new checker.WrappedNodeExpr(n)));
  6734. }
  6735. }
  6736. else {
  6737. // if not array expression then add it as is to the injector's imports field.
  6738. injectorMetadata.imports.push(new checker.WrappedNodeExpr(exp));
  6739. }
  6740. }
  6741. }
  6742. const factoryMetadata = {
  6743. name,
  6744. type,
  6745. typeArgumentCount: 0,
  6746. deps: checker.getValidConstructorDependencies(node, this.reflector, this.isCore),
  6747. target: checker.FactoryTarget.NgModule,
  6748. };
  6749. // Remote scoping is used when adding imports to a component file would create a cycle. In such
  6750. // circumstances the component scope is monkey-patched from the NgModule file instead.
  6751. //
  6752. // However, if the NgModule itself has a cycle with the desired component/directive
  6753. // reference(s), then we need to be careful. This can happen for example if an NgModule imports
  6754. // a standalone component and the component also imports the NgModule.
  6755. //
  6756. // In this case, it'd be tempting to rely on the compiler's cycle detector to automatically put
  6757. // such circular references behind a function/closure. This requires global knowledge of the
  6758. // import graph though, and we don't want to depend on such techniques for new APIs like
  6759. // standalone components.
  6760. //
  6761. // Instead, we look for `forwardRef`s in the NgModule dependencies - an explicit signal from the
  6762. // user that a reference may not be defined until a circular import is resolved. If an NgModule
  6763. // contains forward-referenced declarations or imports, we assume that remotely scoped
  6764. // components should also guard against cycles using a closure-wrapped scope.
  6765. //
  6766. // The actual detection here is done heuristically. The compiler doesn't actually know whether
  6767. // any given `Reference` came from a `forwardRef`, but it does know when a `Reference` came from
  6768. // a `ForeignFunctionResolver` _like_ the `forwardRef` resolver. So we know when it's safe to
  6769. // not use a closure, and will use one just in case otherwise.
  6770. const remoteScopesMayRequireCycleProtection = declarationRefs.some(isSyntheticReference) || importRefs.some(isSyntheticReference);
  6771. return {
  6772. diagnostics: diagnostics.length > 0 ? diagnostics : undefined,
  6773. analysis: {
  6774. id,
  6775. schemas,
  6776. mod: ngModuleMetadata,
  6777. inj: injectorMetadata,
  6778. fac: factoryMetadata,
  6779. declarations: declarationRefs,
  6780. rawDeclarations,
  6781. imports: topLevelImports,
  6782. rawImports,
  6783. importRefs,
  6784. exports: exportRefs,
  6785. rawExports,
  6786. providers: rawProviders,
  6787. providersRequiringFactory: rawProviders
  6788. ? checker.resolveProvidersRequiringFactory(rawProviders, this.reflector, this.evaluator)
  6789. : null,
  6790. classMetadata: this.includeClassMetadata
  6791. ? extractClassMetadata(node, this.reflector, this.isCore, this.annotateForClosureCompiler)
  6792. : null,
  6793. factorySymbolName: node.name.text,
  6794. remoteScopesMayRequireCycleProtection,
  6795. decorator: decorator?.node ?? null,
  6796. },
  6797. };
  6798. }
  6799. symbol(node, analysis) {
  6800. return new NgModuleSymbol(node, analysis.providers !== null);
  6801. }
  6802. register(node, analysis) {
  6803. // Register this module's information with the LocalModuleScopeRegistry. This ensures that
  6804. // during the compile() phase, the module's metadata is available for selector scope
  6805. // computation.
  6806. this.metaRegistry.registerNgModuleMetadata({
  6807. kind: checker.MetaKind.NgModule,
  6808. ref: new checker.Reference(node),
  6809. schemas: analysis.schemas,
  6810. declarations: analysis.declarations,
  6811. imports: analysis.importRefs,
  6812. exports: analysis.exports,
  6813. rawDeclarations: analysis.rawDeclarations,
  6814. rawImports: analysis.rawImports,
  6815. rawExports: analysis.rawExports,
  6816. decorator: analysis.decorator,
  6817. mayDeclareProviders: analysis.providers !== null,
  6818. isPoisoned: false,
  6819. });
  6820. this.injectableRegistry.registerInjectable(node, {
  6821. ctorDeps: analysis.fac.deps,
  6822. });
  6823. }
  6824. resolve(node, analysis) {
  6825. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  6826. return {};
  6827. }
  6828. const scope = this.scopeRegistry.getScopeOfModule(node);
  6829. const diagnostics = [];
  6830. const scopeDiagnostics = this.scopeRegistry.getDiagnosticsOfModule(node);
  6831. if (scopeDiagnostics !== null) {
  6832. diagnostics.push(...scopeDiagnostics);
  6833. }
  6834. if (analysis.providersRequiringFactory !== null) {
  6835. const providerDiagnostics = checker.getProviderDiagnostics(analysis.providersRequiringFactory, analysis.providers, this.injectableRegistry);
  6836. diagnostics.push(...providerDiagnostics);
  6837. }
  6838. const data = {
  6839. injectorImports: [],
  6840. };
  6841. // Add all top-level imports from the `imports` field to the injector imports.
  6842. for (const topLevelImport of analysis.imports) {
  6843. if (topLevelImport.hasModuleWithProviders) {
  6844. // We have no choice but to emit expressions which contain MWPs, as we cannot filter on
  6845. // individual references.
  6846. data.injectorImports.push(new checker.WrappedNodeExpr(topLevelImport.expression));
  6847. continue;
  6848. }
  6849. const refsToEmit = [];
  6850. let symbol = null;
  6851. if (this.semanticDepGraphUpdater !== null) {
  6852. const sym = this.semanticDepGraphUpdater.getSymbol(node);
  6853. if (sym instanceof NgModuleSymbol) {
  6854. symbol = sym;
  6855. }
  6856. }
  6857. for (const ref of topLevelImport.resolvedReferences) {
  6858. const dirMeta = this.metaReader.getDirectiveMetadata(ref);
  6859. if (dirMeta !== null) {
  6860. if (!dirMeta.isComponent) {
  6861. // Skip emit of directives in imports - directives can't carry providers.
  6862. continue;
  6863. }
  6864. // Check whether this component has providers.
  6865. const mayExportProviders = this.exportedProviderStatusResolver.mayExportProviders(dirMeta.ref, (importRef) => {
  6866. // We need to keep track of which transitive imports were used to decide
  6867. // `mayExportProviders`, since if those change in a future compilation this
  6868. // NgModule will need to be re-emitted.
  6869. if (symbol !== null && this.semanticDepGraphUpdater !== null) {
  6870. const importSymbol = this.semanticDepGraphUpdater.getSymbol(importRef.node);
  6871. symbol.addTransitiveImportFromStandaloneComponent(importSymbol);
  6872. }
  6873. });
  6874. if (!mayExportProviders) {
  6875. // Skip emit of components that don't carry providers.
  6876. continue;
  6877. }
  6878. }
  6879. const pipeMeta = dirMeta === null ? this.metaReader.getPipeMetadata(ref) : null;
  6880. if (pipeMeta !== null) {
  6881. // Skip emit of pipes in imports - pipes can't carry providers.
  6882. continue;
  6883. }
  6884. refsToEmit.push(ref);
  6885. }
  6886. if (refsToEmit.length === topLevelImport.resolvedReferences.length) {
  6887. // All references within this top-level import should be emitted, so just use the user's
  6888. // expression.
  6889. data.injectorImports.push(new checker.WrappedNodeExpr(topLevelImport.expression));
  6890. }
  6891. else {
  6892. // Some references have been filtered out. Emit references to individual classes.
  6893. const context = node.getSourceFile();
  6894. for (const ref of refsToEmit) {
  6895. const emittedRef = this.refEmitter.emit(ref, context);
  6896. checker.assertSuccessfulReferenceEmit(emittedRef, topLevelImport.expression, 'class');
  6897. data.injectorImports.push(emittedRef.expression);
  6898. }
  6899. }
  6900. }
  6901. if (scope !== null && !scope.compilation.isPoisoned) {
  6902. // Using the scope information, extend the injector's imports using the modules that are
  6903. // specified as module exports.
  6904. const context = checker.getSourceFile(node);
  6905. for (const exportRef of analysis.exports) {
  6906. if (isNgModule(exportRef.node, scope.compilation)) {
  6907. const type = this.refEmitter.emit(exportRef, context);
  6908. checker.assertSuccessfulReferenceEmit(type, node, 'NgModule');
  6909. data.injectorImports.push(type.expression);
  6910. }
  6911. }
  6912. for (const decl of analysis.declarations) {
  6913. const dirMeta = this.metaReader.getDirectiveMetadata(decl);
  6914. if (dirMeta !== null) {
  6915. const refType = dirMeta.isComponent ? 'Component' : 'Directive';
  6916. if (dirMeta.selector === null) {
  6917. throw new checker.FatalDiagnosticError(checker.ErrorCode.DIRECTIVE_MISSING_SELECTOR, decl.node, `${refType} ${decl.node.name.text} has no selector, please add it!`);
  6918. }
  6919. continue;
  6920. }
  6921. }
  6922. }
  6923. if (diagnostics.length > 0) {
  6924. return { diagnostics };
  6925. }
  6926. if (scope === null ||
  6927. scope.compilation.isPoisoned ||
  6928. scope.exported.isPoisoned ||
  6929. scope.reexports === null) {
  6930. return { data };
  6931. }
  6932. else {
  6933. return {
  6934. data,
  6935. reexports: scope.reexports,
  6936. };
  6937. }
  6938. }
  6939. compileFull(node, { inj, mod, fac, classMetadata, declarations, remoteScopesMayRequireCycleProtection, }, { injectorImports }) {
  6940. const factoryFn = compileNgFactoryDefField(fac);
  6941. const ngInjectorDef = checker.compileInjector({
  6942. ...inj,
  6943. imports: injectorImports,
  6944. });
  6945. const ngModuleDef = checker.compileNgModule(mod);
  6946. const statements = ngModuleDef.statements;
  6947. const metadata = classMetadata !== null ? compileClassMetadata(classMetadata) : null;
  6948. this.insertMetadataStatement(statements, metadata);
  6949. this.appendRemoteScopingStatements(statements, node, declarations, remoteScopesMayRequireCycleProtection);
  6950. return this.compileNgModule(factoryFn, ngInjectorDef, ngModuleDef);
  6951. }
  6952. compilePartial(node, { inj, fac, mod, classMetadata }, { injectorImports }) {
  6953. const factoryFn = compileDeclareFactory(fac);
  6954. const injectorDef = compileDeclareInjectorFromMetadata({
  6955. ...inj,
  6956. imports: injectorImports,
  6957. });
  6958. const ngModuleDef = compileDeclareNgModuleFromMetadata(mod);
  6959. const metadata = classMetadata !== null ? compileDeclareClassMetadata(classMetadata) : null;
  6960. this.insertMetadataStatement(ngModuleDef.statements, metadata);
  6961. // NOTE: no remote scoping required as this is banned in partial compilation.
  6962. return this.compileNgModule(factoryFn, injectorDef, ngModuleDef);
  6963. }
  6964. compileLocal(node, { inj, mod, fac, classMetadata, declarations, remoteScopesMayRequireCycleProtection, }) {
  6965. const factoryFn = compileNgFactoryDefField(fac);
  6966. const ngInjectorDef = checker.compileInjector({
  6967. ...inj,
  6968. });
  6969. const ngModuleDef = checker.compileNgModule(mod);
  6970. const statements = ngModuleDef.statements;
  6971. const metadata = classMetadata !== null ? compileClassMetadata(classMetadata) : null;
  6972. this.insertMetadataStatement(statements, metadata);
  6973. this.appendRemoteScopingStatements(statements, node, declarations, remoteScopesMayRequireCycleProtection);
  6974. return this.compileNgModule(factoryFn, ngInjectorDef, ngModuleDef);
  6975. }
  6976. /**
  6977. * Add class metadata statements, if provided, to the `ngModuleStatements`.
  6978. */
  6979. insertMetadataStatement(ngModuleStatements, metadata) {
  6980. if (metadata !== null) {
  6981. ngModuleStatements.unshift(metadata.toStmt());
  6982. }
  6983. }
  6984. /**
  6985. * Add remote scoping statements, as needed, to the `ngModuleStatements`.
  6986. */
  6987. appendRemoteScopingStatements(ngModuleStatements, node, declarations, remoteScopesMayRequireCycleProtection) {
  6988. // Local compilation mode generates its own runtimes to compute the dependencies. So there no
  6989. // need to add remote scope statements (which also conflicts with local compilation runtimes)
  6990. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  6991. return;
  6992. }
  6993. const context = checker.getSourceFile(node);
  6994. for (const decl of declarations) {
  6995. const remoteScope = this.scopeRegistry.getRemoteScope(decl.node);
  6996. if (remoteScope !== null) {
  6997. const directives = remoteScope.directives.map((directive) => {
  6998. const type = this.refEmitter.emit(directive, context);
  6999. checker.assertSuccessfulReferenceEmit(type, node, 'directive');
  7000. return type.expression;
  7001. });
  7002. const pipes = remoteScope.pipes.map((pipe) => {
  7003. const type = this.refEmitter.emit(pipe, context);
  7004. checker.assertSuccessfulReferenceEmit(type, node, 'pipe');
  7005. return type.expression;
  7006. });
  7007. const directiveArray = new checker.LiteralArrayExpr(directives);
  7008. const pipesArray = new checker.LiteralArrayExpr(pipes);
  7009. const directiveExpr = remoteScopesMayRequireCycleProtection && directives.length > 0
  7010. ? new checker.FunctionExpr([], [new checker.ReturnStatement(directiveArray)])
  7011. : directiveArray;
  7012. const pipesExpr = remoteScopesMayRequireCycleProtection && pipes.length > 0
  7013. ? new checker.FunctionExpr([], [new checker.ReturnStatement(pipesArray)])
  7014. : pipesArray;
  7015. const componentType = this.refEmitter.emit(decl, context);
  7016. checker.assertSuccessfulReferenceEmit(componentType, node, 'component');
  7017. const declExpr = componentType.expression;
  7018. const setComponentScope = new checker.ExternalExpr(checker.Identifiers.setComponentScope);
  7019. const callExpr = new checker.InvokeFunctionExpr(setComponentScope, [
  7020. declExpr,
  7021. directiveExpr,
  7022. pipesExpr,
  7023. ]);
  7024. ngModuleStatements.push(callExpr.toStmt());
  7025. }
  7026. }
  7027. }
  7028. compileNgModule(factoryFn, injectorDef, ngModuleDef) {
  7029. const res = [
  7030. factoryFn,
  7031. {
  7032. name: 'ɵmod',
  7033. initializer: ngModuleDef.expression,
  7034. statements: ngModuleDef.statements,
  7035. type: ngModuleDef.type,
  7036. deferrableImports: null,
  7037. },
  7038. {
  7039. name: 'ɵinj',
  7040. initializer: injectorDef.expression,
  7041. statements: injectorDef.statements,
  7042. type: injectorDef.type,
  7043. deferrableImports: null,
  7044. },
  7045. ];
  7046. return res;
  7047. }
  7048. _toR3Reference(origin, valueRef, valueContext) {
  7049. if (valueRef.hasOwningModuleGuess) {
  7050. return checker.toR3Reference(origin, valueRef, valueContext, this.refEmitter);
  7051. }
  7052. else {
  7053. return checker.toR3Reference(origin, valueRef, valueContext, this.refEmitter);
  7054. }
  7055. }
  7056. // Verify that a "Declaration" reference is a `ClassDeclaration` reference.
  7057. isClassDeclarationReference(ref) {
  7058. return this.reflector.isClass(ref.node);
  7059. }
  7060. /**
  7061. * Compute a list of `Reference`s from a resolved metadata value.
  7062. */
  7063. resolveTypeList(expr, resolvedList, className, arrayName, absoluteIndex, allowUnresolvedReferences) {
  7064. let hasModuleWithProviders = false;
  7065. const refList = [];
  7066. const dynamicValueSet = new Set();
  7067. if (!Array.isArray(resolvedList)) {
  7068. if (allowUnresolvedReferences) {
  7069. return {
  7070. references: [],
  7071. hasModuleWithProviders: false,
  7072. dynamicValues: [],
  7073. };
  7074. }
  7075. throw checker.createValueHasWrongTypeError(expr, resolvedList, `Expected array when reading the NgModule.${arrayName} of ${className}`);
  7076. }
  7077. for (let idx = 0; idx < resolvedList.length; idx++) {
  7078. let entry = resolvedList[idx];
  7079. // Unwrap ModuleWithProviders for modules that are locally declared (and thus static
  7080. // resolution was able to descend into the function and return an object literal, a Map).
  7081. if (entry instanceof checker.SyntheticValue && isResolvedModuleWithProviders(entry)) {
  7082. entry = entry.value.ngModule;
  7083. hasModuleWithProviders = true;
  7084. }
  7085. else if (entry instanceof Map && entry.has('ngModule')) {
  7086. entry = entry.get('ngModule');
  7087. hasModuleWithProviders = true;
  7088. }
  7089. if (Array.isArray(entry)) {
  7090. // Recurse into nested arrays.
  7091. const recursiveResult = this.resolveTypeList(expr, entry, className, arrayName, absoluteIndex, allowUnresolvedReferences);
  7092. refList.push(...recursiveResult.references);
  7093. for (const d of recursiveResult.dynamicValues) {
  7094. dynamicValueSet.add(d);
  7095. }
  7096. absoluteIndex += recursiveResult.references.length;
  7097. hasModuleWithProviders = hasModuleWithProviders || recursiveResult.hasModuleWithProviders;
  7098. }
  7099. else if (entry instanceof checker.Reference) {
  7100. if (!this.isClassDeclarationReference(entry)) {
  7101. throw checker.createValueHasWrongTypeError(entry.node, entry, `Value at position ${absoluteIndex} in the NgModule.${arrayName} of ${className} is not a class`);
  7102. }
  7103. refList.push(entry);
  7104. absoluteIndex += 1;
  7105. }
  7106. else if (entry instanceof checker.DynamicValue && allowUnresolvedReferences) {
  7107. dynamicValueSet.add(entry);
  7108. continue;
  7109. }
  7110. else {
  7111. // TODO(alxhub): Produce a better diagnostic here - the array index may be an inner array.
  7112. throw checker.createValueHasWrongTypeError(expr, entry, `Value at position ${absoluteIndex} in the NgModule.${arrayName} of ${className} is not a reference`);
  7113. }
  7114. }
  7115. return {
  7116. references: refList,
  7117. hasModuleWithProviders,
  7118. dynamicValues: [...dynamicValueSet],
  7119. };
  7120. }
  7121. }
  7122. function isNgModule(node, compilation) {
  7123. return !compilation.dependencies.some((dep) => dep.ref.node === node);
  7124. }
  7125. /**
  7126. * Checks whether the given `ts.Expression` is the expression `module.id`.
  7127. */
  7128. function isModuleIdExpression(expr) {
  7129. return (ts.isPropertyAccessExpression(expr) &&
  7130. ts.isIdentifier(expr.expression) &&
  7131. expr.expression.text === 'module' &&
  7132. expr.name.text === 'id');
  7133. }
  7134. /**
  7135. * Helper method to produce a diagnostics for a situation when a standalone component
  7136. * is referenced in the `@NgModule.bootstrap` array.
  7137. */
  7138. function makeStandaloneBootstrapDiagnostic(ngModuleClass, bootstrappedClassRef, rawBootstrapExpr) {
  7139. const componentClassName = bootstrappedClassRef.node.name.text;
  7140. // Note: this error message should be aligned with the one produced by JIT.
  7141. const message = //
  7142. `The \`${componentClassName}\` class is a standalone component, which can ` +
  7143. `not be used in the \`@NgModule.bootstrap\` array. Use the \`bootstrapApplication\` ` +
  7144. `function for bootstrap instead.`;
  7145. const relatedInformation = [
  7146. checker.makeRelatedInformation(ngModuleClass, `The 'bootstrap' array is present on this NgModule.`),
  7147. ];
  7148. return checker.makeDiagnostic(checker.ErrorCode.NGMODULE_BOOTSTRAP_IS_STANDALONE, getDiagnosticNode(bootstrappedClassRef, rawBootstrapExpr), message, relatedInformation);
  7149. }
  7150. function isSyntheticReference(ref) {
  7151. return ref.synthetic;
  7152. }
  7153. /**
  7154. * Generate a diagnostic related information object that describes a potential cyclic import path.
  7155. */
  7156. function makeCyclicImportInfo(ref, type, cycle) {
  7157. const name = ref.debugName || '(unknown)';
  7158. const path = cycle
  7159. .getPath()
  7160. .map((sf) => sf.fileName)
  7161. .join(' -> ');
  7162. const message = `The ${type} '${name}' is used in the template but importing it would create a cycle: `;
  7163. return checker.makeRelatedInformation(ref.node, message + path);
  7164. }
  7165. /**
  7166. * Checks whether a selector is a valid custom element tag name.
  7167. * Based loosely on https://github.com/sindresorhus/validate-element-name.
  7168. */
  7169. function checkCustomElementSelectorForErrors(selector) {
  7170. // Avoid flagging components with an attribute or class selector. This isn't bulletproof since it
  7171. // won't catch cases like `foo[]bar`, but we don't need it to be. This is mainly to avoid flagging
  7172. // something like `foo-bar[baz]` incorrectly.
  7173. if (selector.includes('.') || (selector.includes('[') && selector.includes(']'))) {
  7174. return null;
  7175. }
  7176. if (!/^[a-z]/.test(selector)) {
  7177. return 'Selector of a ShadowDom-encapsulated component must start with a lower case letter.';
  7178. }
  7179. if (/[A-Z]/.test(selector)) {
  7180. return 'Selector of a ShadowDom-encapsulated component must all be in lower case.';
  7181. }
  7182. if (!selector.includes('-')) {
  7183. return 'Selector of a component that uses ViewEncapsulation.ShadowDom must contain a hyphen.';
  7184. }
  7185. return null;
  7186. }
  7187. /** Determines the node to use for debugging purposes for the given TemplateDeclaration. */
  7188. function getTemplateDeclarationNodeForError(declaration) {
  7189. return declaration.isInline ? declaration.expression : declaration.templateUrlExpression;
  7190. }
  7191. function extractTemplate(node, template, evaluator, depTracker, resourceLoader, options, compilationMode) {
  7192. if (template.isInline) {
  7193. let sourceStr;
  7194. let sourceParseRange = null;
  7195. let templateContent;
  7196. let sourceMapping;
  7197. let escapedString = false;
  7198. let sourceMapUrl;
  7199. // We only support SourceMaps for inline templates that are simple string literals.
  7200. if (ts.isStringLiteral(template.expression) ||
  7201. ts.isNoSubstitutionTemplateLiteral(template.expression)) {
  7202. // the start and end of the `templateExpr` node includes the quotation marks, which we must
  7203. // strip
  7204. sourceParseRange = getTemplateRange(template.expression);
  7205. sourceStr = template.expression.getSourceFile().text;
  7206. templateContent = template.expression.text;
  7207. escapedString = true;
  7208. sourceMapping = {
  7209. type: 'direct',
  7210. node: template.expression,
  7211. };
  7212. sourceMapUrl = template.resolvedTemplateUrl;
  7213. }
  7214. else {
  7215. const resolvedTemplate = evaluator.evaluate(template.expression);
  7216. // The identifier used for @Component.template cannot be resolved in local compilation mode. An error specific to this situation is generated.
  7217. checker.assertLocalCompilationUnresolvedConst(compilationMode, resolvedTemplate, template.expression, 'Unresolved identifier found for @Component.template field! ' +
  7218. 'Did you import this identifier from a file outside of the compilation unit? ' +
  7219. 'This is not allowed when Angular compiler runs in local mode. ' +
  7220. 'Possible solutions: 1) Move the declaration into a file within the ' +
  7221. 'compilation unit, 2) Inline the template, 3) Move the template into ' +
  7222. 'a separate .html file and include it using @Component.templateUrl');
  7223. if (typeof resolvedTemplate !== 'string') {
  7224. throw checker.createValueHasWrongTypeError(template.expression, resolvedTemplate, 'template must be a string');
  7225. }
  7226. // We do not parse the template directly from the source file using a lexer range, so
  7227. // the template source and content are set to the statically resolved template.
  7228. sourceStr = resolvedTemplate;
  7229. templateContent = resolvedTemplate;
  7230. sourceMapping = {
  7231. type: 'indirect',
  7232. node: template.expression,
  7233. componentClass: node,
  7234. template: templateContent,
  7235. };
  7236. // Indirect templates cannot be mapped to a particular byte range of any input file, since
  7237. // they're computed by expressions that may span many files. Don't attempt to map them back
  7238. // to a given file.
  7239. sourceMapUrl = null;
  7240. }
  7241. return {
  7242. ...parseExtractedTemplate(template, sourceStr, sourceParseRange, escapedString, sourceMapUrl, options),
  7243. content: templateContent,
  7244. sourceMapping,
  7245. declaration: template,
  7246. };
  7247. }
  7248. else {
  7249. const templateContent = resourceLoader.load(template.resolvedTemplateUrl);
  7250. if (depTracker !== null) {
  7251. depTracker.addResourceDependency(node.getSourceFile(), checker.absoluteFrom(template.resolvedTemplateUrl));
  7252. }
  7253. return {
  7254. ...parseExtractedTemplate(template,
  7255. /* sourceStr */ templateContent,
  7256. /* sourceParseRange */ null,
  7257. /* escapedString */ false,
  7258. /* sourceMapUrl */ template.resolvedTemplateUrl, options),
  7259. content: templateContent,
  7260. sourceMapping: {
  7261. type: 'external',
  7262. componentClass: node,
  7263. node: template.templateUrlExpression,
  7264. template: templateContent,
  7265. templateUrl: template.resolvedTemplateUrl,
  7266. },
  7267. declaration: template,
  7268. };
  7269. }
  7270. }
  7271. function parseExtractedTemplate(template, sourceStr, sourceParseRange, escapedString, sourceMapUrl, options) {
  7272. // We always normalize line endings if the template has been escaped (i.e. is inline).
  7273. const i18nNormalizeLineEndingsInICUs = escapedString || options.i18nNormalizeLineEndingsInICUs;
  7274. const commonParseOptions = {
  7275. interpolationConfig: template.interpolationConfig,
  7276. range: sourceParseRange ?? undefined,
  7277. enableI18nLegacyMessageIdFormat: options.enableI18nLegacyMessageIdFormat,
  7278. i18nNormalizeLineEndingsInICUs,
  7279. alwaysAttemptHtmlToR3AstConversion: options.usePoisonedData,
  7280. escapedString,
  7281. enableBlockSyntax: options.enableBlockSyntax,
  7282. enableLetSyntax: options.enableLetSyntax,
  7283. };
  7284. const parsedTemplate = checker.parseTemplate(sourceStr, sourceMapUrl ?? '', {
  7285. ...commonParseOptions,
  7286. preserveWhitespaces: template.preserveWhitespaces,
  7287. preserveSignificantWhitespace: options.preserveSignificantWhitespace,
  7288. });
  7289. // Unfortunately, the primary parse of the template above may not contain accurate source map
  7290. // information. If used directly, it would result in incorrect code locations in template
  7291. // errors, etc. There are three main problems:
  7292. //
  7293. // 1. `preserveWhitespaces: false` or `preserveSignificantWhitespace: false` annihilates the
  7294. // correctness of template source mapping, as the whitespace transformation changes the
  7295. // contents of HTML text nodes before they're parsed into Angular expressions.
  7296. // 2. `preserveLineEndings: false` causes growing misalignments in templates that use '\r\n'
  7297. // line endings, by normalizing them to '\n'.
  7298. // 3. By default, the template parser strips leading trivia characters (like spaces, tabs, and
  7299. // newlines). This also destroys source mapping information.
  7300. //
  7301. // In order to guarantee the correctness of diagnostics, templates are parsed a second time
  7302. // with the above options set to preserve source mappings.
  7303. const { nodes: diagNodes } = checker.parseTemplate(sourceStr, sourceMapUrl ?? '', {
  7304. ...commonParseOptions,
  7305. preserveWhitespaces: true,
  7306. preserveLineEndings: true,
  7307. preserveSignificantWhitespace: true,
  7308. leadingTriviaChars: [],
  7309. });
  7310. return {
  7311. ...parsedTemplate,
  7312. diagNodes,
  7313. file: new checker.ParseSourceFile(sourceStr, sourceMapUrl ?? ''),
  7314. };
  7315. }
  7316. function parseTemplateDeclaration(node, decorator, component, containingFile, evaluator, depTracker, resourceLoader, defaultPreserveWhitespaces) {
  7317. let preserveWhitespaces = defaultPreserveWhitespaces;
  7318. if (component.has('preserveWhitespaces')) {
  7319. const expr = component.get('preserveWhitespaces');
  7320. const value = evaluator.evaluate(expr);
  7321. if (typeof value !== 'boolean') {
  7322. throw checker.createValueHasWrongTypeError(expr, value, 'preserveWhitespaces must be a boolean');
  7323. }
  7324. preserveWhitespaces = value;
  7325. }
  7326. let interpolationConfig = checker.DEFAULT_INTERPOLATION_CONFIG;
  7327. if (component.has('interpolation')) {
  7328. const expr = component.get('interpolation');
  7329. const value = evaluator.evaluate(expr);
  7330. if (!Array.isArray(value) ||
  7331. value.length !== 2 ||
  7332. !value.every((element) => typeof element === 'string')) {
  7333. throw checker.createValueHasWrongTypeError(expr, value, 'interpolation must be an array with 2 elements of string type');
  7334. }
  7335. interpolationConfig = checker.InterpolationConfig.fromArray(value);
  7336. }
  7337. if (component.has('templateUrl')) {
  7338. const templateUrlExpr = component.get('templateUrl');
  7339. const templateUrl = evaluator.evaluate(templateUrlExpr);
  7340. if (typeof templateUrl !== 'string') {
  7341. throw checker.createValueHasWrongTypeError(templateUrlExpr, templateUrl, 'templateUrl must be a string');
  7342. }
  7343. try {
  7344. const resourceUrl = resourceLoader.resolve(templateUrl, containingFile);
  7345. return {
  7346. isInline: false,
  7347. interpolationConfig,
  7348. preserveWhitespaces,
  7349. templateUrl,
  7350. templateUrlExpression: templateUrlExpr,
  7351. resolvedTemplateUrl: resourceUrl,
  7352. };
  7353. }
  7354. catch (e) {
  7355. if (depTracker !== null) {
  7356. // The analysis of this file cannot be re-used if the template URL could
  7357. // not be resolved. Future builds should re-analyze and re-attempt resolution.
  7358. depTracker.recordDependencyAnalysisFailure(node.getSourceFile());
  7359. }
  7360. throw makeResourceNotFoundError(templateUrl, templateUrlExpr, 0 /* ResourceTypeForDiagnostics.Template */);
  7361. }
  7362. }
  7363. else if (component.has('template')) {
  7364. return {
  7365. isInline: true,
  7366. interpolationConfig,
  7367. preserveWhitespaces,
  7368. expression: component.get('template'),
  7369. templateUrl: containingFile,
  7370. resolvedTemplateUrl: containingFile,
  7371. };
  7372. }
  7373. else {
  7374. throw new checker.FatalDiagnosticError(checker.ErrorCode.COMPONENT_MISSING_TEMPLATE, decorator.node, 'component is missing a template');
  7375. }
  7376. }
  7377. function preloadAndParseTemplate(evaluator, resourceLoader, depTracker, preanalyzeTemplateCache, node, decorator, component, containingFile, defaultPreserveWhitespaces, options, compilationMode) {
  7378. if (component.has('templateUrl')) {
  7379. // Extract the templateUrl and preload it.
  7380. const templateUrlExpr = component.get('templateUrl');
  7381. const templateUrl = evaluator.evaluate(templateUrlExpr);
  7382. if (typeof templateUrl !== 'string') {
  7383. throw checker.createValueHasWrongTypeError(templateUrlExpr, templateUrl, 'templateUrl must be a string');
  7384. }
  7385. try {
  7386. const resourceUrl = resourceLoader.resolve(templateUrl, containingFile);
  7387. const templatePromise = resourceLoader.preload(resourceUrl, {
  7388. type: 'template',
  7389. containingFile,
  7390. className: node.name.text,
  7391. });
  7392. // If the preload worked, then actually load and parse the template, and wait for any
  7393. // style URLs to resolve.
  7394. if (templatePromise !== undefined) {
  7395. return templatePromise.then(() => {
  7396. const templateDecl = parseTemplateDeclaration(node, decorator, component, containingFile, evaluator, depTracker, resourceLoader, defaultPreserveWhitespaces);
  7397. const template = extractTemplate(node, templateDecl, evaluator, depTracker, resourceLoader, options, compilationMode);
  7398. preanalyzeTemplateCache.set(node, template);
  7399. return template;
  7400. });
  7401. }
  7402. else {
  7403. return Promise.resolve(null);
  7404. }
  7405. }
  7406. catch (e) {
  7407. if (depTracker !== null) {
  7408. // The analysis of this file cannot be re-used if the template URL could
  7409. // not be resolved. Future builds should re-analyze and re-attempt resolution.
  7410. depTracker.recordDependencyAnalysisFailure(node.getSourceFile());
  7411. }
  7412. throw makeResourceNotFoundError(templateUrl, templateUrlExpr, 0 /* ResourceTypeForDiagnostics.Template */);
  7413. }
  7414. }
  7415. else {
  7416. const templateDecl = parseTemplateDeclaration(node, decorator, component, containingFile, evaluator, depTracker, resourceLoader, defaultPreserveWhitespaces);
  7417. const template = extractTemplate(node, templateDecl, evaluator, depTracker, resourceLoader, options, compilationMode);
  7418. preanalyzeTemplateCache.set(node, template);
  7419. return Promise.resolve(template);
  7420. }
  7421. }
  7422. function getTemplateRange(templateExpr) {
  7423. const startPos = templateExpr.getStart() + 1;
  7424. const { line, character } = ts.getLineAndCharacterOfPosition(templateExpr.getSourceFile(), startPos);
  7425. return {
  7426. startPos,
  7427. startLine: line,
  7428. startCol: character,
  7429. endPos: templateExpr.getEnd() - 1,
  7430. };
  7431. }
  7432. function makeResourceNotFoundError(file, nodeForError, resourceType) {
  7433. let errorText;
  7434. switch (resourceType) {
  7435. case 0 /* ResourceTypeForDiagnostics.Template */:
  7436. errorText = `Could not find template file '${file}'.`;
  7437. break;
  7438. case 1 /* ResourceTypeForDiagnostics.StylesheetFromTemplate */:
  7439. errorText = `Could not find stylesheet file '${file}' linked from the template.`;
  7440. break;
  7441. case 2 /* ResourceTypeForDiagnostics.StylesheetFromDecorator */:
  7442. errorText = `Could not find stylesheet file '${file}'.`;
  7443. break;
  7444. }
  7445. return new checker.FatalDiagnosticError(checker.ErrorCode.COMPONENT_RESOURCE_NOT_FOUND, nodeForError, errorText);
  7446. }
  7447. /**
  7448. * Transforms the given decorator to inline external resources. i.e. if the decorator
  7449. * resolves to `@Component`, the `templateUrl` and `styleUrls` metadata fields will be
  7450. * transformed to their semantically-equivalent inline variants.
  7451. *
  7452. * This method is used for serializing decorators into the class metadata. The emitted
  7453. * class metadata should not refer to external resources as this would be inconsistent
  7454. * with the component definitions/declarations which already inline external resources.
  7455. *
  7456. * Additionally, the references to external resources would require libraries to ship
  7457. * external resources exclusively for the class metadata.
  7458. */
  7459. function transformDecoratorResources(dec, component, styles, template) {
  7460. if (dec.name !== 'Component') {
  7461. return dec;
  7462. }
  7463. // If no external resources are referenced, preserve the original decorator
  7464. // for the best source map experience when the decorator is emitted in TS.
  7465. if (!component.has('templateUrl') &&
  7466. !component.has('styleUrls') &&
  7467. !component.has('styleUrl') &&
  7468. !component.has('styles')) {
  7469. return dec;
  7470. }
  7471. const metadata = new Map(component);
  7472. // Set the `template` property if the `templateUrl` property is set.
  7473. if (metadata.has('templateUrl')) {
  7474. metadata.delete('templateUrl');
  7475. metadata.set('template', ts.factory.createStringLiteral(template.content));
  7476. }
  7477. if (metadata.has('styleUrls') || metadata.has('styleUrl') || metadata.has('styles')) {
  7478. metadata.delete('styles');
  7479. metadata.delete('styleUrls');
  7480. metadata.delete('styleUrl');
  7481. if (styles.length > 0) {
  7482. const styleNodes = styles.reduce((result, style) => {
  7483. if (style.trim().length > 0) {
  7484. result.push(ts.factory.createStringLiteral(style));
  7485. }
  7486. return result;
  7487. }, []);
  7488. if (styleNodes.length > 0) {
  7489. metadata.set('styles', ts.factory.createArrayLiteralExpression(styleNodes));
  7490. }
  7491. }
  7492. }
  7493. // Convert the metadata to TypeScript AST object literal element nodes.
  7494. const newMetadataFields = [];
  7495. for (const [name, value] of metadata.entries()) {
  7496. newMetadataFields.push(ts.factory.createPropertyAssignment(name, value));
  7497. }
  7498. // Return the original decorator with the overridden metadata argument.
  7499. return { ...dec, args: [ts.factory.createObjectLiteralExpression(newMetadataFields)] };
  7500. }
  7501. function extractComponentStyleUrls(evaluator, component) {
  7502. const styleUrlsExpr = component.get('styleUrls');
  7503. const styleUrlExpr = component.get('styleUrl');
  7504. if (styleUrlsExpr !== undefined && styleUrlExpr !== undefined) {
  7505. throw new checker.FatalDiagnosticError(checker.ErrorCode.COMPONENT_INVALID_STYLE_URLS, styleUrlExpr, '@Component cannot define both `styleUrl` and `styleUrls`. ' +
  7506. 'Use `styleUrl` if the component has one stylesheet, or `styleUrls` if it has multiple');
  7507. }
  7508. if (styleUrlsExpr !== undefined) {
  7509. return extractStyleUrlsFromExpression(evaluator, component.get('styleUrls'));
  7510. }
  7511. if (styleUrlExpr !== undefined) {
  7512. const styleUrl = evaluator.evaluate(styleUrlExpr);
  7513. if (typeof styleUrl !== 'string') {
  7514. throw checker.createValueHasWrongTypeError(styleUrlExpr, styleUrl, 'styleUrl must be a string');
  7515. }
  7516. return [
  7517. {
  7518. url: styleUrl,
  7519. source: 2 /* ResourceTypeForDiagnostics.StylesheetFromDecorator */,
  7520. expression: styleUrlExpr,
  7521. },
  7522. ];
  7523. }
  7524. return [];
  7525. }
  7526. function extractStyleUrlsFromExpression(evaluator, styleUrlsExpr) {
  7527. const styleUrls = [];
  7528. if (ts.isArrayLiteralExpression(styleUrlsExpr)) {
  7529. for (const styleUrlExpr of styleUrlsExpr.elements) {
  7530. if (ts.isSpreadElement(styleUrlExpr)) {
  7531. styleUrls.push(...extractStyleUrlsFromExpression(evaluator, styleUrlExpr.expression));
  7532. }
  7533. else {
  7534. const styleUrl = evaluator.evaluate(styleUrlExpr);
  7535. if (typeof styleUrl !== 'string') {
  7536. throw checker.createValueHasWrongTypeError(styleUrlExpr, styleUrl, 'styleUrl must be a string');
  7537. }
  7538. styleUrls.push({
  7539. url: styleUrl,
  7540. source: 2 /* ResourceTypeForDiagnostics.StylesheetFromDecorator */,
  7541. expression: styleUrlExpr,
  7542. });
  7543. }
  7544. }
  7545. }
  7546. else {
  7547. const evaluatedStyleUrls = evaluator.evaluate(styleUrlsExpr);
  7548. if (!isStringArray(evaluatedStyleUrls)) {
  7549. throw checker.createValueHasWrongTypeError(styleUrlsExpr, evaluatedStyleUrls, 'styleUrls must be an array of strings');
  7550. }
  7551. for (const styleUrl of evaluatedStyleUrls) {
  7552. styleUrls.push({
  7553. url: styleUrl,
  7554. source: 2 /* ResourceTypeForDiagnostics.StylesheetFromDecorator */,
  7555. expression: styleUrlsExpr,
  7556. });
  7557. }
  7558. }
  7559. return styleUrls;
  7560. }
  7561. function extractInlineStyleResources(component) {
  7562. const styles = new Set();
  7563. function stringLiteralElements(array) {
  7564. return array.elements.filter((e) => ts.isStringLiteralLike(e));
  7565. }
  7566. const stylesExpr = component.get('styles');
  7567. if (stylesExpr !== undefined) {
  7568. if (ts.isArrayLiteralExpression(stylesExpr)) {
  7569. for (const expression of stringLiteralElements(stylesExpr)) {
  7570. styles.add({ path: null, expression });
  7571. }
  7572. }
  7573. else if (ts.isStringLiteralLike(stylesExpr)) {
  7574. styles.add({ path: null, expression: stylesExpr });
  7575. }
  7576. }
  7577. return styles;
  7578. }
  7579. function _extractTemplateStyleUrls(template) {
  7580. if (template.styleUrls === null) {
  7581. return [];
  7582. }
  7583. const expression = getTemplateDeclarationNodeForError(template.declaration);
  7584. return template.styleUrls.map((url) => ({
  7585. url,
  7586. source: 1 /* ResourceTypeForDiagnostics.StylesheetFromTemplate */,
  7587. expression,
  7588. }));
  7589. }
  7590. /**
  7591. * Represents an Angular component.
  7592. */
  7593. class ComponentSymbol extends DirectiveSymbol {
  7594. usedDirectives = [];
  7595. usedPipes = [];
  7596. isRemotelyScoped = false;
  7597. isEmitAffected(previousSymbol, publicApiAffected) {
  7598. if (!(previousSymbol instanceof ComponentSymbol)) {
  7599. return true;
  7600. }
  7601. // Create an equality function that considers symbols equal if they represent the same
  7602. // declaration, but only if the symbol in the current compilation does not have its public API
  7603. // affected.
  7604. const isSymbolUnaffected = (current, previous) => isReferenceEqual(current, previous) && !publicApiAffected.has(current.symbol);
  7605. // The emit of a component is affected if either of the following is true:
  7606. // 1. The component used to be remotely scoped but no longer is, or vice versa.
  7607. // 2. The list of used directives has changed or any of those directives have had their public
  7608. // API changed. If the used directives have been reordered but not otherwise affected then
  7609. // the component must still be re-emitted, as this may affect directive instantiation order.
  7610. // 3. The list of used pipes has changed, or any of those pipes have had their public API
  7611. // changed.
  7612. return (this.isRemotelyScoped !== previousSymbol.isRemotelyScoped ||
  7613. !isArrayEqual(this.usedDirectives, previousSymbol.usedDirectives, isSymbolUnaffected) ||
  7614. !isArrayEqual(this.usedPipes, previousSymbol.usedPipes, isSymbolUnaffected));
  7615. }
  7616. isTypeCheckBlockAffected(previousSymbol, typeCheckApiAffected) {
  7617. if (!(previousSymbol instanceof ComponentSymbol)) {
  7618. return true;
  7619. }
  7620. // To verify that a used directive is not affected we need to verify that its full inheritance
  7621. // chain is not present in `typeCheckApiAffected`.
  7622. const isInheritanceChainAffected = (symbol) => {
  7623. let currentSymbol = symbol;
  7624. while (currentSymbol instanceof DirectiveSymbol) {
  7625. if (typeCheckApiAffected.has(currentSymbol)) {
  7626. return true;
  7627. }
  7628. currentSymbol = currentSymbol.baseClass;
  7629. }
  7630. return false;
  7631. };
  7632. // Create an equality function that considers directives equal if they represent the same
  7633. // declaration and if the symbol and all symbols it inherits from in the current compilation
  7634. // do not have their type-check API affected.
  7635. const isDirectiveUnaffected = (current, previous) => isReferenceEqual(current, previous) && !isInheritanceChainAffected(current.symbol);
  7636. // Create an equality function that considers pipes equal if they represent the same
  7637. // declaration and if the symbol in the current compilation does not have its type-check
  7638. // API affected.
  7639. const isPipeUnaffected = (current, previous) => isReferenceEqual(current, previous) && !typeCheckApiAffected.has(current.symbol);
  7640. // The emit of a type-check block of a component is affected if either of the following is true:
  7641. // 1. The list of used directives has changed or any of those directives have had their
  7642. // type-check API changed.
  7643. // 2. The list of used pipes has changed, or any of those pipes have had their type-check API
  7644. // changed.
  7645. return (!isArrayEqual(this.usedDirectives, previousSymbol.usedDirectives, isDirectiveUnaffected) ||
  7646. !isArrayEqual(this.usedPipes, previousSymbol.usedPipes, isPipeUnaffected));
  7647. }
  7648. }
  7649. /**
  7650. * Collect the animation names from the static evaluation result.
  7651. * @param value the static evaluation result of the animations
  7652. * @param animationTriggerNames the animation names collected and whether some names could not be
  7653. * statically evaluated.
  7654. */
  7655. function collectAnimationNames(value, animationTriggerNames) {
  7656. if (value instanceof Map) {
  7657. const name = value.get('name');
  7658. if (typeof name === 'string') {
  7659. animationTriggerNames.staticTriggerNames.push(name);
  7660. }
  7661. else {
  7662. animationTriggerNames.includesDynamicAnimations = true;
  7663. }
  7664. }
  7665. else if (Array.isArray(value)) {
  7666. for (const resolvedValue of value) {
  7667. collectAnimationNames(resolvedValue, animationTriggerNames);
  7668. }
  7669. }
  7670. else {
  7671. animationTriggerNames.includesDynamicAnimations = true;
  7672. }
  7673. }
  7674. function isAngularAnimationsReference(reference, symbolName) {
  7675. return (reference.ownedByModuleGuess === '@angular/animations' && reference.debugName === symbolName);
  7676. }
  7677. const animationTriggerResolver = (fn, node, resolve, unresolvable) => {
  7678. const animationTriggerMethodName = 'trigger';
  7679. if (!isAngularAnimationsReference(fn, animationTriggerMethodName)) {
  7680. return unresolvable;
  7681. }
  7682. const triggerNameExpression = node.arguments[0];
  7683. if (!triggerNameExpression) {
  7684. return unresolvable;
  7685. }
  7686. const res = new Map();
  7687. res.set('name', resolve(triggerNameExpression));
  7688. return res;
  7689. };
  7690. function validateAndFlattenComponentImports(imports, expr, isDeferred) {
  7691. const flattened = [];
  7692. const errorMessage = isDeferred
  7693. ? `'deferredImports' must be an array of components, directives, or pipes.`
  7694. : `'imports' must be an array of components, directives, pipes, or NgModules.`;
  7695. if (!Array.isArray(imports)) {
  7696. const error = checker.createValueHasWrongTypeError(expr, imports, errorMessage).toDiagnostic();
  7697. return {
  7698. imports: [],
  7699. diagnostics: [error],
  7700. };
  7701. }
  7702. const diagnostics = [];
  7703. for (let i = 0; i < imports.length; i++) {
  7704. const ref = imports[i];
  7705. if (Array.isArray(ref)) {
  7706. const { imports: childImports, diagnostics: childDiagnostics } = validateAndFlattenComponentImports(ref, expr, isDeferred);
  7707. flattened.push(...childImports);
  7708. diagnostics.push(...childDiagnostics);
  7709. }
  7710. else if (ref instanceof checker.Reference) {
  7711. if (checker.isNamedClassDeclaration(ref.node)) {
  7712. flattened.push(ref);
  7713. }
  7714. else {
  7715. diagnostics.push(checker.createValueHasWrongTypeError(ref.getOriginForDiagnostics(expr), ref, errorMessage).toDiagnostic());
  7716. }
  7717. }
  7718. else if (isLikelyModuleWithProviders(ref)) {
  7719. let origin = expr;
  7720. if (ref instanceof checker.SyntheticValue) {
  7721. // The `ModuleWithProviders` type originated from a foreign function declaration, in which
  7722. // case the original foreign call is available which is used to get a more accurate origin
  7723. // node that points at the specific call expression.
  7724. origin = checker.getOriginNodeForDiagnostics(ref.value.mwpCall, expr);
  7725. }
  7726. diagnostics.push(checker.makeDiagnostic(checker.ErrorCode.COMPONENT_UNKNOWN_IMPORT, origin, `Component imports contains a ModuleWithProviders value, likely the result of a 'Module.forRoot()'-style call. ` +
  7727. `These calls are not used to configure components and are not valid in standalone component imports - ` +
  7728. `consider importing them in the application bootstrap instead.`));
  7729. }
  7730. else {
  7731. let diagnosticNode;
  7732. let diagnosticValue;
  7733. if (ref instanceof checker.DynamicValue) {
  7734. diagnosticNode = ref.node;
  7735. diagnosticValue = ref;
  7736. }
  7737. else if (ts.isArrayLiteralExpression(expr) &&
  7738. expr.elements.length === imports.length &&
  7739. !expr.elements.some(ts.isSpreadAssignment) &&
  7740. !imports.some(Array.isArray)) {
  7741. // Reporting a diagnostic on the entire array can be noisy, especially if the user has a
  7742. // large array. The most common case is that the array will be static so we can reliably
  7743. // trace back a `ResolvedValue` back to its node using its index. This allows us to report
  7744. // the exact node that caused the issue.
  7745. diagnosticNode = expr.elements[i];
  7746. diagnosticValue = ref;
  7747. }
  7748. else {
  7749. diagnosticNode = expr;
  7750. diagnosticValue = imports;
  7751. }
  7752. diagnostics.push(checker.createValueHasWrongTypeError(diagnosticNode, diagnosticValue, errorMessage).toDiagnostic());
  7753. }
  7754. }
  7755. return { imports: flattened, diagnostics };
  7756. }
  7757. /**
  7758. * Inspects `value` to determine if it resembles a `ModuleWithProviders` value. This is an
  7759. * approximation only suitable for error reporting as any resolved object with an `ngModule`
  7760. * key is considered a `ModuleWithProviders`.
  7761. */
  7762. function isLikelyModuleWithProviders(value) {
  7763. if (value instanceof checker.SyntheticValue && isResolvedModuleWithProviders(value)) {
  7764. // This is a `ModuleWithProviders` as extracted from a foreign function call.
  7765. return true;
  7766. }
  7767. if (value instanceof Map && value.has('ngModule')) {
  7768. // A resolved `Map` with `ngModule` property would have been extracted from locally declared
  7769. // functions that return a `ModuleWithProviders` object.
  7770. return true;
  7771. }
  7772. return false;
  7773. }
  7774. const TS_EXTENSIONS = /\.tsx?$/i;
  7775. /**
  7776. * Replace the .ts or .tsx extension of a file with the shim filename suffix.
  7777. */
  7778. function makeShimFileName(fileName, suffix) {
  7779. return checker.absoluteFrom(fileName.replace(TS_EXTENSIONS, suffix));
  7780. }
  7781. /**
  7782. * Generates and tracks shim files for each original `ts.SourceFile`.
  7783. *
  7784. * The `ShimAdapter` provides an API that's designed to be used by a `ts.CompilerHost`
  7785. * implementation and allows it to include synthetic "shim" files in the program that's being
  7786. * created. It works for both freshly created programs as well as with reuse of an older program
  7787. * (which already may contain shim files and thus have a different creation flow).
  7788. */
  7789. class ShimAdapter {
  7790. delegate;
  7791. /**
  7792. * A map of shim file names to the `ts.SourceFile` generated for those shims.
  7793. */
  7794. shims = new Map();
  7795. /**
  7796. * A map of shim file names to existing shims which were part of a previous iteration of this
  7797. * program.
  7798. *
  7799. * Not all of these shims will be inherited into this program.
  7800. */
  7801. priorShims = new Map();
  7802. /**
  7803. * File names which are already known to not be shims.
  7804. *
  7805. * This allows for short-circuit returns without the expense of running regular expressions
  7806. * against the filename repeatedly.
  7807. */
  7808. notShims = new Set();
  7809. /**
  7810. * The shim generators supported by this adapter as well as extra precalculated data facilitating
  7811. * their use.
  7812. */
  7813. generators = [];
  7814. /**
  7815. * A `Set` of shim `ts.SourceFile`s which should not be emitted.
  7816. */
  7817. ignoreForEmit = new Set();
  7818. /**
  7819. * A list of extra filenames which should be considered inputs to program creation.
  7820. *
  7821. * This includes any top-level shims generated for the program, as well as per-file shim names for
  7822. * those files which are included in the root files of the program.
  7823. */
  7824. extraInputFiles;
  7825. /**
  7826. * Extension prefixes of all installed per-file shims.
  7827. */
  7828. extensionPrefixes = [];
  7829. constructor(delegate, tsRootFiles, topLevelGenerators, perFileGenerators, oldProgram) {
  7830. this.delegate = delegate;
  7831. // Initialize `this.generators` with a regex that matches each generator's paths.
  7832. for (const gen of perFileGenerators) {
  7833. // This regex matches paths for shims from this generator. The first (and only) capture group
  7834. // extracts the filename prefix, which can be used to find the original file that was used to
  7835. // generate this shim.
  7836. const pattern = `^(.*)\\.${gen.extensionPrefix}\\.ts$`;
  7837. const regexp = new RegExp(pattern, 'i');
  7838. this.generators.push({
  7839. generator: gen,
  7840. test: regexp,
  7841. suffix: `.${gen.extensionPrefix}.ts`,
  7842. });
  7843. this.extensionPrefixes.push(gen.extensionPrefix);
  7844. }
  7845. // Process top-level generators and pre-generate their shims. Accumulate the list of filenames
  7846. // as extra input files.
  7847. const extraInputFiles = [];
  7848. for (const gen of topLevelGenerators) {
  7849. const sf = gen.makeTopLevelShim();
  7850. checker.sfExtensionData(sf).isTopLevelShim = true;
  7851. if (!gen.shouldEmit) {
  7852. this.ignoreForEmit.add(sf);
  7853. }
  7854. const fileName = checker.absoluteFromSourceFile(sf);
  7855. this.shims.set(fileName, sf);
  7856. extraInputFiles.push(fileName);
  7857. }
  7858. // Add to that list the per-file shims associated with each root file. This is needed because
  7859. // reference tagging alone may not work in TS compilations that have `noResolve` set. Such
  7860. // compilations rely on the list of input files completely describing the program.
  7861. for (const rootFile of tsRootFiles) {
  7862. for (const gen of this.generators) {
  7863. extraInputFiles.push(makeShimFileName(rootFile, gen.suffix));
  7864. }
  7865. }
  7866. this.extraInputFiles = extraInputFiles;
  7867. // If an old program is present, extract all per-file shims into a map, which will be used to
  7868. // generate new versions of those shims.
  7869. if (oldProgram !== null) {
  7870. for (const oldSf of oldProgram.getSourceFiles()) {
  7871. if (oldSf.isDeclarationFile || !checker.isFileShimSourceFile(oldSf)) {
  7872. continue;
  7873. }
  7874. this.priorShims.set(checker.absoluteFromSourceFile(oldSf), oldSf);
  7875. }
  7876. }
  7877. }
  7878. /**
  7879. * Produce a shim `ts.SourceFile` if `fileName` refers to a shim file which should exist in the
  7880. * program.
  7881. *
  7882. * If `fileName` does not refer to a potential shim file, `null` is returned. If a corresponding
  7883. * base file could not be determined, `undefined` is returned instead.
  7884. */
  7885. maybeGenerate(fileName) {
  7886. // Fast path: either this filename has been proven not to be a shim before, or it is a known
  7887. // shim and no generation is required.
  7888. if (this.notShims.has(fileName)) {
  7889. return null;
  7890. }
  7891. else if (this.shims.has(fileName)) {
  7892. return this.shims.get(fileName);
  7893. }
  7894. // .d.ts files can't be shims.
  7895. if (checker.isDtsPath(fileName)) {
  7896. this.notShims.add(fileName);
  7897. return null;
  7898. }
  7899. // This is the first time seeing this path. Try to match it against a shim generator.
  7900. for (const record of this.generators) {
  7901. const match = record.test.exec(fileName);
  7902. if (match === null) {
  7903. continue;
  7904. }
  7905. // The path matched. Extract the filename prefix without the extension.
  7906. const prefix = match[1];
  7907. // This _might_ be a shim, if an underlying base file exists. The base file might be .ts or
  7908. // .tsx.
  7909. let baseFileName = checker.absoluteFrom(prefix + '.ts');
  7910. // Retrieve the original file for which the shim will be generated.
  7911. let inputFile = this.delegate.getSourceFile(baseFileName, ts.ScriptTarget.Latest);
  7912. if (inputFile === undefined) {
  7913. // No .ts file by that name - try .tsx.
  7914. baseFileName = checker.absoluteFrom(prefix + '.tsx');
  7915. inputFile = this.delegate.getSourceFile(baseFileName, ts.ScriptTarget.Latest);
  7916. }
  7917. if (inputFile === undefined || checker.isShim(inputFile)) {
  7918. // This isn't a shim after all since there is no original file which would have triggered
  7919. // its generation, even though the path is right. There are a few reasons why this could
  7920. // occur:
  7921. //
  7922. // * when resolving an import to an .ngfactory.d.ts file, the module resolution algorithm
  7923. // will first look for an .ngfactory.ts file in its place, which will be requested here.
  7924. // * when the user writes a bad import.
  7925. // * when a file is present in one compilation and removed in the next incremental step.
  7926. //
  7927. // Note that this does not add the filename to `notShims`, so this path is not cached.
  7928. // That's okay as these cases above are edge cases and do not occur regularly in normal
  7929. // operations.
  7930. return undefined;
  7931. }
  7932. // Actually generate and cache the shim.
  7933. return this.generateSpecific(fileName, record.generator, inputFile);
  7934. }
  7935. // No generator matched.
  7936. this.notShims.add(fileName);
  7937. return null;
  7938. }
  7939. generateSpecific(fileName, generator, inputFile) {
  7940. let priorShimSf = null;
  7941. if (this.priorShims.has(fileName)) {
  7942. // In the previous program a shim with this name already existed. It's passed to the shim
  7943. // generator which may reuse it instead of generating a fresh shim.
  7944. priorShimSf = this.priorShims.get(fileName);
  7945. this.priorShims.delete(fileName);
  7946. }
  7947. const shimSf = generator.generateShimForFile(inputFile, fileName, priorShimSf);
  7948. // Mark the new generated source file as a shim that originated from this generator.
  7949. checker.sfExtensionData(shimSf).fileShim = {
  7950. extension: generator.extensionPrefix,
  7951. generatedFrom: checker.absoluteFromSourceFile(inputFile),
  7952. };
  7953. if (!generator.shouldEmit) {
  7954. this.ignoreForEmit.add(shimSf);
  7955. }
  7956. this.shims.set(fileName, shimSf);
  7957. return shimSf;
  7958. }
  7959. }
  7960. /**
  7961. * Manipulates the `referencedFiles` property of `ts.SourceFile`s to add references to shim files
  7962. * for each original source file, causing the shims to be loaded into the program as well.
  7963. *
  7964. * `ShimReferenceTagger`s are intended to operate during program creation only.
  7965. */
  7966. class ShimReferenceTagger {
  7967. suffixes;
  7968. /**
  7969. * Tracks which original files have been processed and had shims generated if necessary.
  7970. *
  7971. * This is used to avoid generating shims twice for the same file.
  7972. */
  7973. tagged = new Set();
  7974. /**
  7975. * Whether shim tagging is currently being performed.
  7976. */
  7977. enabled = true;
  7978. constructor(shimExtensions) {
  7979. this.suffixes = shimExtensions.map((extension) => `.${extension}.ts`);
  7980. }
  7981. /**
  7982. * Tag `sf` with any needed references if it's not a shim itself.
  7983. */
  7984. tag(sf) {
  7985. if (!this.enabled ||
  7986. sf.isDeclarationFile ||
  7987. checker.isShim(sf) ||
  7988. this.tagged.has(sf) ||
  7989. !checker.isNonDeclarationTsPath(sf.fileName)) {
  7990. return;
  7991. }
  7992. const ext = checker.sfExtensionData(sf);
  7993. // If this file has never been tagged before, capture its `referencedFiles` in the extension
  7994. // data.
  7995. if (ext.originalReferencedFiles === null) {
  7996. ext.originalReferencedFiles = sf.referencedFiles;
  7997. }
  7998. const referencedFiles = [...ext.originalReferencedFiles];
  7999. const sfPath = checker.absoluteFromSourceFile(sf);
  8000. for (const suffix of this.suffixes) {
  8001. referencedFiles.push({
  8002. fileName: makeShimFileName(sfPath, suffix),
  8003. pos: 0,
  8004. end: 0,
  8005. });
  8006. }
  8007. ext.taggedReferenceFiles = referencedFiles;
  8008. sf.referencedFiles = referencedFiles;
  8009. this.tagged.add(sf);
  8010. }
  8011. /**
  8012. * Disable the `ShimReferenceTagger` and free memory associated with tracking tagged files.
  8013. */
  8014. finalize() {
  8015. this.enabled = false;
  8016. this.tagged.clear();
  8017. }
  8018. }
  8019. /**
  8020. * Delegates all methods of `ts.CompilerHost` to a delegate, with the exception of
  8021. * `getSourceFile`, `fileExists` and `writeFile` which are implemented in `TypeCheckProgramHost`.
  8022. *
  8023. * If a new method is added to `ts.CompilerHost` which is not delegated, a type error will be
  8024. * generated for this class.
  8025. */
  8026. let DelegatingCompilerHost$1 = class DelegatingCompilerHost {
  8027. delegate;
  8028. createHash;
  8029. directoryExists;
  8030. getCancellationToken;
  8031. getCanonicalFileName;
  8032. getCurrentDirectory;
  8033. getDefaultLibFileName;
  8034. getDefaultLibLocation;
  8035. getDirectories;
  8036. getEnvironmentVariable;
  8037. getNewLine;
  8038. getParsedCommandLine;
  8039. getSourceFileByPath;
  8040. readDirectory;
  8041. readFile;
  8042. realpath;
  8043. resolveModuleNames;
  8044. resolveTypeReferenceDirectives;
  8045. trace;
  8046. useCaseSensitiveFileNames;
  8047. getModuleResolutionCache;
  8048. hasInvalidatedResolutions;
  8049. resolveModuleNameLiterals;
  8050. resolveTypeReferenceDirectiveReferences;
  8051. // jsDocParsingMode is not a method like the other elements above
  8052. // TODO: ignore usage can be dropped once 5.2 support is dropped
  8053. get jsDocParsingMode() {
  8054. // @ts-ignore
  8055. return this.delegate.jsDocParsingMode;
  8056. }
  8057. set jsDocParsingMode(mode) {
  8058. // @ts-ignore
  8059. this.delegate.jsDocParsingMode = mode;
  8060. }
  8061. constructor(delegate) {
  8062. // Excluded are 'getSourceFile', 'fileExists' and 'writeFile', which are actually implemented by
  8063. // `TypeCheckProgramHost` below.
  8064. this.delegate = delegate;
  8065. this.createHash = this.delegateMethod('createHash');
  8066. this.directoryExists = this.delegateMethod('directoryExists');
  8067. this.getCancellationToken = this.delegateMethod('getCancellationToken');
  8068. this.getCanonicalFileName = this.delegateMethod('getCanonicalFileName');
  8069. this.getCurrentDirectory = this.delegateMethod('getCurrentDirectory');
  8070. this.getDefaultLibFileName = this.delegateMethod('getDefaultLibFileName');
  8071. this.getDefaultLibLocation = this.delegateMethod('getDefaultLibLocation');
  8072. this.getDirectories = this.delegateMethod('getDirectories');
  8073. this.getEnvironmentVariable = this.delegateMethod('getEnvironmentVariable');
  8074. this.getNewLine = this.delegateMethod('getNewLine');
  8075. this.getParsedCommandLine = this.delegateMethod('getParsedCommandLine');
  8076. this.getSourceFileByPath = this.delegateMethod('getSourceFileByPath');
  8077. this.readDirectory = this.delegateMethod('readDirectory');
  8078. this.readFile = this.delegateMethod('readFile');
  8079. this.realpath = this.delegateMethod('realpath');
  8080. this.resolveModuleNames = this.delegateMethod('resolveModuleNames');
  8081. this.resolveTypeReferenceDirectives = this.delegateMethod('resolveTypeReferenceDirectives');
  8082. this.trace = this.delegateMethod('trace');
  8083. this.useCaseSensitiveFileNames = this.delegateMethod('useCaseSensitiveFileNames');
  8084. this.getModuleResolutionCache = this.delegateMethod('getModuleResolutionCache');
  8085. this.hasInvalidatedResolutions = this.delegateMethod('hasInvalidatedResolutions');
  8086. this.resolveModuleNameLiterals = this.delegateMethod('resolveModuleNameLiterals');
  8087. this.resolveTypeReferenceDirectiveReferences = this.delegateMethod('resolveTypeReferenceDirectiveReferences');
  8088. }
  8089. delegateMethod(name) {
  8090. return this.delegate[name] !== undefined
  8091. ? this.delegate[name].bind(this.delegate)
  8092. : undefined;
  8093. }
  8094. };
  8095. /**
  8096. * A `ts.CompilerHost` which augments source files.
  8097. */
  8098. class UpdatedProgramHost extends DelegatingCompilerHost$1 {
  8099. originalProgram;
  8100. shimExtensionPrefixes;
  8101. /**
  8102. * Map of source file names to `ts.SourceFile` instances.
  8103. */
  8104. sfMap;
  8105. /**
  8106. * The `ShimReferenceTagger` responsible for tagging `ts.SourceFile`s loaded via this host.
  8107. *
  8108. * The `UpdatedProgramHost` is used in the creation of a new `ts.Program`. Even though this new
  8109. * program is based on a prior one, TypeScript will still start from the root files and enumerate
  8110. * all source files to include in the new program. This means that just like during the original
  8111. * program's creation, these source files must be tagged with references to per-file shims in
  8112. * order for those shims to be loaded, and then cleaned up afterwards. Thus the
  8113. * `UpdatedProgramHost` has its own `ShimReferenceTagger` to perform this function.
  8114. */
  8115. shimTagger;
  8116. constructor(sfMap, originalProgram, delegate, shimExtensionPrefixes) {
  8117. super(delegate);
  8118. this.originalProgram = originalProgram;
  8119. this.shimExtensionPrefixes = shimExtensionPrefixes;
  8120. this.shimTagger = new ShimReferenceTagger(this.shimExtensionPrefixes);
  8121. this.sfMap = sfMap;
  8122. }
  8123. getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) {
  8124. // Try to use the same `ts.SourceFile` as the original program, if possible. This guarantees
  8125. // that program reuse will be as efficient as possible.
  8126. let delegateSf = this.originalProgram.getSourceFile(fileName);
  8127. if (delegateSf === undefined) {
  8128. // Something went wrong and a source file is being requested that's not in the original
  8129. // program. Just in case, try to retrieve it from the delegate.
  8130. delegateSf = this.delegate.getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
  8131. }
  8132. if (delegateSf === undefined) {
  8133. return undefined;
  8134. }
  8135. // Look for replacements.
  8136. let sf;
  8137. if (this.sfMap.has(fileName)) {
  8138. sf = this.sfMap.get(fileName);
  8139. checker.copyFileShimData(delegateSf, sf);
  8140. }
  8141. else {
  8142. sf = delegateSf;
  8143. }
  8144. // TypeScript doesn't allow returning redirect source files. To avoid unforeseen errors we
  8145. // return the original source file instead of the redirect target.
  8146. sf = checker.toUnredirectedSourceFile(sf);
  8147. this.shimTagger.tag(sf);
  8148. return sf;
  8149. }
  8150. postProgramCreationCleanup() {
  8151. this.shimTagger.finalize();
  8152. }
  8153. writeFile() {
  8154. throw new Error(`TypeCheckProgramHost should never write files`);
  8155. }
  8156. fileExists(fileName) {
  8157. return this.sfMap.has(fileName) || this.delegate.fileExists(fileName);
  8158. }
  8159. }
  8160. /**
  8161. * Updates a `ts.Program` instance with a new one that incorporates specific changes, using the
  8162. * TypeScript compiler APIs for incremental program creation.
  8163. */
  8164. class TsCreateProgramDriver {
  8165. originalProgram;
  8166. originalHost;
  8167. options;
  8168. shimExtensionPrefixes;
  8169. /**
  8170. * A map of source file paths to replacement `ts.SourceFile`s for those paths.
  8171. *
  8172. * Effectively, this tracks the delta between the user's program (represented by the
  8173. * `originalHost`) and the template type-checking program being managed.
  8174. */
  8175. sfMap = new Map();
  8176. program;
  8177. constructor(originalProgram, originalHost, options, shimExtensionPrefixes) {
  8178. this.originalProgram = originalProgram;
  8179. this.originalHost = originalHost;
  8180. this.options = options;
  8181. this.shimExtensionPrefixes = shimExtensionPrefixes;
  8182. this.program = this.originalProgram;
  8183. }
  8184. supportsInlineOperations = true;
  8185. getProgram() {
  8186. return this.program;
  8187. }
  8188. updateFiles(contents, updateMode) {
  8189. if (contents.size === 0) {
  8190. // No changes have been requested. Is it safe to skip updating entirely?
  8191. // If UpdateMode is Incremental, then yes. If UpdateMode is Complete, then it's safe to skip
  8192. // only if there are no active changes already (that would be cleared by the update).
  8193. if (updateMode !== checker.UpdateMode.Complete || this.sfMap.size === 0) {
  8194. // No changes would be made to the `ts.Program` anyway, so it's safe to do nothing here.
  8195. return;
  8196. }
  8197. }
  8198. if (updateMode === checker.UpdateMode.Complete) {
  8199. this.sfMap.clear();
  8200. }
  8201. for (const [filePath, { newText, originalFile }] of contents.entries()) {
  8202. const sf = ts.createSourceFile(filePath, newText, ts.ScriptTarget.Latest, true);
  8203. if (originalFile !== null) {
  8204. sf[checker.NgOriginalFile] = originalFile;
  8205. }
  8206. this.sfMap.set(filePath, sf);
  8207. }
  8208. const host = new UpdatedProgramHost(this.sfMap, this.originalProgram, this.originalHost, this.shimExtensionPrefixes);
  8209. const oldProgram = this.program;
  8210. // Retag the old program's `ts.SourceFile`s with shim tags, to allow TypeScript to reuse the
  8211. // most data.
  8212. checker.retagAllTsFiles(oldProgram);
  8213. this.program = ts.createProgram({
  8214. host,
  8215. rootNames: this.program.getRootFileNames(),
  8216. options: this.options,
  8217. oldProgram,
  8218. });
  8219. host.postProgramCreationCleanup();
  8220. // Only untag the old program. The new program needs to keep the tagged files, because as of
  8221. // TS 5.5 not having the files tagged while producing diagnostics can lead to errors. See:
  8222. // https://github.com/microsoft/TypeScript/pull/58398
  8223. checker.untagAllTsFiles(oldProgram);
  8224. }
  8225. }
  8226. /*!
  8227. * @license
  8228. * Copyright Google LLC All Rights Reserved.
  8229. *
  8230. * Use of this source code is governed by an MIT-style license that can be
  8231. * found in the LICENSE file at https://angular.dev/license
  8232. */
  8233. /**
  8234. * Determines the file-level dependencies that the HMR initializer needs to capture and pass along.
  8235. * @param sourceFile File in which the file is being compiled.
  8236. * @param definition Compiled component definition.
  8237. * @param factory Compiled component factory.
  8238. * @param deferBlockMetadata Metadata about the defer blocks in the component.
  8239. * @param classMetadata Compiled `setClassMetadata` expression, if any.
  8240. * @param debugInfo Compiled `setClassDebugInfo` expression, if any.
  8241. */
  8242. function extractHmrDependencies(node, definition, factory, deferBlockMetadata, classMetadata, debugInfo, reflection, evaluator) {
  8243. const name = ts.isClassDeclaration(node) && node.name ? node.name.text : null;
  8244. const visitor = new PotentialTopLevelReadsVisitor();
  8245. const sourceFile = ts.getOriginalNode(node).getSourceFile();
  8246. // Visit all of the compiled expressions to look for potential
  8247. // local references that would have to be retained.
  8248. definition.expression.visitExpression(visitor, null);
  8249. definition.statements.forEach((statement) => statement.visitStatement(visitor, null));
  8250. factory.initializer?.visitExpression(visitor, null);
  8251. factory.statements.forEach((statement) => statement.visitStatement(visitor, null));
  8252. classMetadata?.visitStatement(visitor, null);
  8253. debugInfo?.visitStatement(visitor, null);
  8254. if (deferBlockMetadata.mode === 0 /* DeferBlockDepsEmitMode.PerBlock */) {
  8255. deferBlockMetadata.blocks.forEach((loader) => loader?.visitExpression(visitor, null));
  8256. }
  8257. else {
  8258. deferBlockMetadata.dependenciesFn?.visitExpression(visitor, null);
  8259. }
  8260. // Filter out only the references to defined top-level symbols. This allows us to ignore local
  8261. // variables inside of functions. Note that we filter out the class name since it is always
  8262. // defined and it saves us having to repeat this logic wherever the locals are consumed.
  8263. const availableTopLevel = getTopLevelDeclarationNames(sourceFile);
  8264. const local = [];
  8265. const seenLocals = new Set();
  8266. for (const readNode of visitor.allReads) {
  8267. const readName = readNode instanceof checker.ReadVarExpr ? readNode.name : readNode.text;
  8268. if (readName !== name && !seenLocals.has(readName) && availableTopLevel.has(readName)) {
  8269. const runtimeRepresentation = getRuntimeRepresentation(readNode, reflection, evaluator);
  8270. if (runtimeRepresentation === null) {
  8271. return null;
  8272. }
  8273. local.push({ name: readName, runtimeRepresentation });
  8274. seenLocals.add(readName);
  8275. }
  8276. }
  8277. return {
  8278. local,
  8279. external: Array.from(visitor.namespaceReads, (name, index) => ({
  8280. moduleName: name,
  8281. assignedName: `ɵhmr${index}`,
  8282. })),
  8283. };
  8284. }
  8285. /**
  8286. * Gets a node that can be used to represent an identifier in the HMR replacement code at runtime.
  8287. */
  8288. function getRuntimeRepresentation(node, reflection, evaluator) {
  8289. if (node instanceof checker.ReadVarExpr) {
  8290. return checker.variable(node.name);
  8291. }
  8292. // Const enums can't be passed by reference, because their values are inlined.
  8293. // Pass in an object literal with all of the values instead.
  8294. if (isConstEnumReference(node, reflection)) {
  8295. const evaluated = evaluator.evaluate(node);
  8296. if (evaluated instanceof Map) {
  8297. const members = [];
  8298. for (const [name, value] of evaluated.entries()) {
  8299. if (value instanceof checker.EnumValue &&
  8300. (value.resolved == null ||
  8301. typeof value.resolved === 'string' ||
  8302. typeof value.resolved === 'boolean' ||
  8303. typeof value.resolved === 'number')) {
  8304. members.push({
  8305. key: name,
  8306. quoted: false,
  8307. value: checker.literal(value.resolved),
  8308. });
  8309. }
  8310. else {
  8311. // TS is pretty restrictive about what values can be in a const enum so our evaluator
  8312. // should be able to handle them, however if we happen to hit such a case, we return null
  8313. // so the HMR update can be invalidated.
  8314. return null;
  8315. }
  8316. }
  8317. return checker.literalMap(members);
  8318. }
  8319. }
  8320. return checker.variable(node.text);
  8321. }
  8322. /**
  8323. * Gets the names of all top-level declarations within the file (imports, declared classes etc).
  8324. * @param sourceFile File in which to search for locals.
  8325. */
  8326. function getTopLevelDeclarationNames(sourceFile) {
  8327. const results = new Set();
  8328. // Only look through the top-level statements.
  8329. for (const node of sourceFile.statements) {
  8330. // Class, function and const enum declarations need to be captured since they correspond
  8331. // to runtime code. Intentionally excludes interfaces and type declarations.
  8332. if (ts.isClassDeclaration(node) ||
  8333. ts.isFunctionDeclaration(node) ||
  8334. ts.isEnumDeclaration(node)) {
  8335. if (node.name) {
  8336. results.add(node.name.text);
  8337. }
  8338. continue;
  8339. }
  8340. // Variable declarations.
  8341. if (ts.isVariableStatement(node)) {
  8342. for (const decl of node.declarationList.declarations) {
  8343. trackBindingName(decl.name, results);
  8344. }
  8345. continue;
  8346. }
  8347. // Import declarations.
  8348. if (ts.isImportDeclaration(node) && node.importClause) {
  8349. const importClause = node.importClause;
  8350. // Skip over type-only imports since they won't be emitted to JS.
  8351. if (importClause.isTypeOnly) {
  8352. continue;
  8353. }
  8354. // import foo from 'foo'
  8355. if (importClause.name) {
  8356. results.add(importClause.name.text);
  8357. }
  8358. if (importClause.namedBindings) {
  8359. const namedBindings = importClause.namedBindings;
  8360. if (ts.isNamespaceImport(namedBindings)) {
  8361. // import * as foo from 'foo';
  8362. results.add(namedBindings.name.text);
  8363. }
  8364. else {
  8365. // import {foo} from 'foo';
  8366. namedBindings.elements.forEach((el) => {
  8367. if (!el.isTypeOnly) {
  8368. results.add(el.name.text);
  8369. }
  8370. });
  8371. }
  8372. }
  8373. continue;
  8374. }
  8375. }
  8376. return results;
  8377. }
  8378. /**
  8379. * Adds all the variables declared through a `ts.BindingName` to a set of results.
  8380. * @param node Node from which to start searching for variables.
  8381. * @param results Set to which to add the matches.
  8382. */
  8383. function trackBindingName(node, results) {
  8384. if (ts.isIdentifier(node)) {
  8385. results.add(node.text);
  8386. }
  8387. else {
  8388. for (const el of node.elements) {
  8389. if (!ts.isOmittedExpression(el)) {
  8390. trackBindingName(el.name, results);
  8391. }
  8392. }
  8393. }
  8394. }
  8395. /**
  8396. * Visitor that will traverse an AST looking for potential top-level variable reads.
  8397. * The reads are "potential", because the visitor doesn't account for local variables
  8398. * inside functions.
  8399. */
  8400. class PotentialTopLevelReadsVisitor extends checker.RecursiveAstVisitor$1 {
  8401. allReads = new Set();
  8402. namespaceReads = new Set();
  8403. visitExternalExpr(ast, context) {
  8404. if (ast.value.moduleName !== null) {
  8405. this.namespaceReads.add(ast.value.moduleName);
  8406. }
  8407. super.visitExternalExpr(ast, context);
  8408. }
  8409. visitReadVarExpr(ast, context) {
  8410. this.allReads.add(ast);
  8411. super.visitReadVarExpr(ast, context);
  8412. }
  8413. visitWrappedNodeExpr(ast, context) {
  8414. if (this.isTypeScriptNode(ast.node)) {
  8415. this.addAllTopLevelIdentifiers(ast.node);
  8416. }
  8417. super.visitWrappedNodeExpr(ast, context);
  8418. }
  8419. /**
  8420. * Traverses a TypeScript AST and tracks all the top-level reads.
  8421. * @param node Node from which to start the traversal.
  8422. */
  8423. addAllTopLevelIdentifiers = (node) => {
  8424. if (ts.isIdentifier(node) && this.isTopLevelIdentifierReference(node)) {
  8425. this.allReads.add(node);
  8426. }
  8427. else {
  8428. ts.forEachChild(node, this.addAllTopLevelIdentifiers);
  8429. }
  8430. };
  8431. /**
  8432. * TypeScript identifiers are used both when referring to a variable (e.g. `console.log(foo)`)
  8433. * and for names (e.g. `{foo: 123}`). This function determines if the identifier is a top-level
  8434. * variable read, rather than a nested name.
  8435. * @param identifier Identifier to check.
  8436. */
  8437. isTopLevelIdentifierReference(identifier) {
  8438. let node = identifier;
  8439. let parent = node.parent;
  8440. // The parent might be undefined for a synthetic node or if `setParentNodes` is set to false
  8441. // when the SourceFile was created. We can account for such cases using the type checker, at
  8442. // the expense of performance. At the moment of writing, we're keeping it simple since the
  8443. // compiler sets `setParentNodes: true`.
  8444. if (!parent) {
  8445. return false;
  8446. }
  8447. // Unwrap parenthesized identifiers, but use the closest parenthesized expression
  8448. // as the reference node so that we can check cases like `{prop: ((value))}`.
  8449. if (ts.isParenthesizedExpression(parent) && parent.expression === node) {
  8450. while (parent && ts.isParenthesizedExpression(parent)) {
  8451. node = parent;
  8452. parent = parent.parent;
  8453. }
  8454. }
  8455. // Identifier referenced at the top level. Unlikely.
  8456. if (ts.isSourceFile(parent)) {
  8457. return true;
  8458. }
  8459. // Identifier used inside a call is only top-level if it's an argument.
  8460. // This also covers decorators since their expression is usually a call.
  8461. if (ts.isCallExpression(parent)) {
  8462. return parent.expression === node || parent.arguments.includes(node);
  8463. }
  8464. // Identifier used in a nested expression is only top-level if it's the actual expression.
  8465. if (ts.isExpressionStatement(parent) ||
  8466. ts.isPropertyAccessExpression(parent) ||
  8467. ts.isComputedPropertyName(parent) ||
  8468. ts.isTemplateSpan(parent) ||
  8469. ts.isSpreadAssignment(parent) ||
  8470. ts.isSpreadElement(parent) ||
  8471. ts.isAwaitExpression(parent) ||
  8472. ts.isNonNullExpression(parent) ||
  8473. ts.isIfStatement(parent) ||
  8474. ts.isDoStatement(parent) ||
  8475. ts.isWhileStatement(parent) ||
  8476. ts.isSwitchStatement(parent) ||
  8477. ts.isCaseClause(parent) ||
  8478. ts.isThrowStatement(parent) ||
  8479. ts.isNewExpression(parent)) {
  8480. return parent.expression === node;
  8481. }
  8482. // Identifier used in an array is only top-level if it's one of the elements.
  8483. if (ts.isArrayLiteralExpression(parent)) {
  8484. return parent.elements.includes(node);
  8485. }
  8486. // If the parent is an initialized node, the identifier is
  8487. // at the top level if it's the initializer itself.
  8488. if (ts.isPropertyAssignment(parent) ||
  8489. ts.isParameter(parent) ||
  8490. ts.isBindingElement(parent) ||
  8491. ts.isPropertyDeclaration(parent) ||
  8492. ts.isEnumMember(parent)) {
  8493. return parent.initializer === node;
  8494. }
  8495. // Identifier in a function is top level if it's either the name or the initializer.
  8496. if (ts.isVariableDeclaration(parent)) {
  8497. return parent.name === node || parent.initializer === node;
  8498. }
  8499. // Identifier in a declaration is only top level if it's the name.
  8500. // In shorthand assignments the name is also the value.
  8501. if (ts.isClassDeclaration(parent) ||
  8502. ts.isFunctionDeclaration(parent) ||
  8503. ts.isShorthandPropertyAssignment(parent)) {
  8504. return parent.name === node;
  8505. }
  8506. if (ts.isElementAccessExpression(parent)) {
  8507. return parent.expression === node || parent.argumentExpression === node;
  8508. }
  8509. if (ts.isBinaryExpression(parent)) {
  8510. return parent.left === node || parent.right === node;
  8511. }
  8512. if (ts.isForInStatement(parent) || ts.isForOfStatement(parent)) {
  8513. return parent.expression === node || parent.initializer === node;
  8514. }
  8515. if (ts.isForStatement(parent)) {
  8516. return (parent.condition === node || parent.initializer === node || parent.incrementor === node);
  8517. }
  8518. if (ts.isArrowFunction(parent)) {
  8519. return parent.body === node;
  8520. }
  8521. // It's unlikely that we'll run into imports/exports in this use case.
  8522. // We handle them since it's simple and for completeness' sake.
  8523. if (ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent)) {
  8524. return (parent.propertyName || parent.name) === node;
  8525. }
  8526. if (ts.isConditionalExpression(parent)) {
  8527. return parent.condition === node || parent.whenFalse === node || parent.whenTrue === node;
  8528. }
  8529. // Otherwise it's not top-level.
  8530. return false;
  8531. }
  8532. /** Checks if a value is a TypeScript AST node. */
  8533. isTypeScriptNode(value) {
  8534. // If this is too permissive, we can also check for `getSourceFile`. This code runs
  8535. // on a narrow set of use cases so checking for `kind` should be enough.
  8536. return !!value && typeof value.kind === 'number';
  8537. }
  8538. }
  8539. /** Checks whether a node is a reference to a const enum. */
  8540. function isConstEnumReference(node, reflection) {
  8541. const parent = node.parent;
  8542. // Only check identifiers that are in the form of `Foo.bar` where `Foo` is the node being checked.
  8543. if (!parent ||
  8544. !ts.isPropertyAccessExpression(parent) ||
  8545. parent.expression !== node ||
  8546. !ts.isIdentifier(parent.name)) {
  8547. return false;
  8548. }
  8549. const declaration = reflection.getDeclarationOfIdentifier(node);
  8550. return (declaration !== null &&
  8551. ts.isEnumDeclaration(declaration.node) &&
  8552. !!declaration.node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ConstKeyword));
  8553. }
  8554. /*!
  8555. * @license
  8556. * Copyright Google LLC All Rights Reserved.
  8557. *
  8558. * Use of this source code is governed by an MIT-style license that can be
  8559. * found in the LICENSE file at https://angular.dev/license
  8560. */
  8561. /**
  8562. * Extracts the HMR metadata for a class declaration.
  8563. * @param clazz Class being analyzed.
  8564. * @param reflection Reflection host.
  8565. * @param compilerHost Compiler host to use when resolving file names.
  8566. * @param rootDirs Root directories configured by the user.
  8567. * @param definition Analyzed component definition.
  8568. * @param factory Analyzed component factory.
  8569. * @param deferBlockMetadata Metadata about the defer blocks in the component.
  8570. * @param classMetadata Analyzed `setClassMetadata` expression, if any.
  8571. * @param debugInfo Analyzed `setClassDebugInfo` expression, if any.
  8572. */
  8573. function extractHmrMetatadata(clazz, reflection, evaluator, compilerHost, rootDirs, definition, factory, deferBlockMetadata, classMetadata, debugInfo) {
  8574. if (!reflection.isClass(clazz)) {
  8575. return null;
  8576. }
  8577. const sourceFile = ts.getOriginalNode(clazz).getSourceFile();
  8578. const filePath = getProjectRelativePath(sourceFile.fileName, rootDirs, compilerHost) ||
  8579. compilerHost.getCanonicalFileName(sourceFile.fileName);
  8580. const dependencies = extractHmrDependencies(clazz, definition, factory, deferBlockMetadata, classMetadata, debugInfo, reflection, evaluator);
  8581. if (dependencies === null) {
  8582. return null;
  8583. }
  8584. const meta = {
  8585. type: new checker.WrappedNodeExpr(clazz.name),
  8586. className: clazz.name.text,
  8587. filePath,
  8588. localDependencies: dependencies.local,
  8589. namespaceDependencies: dependencies.external,
  8590. };
  8591. return meta;
  8592. }
  8593. /*!
  8594. * @license
  8595. * Copyright Google LLC All Rights Reserved.
  8596. *
  8597. * Use of this source code is governed by an MIT-style license that can be
  8598. * found in the LICENSE file at https://angular.dev/license
  8599. */
  8600. /**
  8601. * Gets the declaration for the function that replaces the metadata of a class during HMR.
  8602. * @param compilationResults Code generated for the class during compilation.
  8603. * @param meta HMR metadata about the class.
  8604. * @param declaration Class for which the update declaration is being generated.
  8605. */
  8606. function getHmrUpdateDeclaration(compilationResults, constantStatements, meta, declaration) {
  8607. const namespaceSpecifiers = meta.namespaceDependencies.reduce((result, current) => {
  8608. result.set(current.moduleName, current.assignedName);
  8609. return result;
  8610. }, new Map());
  8611. const importRewriter = new HmrModuleImportRewriter(namespaceSpecifiers);
  8612. const importManager = new checker.ImportManager({
  8613. ...checker.presetImportManagerForceNamespaceImports,
  8614. rewriter: importRewriter,
  8615. });
  8616. const callback = compileHmrUpdateCallback(compilationResults, constantStatements, meta);
  8617. const sourceFile = ts.getOriginalNode(declaration).getSourceFile();
  8618. const node = checker.translateStatement(sourceFile, callback, importManager);
  8619. // The output AST doesn't support modifiers so we have to emit to
  8620. // TS and then update the declaration to add `export default`.
  8621. return ts.factory.updateFunctionDeclaration(node, [
  8622. ts.factory.createToken(ts.SyntaxKind.ExportKeyword),
  8623. ts.factory.createToken(ts.SyntaxKind.DefaultKeyword),
  8624. ], node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body);
  8625. }
  8626. class HmrModuleImportRewriter {
  8627. lookup;
  8628. constructor(lookup) {
  8629. this.lookup = lookup;
  8630. }
  8631. rewriteNamespaceImportIdentifier(specifier, moduleName) {
  8632. return this.lookup.has(moduleName) ? this.lookup.get(moduleName) : specifier;
  8633. }
  8634. rewriteSymbol(symbol) {
  8635. return symbol;
  8636. }
  8637. rewriteSpecifier(specifier) {
  8638. return specifier;
  8639. }
  8640. }
  8641. const EMPTY_ARRAY = [];
  8642. const isUsedDirective = (decl) => decl.kind === checker.R3TemplateDependencyKind.Directive;
  8643. const isUsedPipe = (decl) => decl.kind === checker.R3TemplateDependencyKind.Pipe;
  8644. /**
  8645. * `DecoratorHandler` which handles the `@Component` annotation.
  8646. */
  8647. class ComponentDecoratorHandler {
  8648. reflector;
  8649. evaluator;
  8650. metaRegistry;
  8651. metaReader;
  8652. scopeReader;
  8653. compilerHost;
  8654. scopeRegistry;
  8655. typeCheckScopeRegistry;
  8656. resourceRegistry;
  8657. isCore;
  8658. strictCtorDeps;
  8659. resourceLoader;
  8660. rootDirs;
  8661. defaultPreserveWhitespaces;
  8662. i18nUseExternalIds;
  8663. enableI18nLegacyMessageIdFormat;
  8664. usePoisonedData;
  8665. i18nNormalizeLineEndingsInICUs;
  8666. moduleResolver;
  8667. cycleAnalyzer;
  8668. cycleHandlingStrategy;
  8669. refEmitter;
  8670. referencesRegistry;
  8671. depTracker;
  8672. injectableRegistry;
  8673. semanticDepGraphUpdater;
  8674. annotateForClosureCompiler;
  8675. perf;
  8676. hostDirectivesResolver;
  8677. importTracker;
  8678. includeClassMetadata;
  8679. compilationMode;
  8680. deferredSymbolTracker;
  8681. forbidOrphanRendering;
  8682. enableBlockSyntax;
  8683. enableLetSyntax;
  8684. externalRuntimeStyles;
  8685. localCompilationExtraImportsTracker;
  8686. jitDeclarationRegistry;
  8687. i18nPreserveSignificantWhitespace;
  8688. strictStandalone;
  8689. enableHmr;
  8690. implicitStandaloneValue;
  8691. constructor(reflector, evaluator, metaRegistry, metaReader, scopeReader, compilerHost, scopeRegistry, typeCheckScopeRegistry, resourceRegistry, isCore, strictCtorDeps, resourceLoader, rootDirs, defaultPreserveWhitespaces, i18nUseExternalIds, enableI18nLegacyMessageIdFormat, usePoisonedData, i18nNormalizeLineEndingsInICUs, moduleResolver, cycleAnalyzer, cycleHandlingStrategy, refEmitter, referencesRegistry, depTracker, injectableRegistry, semanticDepGraphUpdater, annotateForClosureCompiler, perf, hostDirectivesResolver, importTracker, includeClassMetadata, compilationMode, deferredSymbolTracker, forbidOrphanRendering, enableBlockSyntax, enableLetSyntax, externalRuntimeStyles, localCompilationExtraImportsTracker, jitDeclarationRegistry, i18nPreserveSignificantWhitespace, strictStandalone, enableHmr, implicitStandaloneValue) {
  8692. this.reflector = reflector;
  8693. this.evaluator = evaluator;
  8694. this.metaRegistry = metaRegistry;
  8695. this.metaReader = metaReader;
  8696. this.scopeReader = scopeReader;
  8697. this.compilerHost = compilerHost;
  8698. this.scopeRegistry = scopeRegistry;
  8699. this.typeCheckScopeRegistry = typeCheckScopeRegistry;
  8700. this.resourceRegistry = resourceRegistry;
  8701. this.isCore = isCore;
  8702. this.strictCtorDeps = strictCtorDeps;
  8703. this.resourceLoader = resourceLoader;
  8704. this.rootDirs = rootDirs;
  8705. this.defaultPreserveWhitespaces = defaultPreserveWhitespaces;
  8706. this.i18nUseExternalIds = i18nUseExternalIds;
  8707. this.enableI18nLegacyMessageIdFormat = enableI18nLegacyMessageIdFormat;
  8708. this.usePoisonedData = usePoisonedData;
  8709. this.i18nNormalizeLineEndingsInICUs = i18nNormalizeLineEndingsInICUs;
  8710. this.moduleResolver = moduleResolver;
  8711. this.cycleAnalyzer = cycleAnalyzer;
  8712. this.cycleHandlingStrategy = cycleHandlingStrategy;
  8713. this.refEmitter = refEmitter;
  8714. this.referencesRegistry = referencesRegistry;
  8715. this.depTracker = depTracker;
  8716. this.injectableRegistry = injectableRegistry;
  8717. this.semanticDepGraphUpdater = semanticDepGraphUpdater;
  8718. this.annotateForClosureCompiler = annotateForClosureCompiler;
  8719. this.perf = perf;
  8720. this.hostDirectivesResolver = hostDirectivesResolver;
  8721. this.importTracker = importTracker;
  8722. this.includeClassMetadata = includeClassMetadata;
  8723. this.compilationMode = compilationMode;
  8724. this.deferredSymbolTracker = deferredSymbolTracker;
  8725. this.forbidOrphanRendering = forbidOrphanRendering;
  8726. this.enableBlockSyntax = enableBlockSyntax;
  8727. this.enableLetSyntax = enableLetSyntax;
  8728. this.externalRuntimeStyles = externalRuntimeStyles;
  8729. this.localCompilationExtraImportsTracker = localCompilationExtraImportsTracker;
  8730. this.jitDeclarationRegistry = jitDeclarationRegistry;
  8731. this.i18nPreserveSignificantWhitespace = i18nPreserveSignificantWhitespace;
  8732. this.strictStandalone = strictStandalone;
  8733. this.enableHmr = enableHmr;
  8734. this.implicitStandaloneValue = implicitStandaloneValue;
  8735. this.extractTemplateOptions = {
  8736. enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
  8737. i18nNormalizeLineEndingsInICUs: this.i18nNormalizeLineEndingsInICUs,
  8738. usePoisonedData: this.usePoisonedData,
  8739. enableBlockSyntax: this.enableBlockSyntax,
  8740. enableLetSyntax: this.enableLetSyntax,
  8741. preserveSignificantWhitespace: this.i18nPreserveSignificantWhitespace,
  8742. };
  8743. // Dependencies can't be deferred during HMR, because the HMR update module can't have
  8744. // dynamic imports and its dependencies need to be passed in directly. If dependencies
  8745. // are deferred, their imports will be deleted so we may lose the reference to them.
  8746. this.canDeferDeps = !enableHmr;
  8747. }
  8748. literalCache = new Map();
  8749. elementSchemaRegistry = new checker.DomElementSchemaRegistry();
  8750. /**
  8751. * During the asynchronous preanalyze phase, it's necessary to parse the template to extract
  8752. * any potential <link> tags which might need to be loaded. This cache ensures that work is not
  8753. * thrown away, and the parsed template is reused during the analyze phase.
  8754. */
  8755. preanalyzeTemplateCache = new Map();
  8756. preanalyzeStylesCache = new Map();
  8757. /** Whether generated code for a component can defer its dependencies. */
  8758. canDeferDeps;
  8759. extractTemplateOptions;
  8760. precedence = checker.HandlerPrecedence.PRIMARY;
  8761. name = 'ComponentDecoratorHandler';
  8762. detect(node, decorators) {
  8763. if (!decorators) {
  8764. return undefined;
  8765. }
  8766. const decorator = checker.findAngularDecorator(decorators, 'Component', this.isCore);
  8767. if (decorator !== undefined) {
  8768. return {
  8769. trigger: decorator.node,
  8770. decorator,
  8771. metadata: decorator,
  8772. };
  8773. }
  8774. else {
  8775. return undefined;
  8776. }
  8777. }
  8778. preanalyze(node, decorator) {
  8779. // In preanalyze, resource URLs associated with the component are asynchronously preloaded via
  8780. // the resourceLoader. This is the only time async operations are allowed for a component.
  8781. // These resources are:
  8782. //
  8783. // - the templateUrl, if there is one
  8784. // - any styleUrls if present
  8785. // - any stylesheets referenced from <link> tags in the template itself
  8786. //
  8787. // As a result of the last one, the template must be parsed as part of preanalysis to extract
  8788. // <link> tags, which may involve waiting for the templateUrl to be resolved first.
  8789. // If preloading isn't possible, then skip this step.
  8790. if (!this.resourceLoader.canPreload) {
  8791. return undefined;
  8792. }
  8793. const meta = resolveLiteral(decorator, this.literalCache);
  8794. const component = checker.reflectObjectLiteral(meta);
  8795. const containingFile = node.getSourceFile().fileName;
  8796. const resolveStyleUrl = (styleUrl) => {
  8797. try {
  8798. const resourceUrl = this.resourceLoader.resolve(styleUrl, containingFile);
  8799. return this.resourceLoader.preload(resourceUrl, {
  8800. type: 'style',
  8801. containingFile,
  8802. className: node.name.text,
  8803. });
  8804. }
  8805. catch {
  8806. // Don't worry about failures to preload. We can handle this problem during analysis by
  8807. // producing a diagnostic.
  8808. return undefined;
  8809. }
  8810. };
  8811. // A Promise that waits for the template and all <link>ed styles within it to be preloaded.
  8812. const templateAndTemplateStyleResources = preloadAndParseTemplate(this.evaluator, this.resourceLoader, this.depTracker, this.preanalyzeTemplateCache, node, decorator, component, containingFile, this.defaultPreserveWhitespaces, this.extractTemplateOptions, this.compilationMode).then((template) => {
  8813. if (template === null) {
  8814. return { templateStyles: [], templateStyleUrls: [] };
  8815. }
  8816. let templateUrl;
  8817. if (template.sourceMapping.type === 'external') {
  8818. templateUrl = template.sourceMapping.templateUrl;
  8819. }
  8820. return {
  8821. templateUrl,
  8822. templateStyles: template.styles,
  8823. templateStyleUrls: template.styleUrls,
  8824. };
  8825. });
  8826. // Extract all the styleUrls in the decorator.
  8827. const componentStyleUrls = extractComponentStyleUrls(this.evaluator, component);
  8828. return templateAndTemplateStyleResources.then(async (templateInfo) => {
  8829. // Extract inline styles, process, and cache for use in synchronous analyze phase
  8830. let styles = null;
  8831. // Order plus className allows inline styles to be identified per component by a preprocessor
  8832. let orderOffset = 0;
  8833. const rawStyles = checker.parseDirectiveStyles(component, this.evaluator, this.compilationMode);
  8834. if (rawStyles?.length) {
  8835. styles = await Promise.all(rawStyles.map((style) => this.resourceLoader.preprocessInline(style, {
  8836. type: 'style',
  8837. containingFile,
  8838. order: orderOffset++,
  8839. className: node.name.text,
  8840. })));
  8841. }
  8842. if (templateInfo.templateStyles) {
  8843. styles ??= [];
  8844. styles.push(...(await Promise.all(templateInfo.templateStyles.map((style) => this.resourceLoader.preprocessInline(style, {
  8845. type: 'style',
  8846. containingFile: templateInfo.templateUrl ?? containingFile,
  8847. order: orderOffset++,
  8848. className: node.name.text,
  8849. })))));
  8850. }
  8851. this.preanalyzeStylesCache.set(node, styles);
  8852. if (this.externalRuntimeStyles) {
  8853. // No preanalysis required for style URLs with external runtime styles
  8854. return;
  8855. }
  8856. // Wait for both the template and all styleUrl resources to resolve.
  8857. await Promise.all([
  8858. ...componentStyleUrls.map((styleUrl) => resolveStyleUrl(styleUrl.url)),
  8859. ...templateInfo.templateStyleUrls.map((url) => resolveStyleUrl(url)),
  8860. ]);
  8861. });
  8862. }
  8863. analyze(node, decorator) {
  8864. this.perf.eventCount(checker.PerfEvent.AnalyzeComponent);
  8865. const containingFile = node.getSourceFile().fileName;
  8866. this.literalCache.delete(decorator);
  8867. let diagnostics;
  8868. let isPoisoned = false;
  8869. // @Component inherits @Directive, so begin by extracting the @Directive metadata and building
  8870. // on it.
  8871. const directiveResult = checker.extractDirectiveMetadata(node, decorator, this.reflector, this.importTracker, this.evaluator, this.refEmitter, this.referencesRegistry, this.isCore, this.annotateForClosureCompiler, this.compilationMode, this.elementSchemaRegistry.getDefaultComponentElementName(), this.strictStandalone, this.implicitStandaloneValue);
  8872. // `extractDirectiveMetadata` returns `jitForced = true` when the `@Component` has
  8873. // set `jit: true`. In this case, compilation of the decorator is skipped. Returning
  8874. // an empty object signifies that no analysis was produced.
  8875. if (directiveResult.jitForced) {
  8876. this.jitDeclarationRegistry.jitDeclarations.add(node);
  8877. return {};
  8878. }
  8879. // Next, read the `@Component`-specific fields.
  8880. const { decorator: component, metadata, inputs, outputs, hostDirectives, rawHostDirectives, } = directiveResult;
  8881. const encapsulation = (this.compilationMode !== checker.CompilationMode.LOCAL
  8882. ? resolveEnumValue(this.evaluator, component, 'encapsulation', 'ViewEncapsulation', this.isCore)
  8883. : resolveEncapsulationEnumValueLocally(component.get('encapsulation'))) ??
  8884. checker.ViewEncapsulation.Emulated;
  8885. let changeDetection = null;
  8886. if (this.compilationMode !== checker.CompilationMode.LOCAL) {
  8887. changeDetection = resolveEnumValue(this.evaluator, component, 'changeDetection', 'ChangeDetectionStrategy', this.isCore);
  8888. }
  8889. else if (component.has('changeDetection')) {
  8890. changeDetection = new checker.WrappedNodeExpr(component.get('changeDetection'));
  8891. }
  8892. let animations = null;
  8893. let animationTriggerNames = null;
  8894. if (component.has('animations')) {
  8895. const animationExpression = component.get('animations');
  8896. animations = new checker.WrappedNodeExpr(animationExpression);
  8897. const animationsValue = this.evaluator.evaluate(animationExpression, animationTriggerResolver);
  8898. animationTriggerNames = { includesDynamicAnimations: false, staticTriggerNames: [] };
  8899. collectAnimationNames(animationsValue, animationTriggerNames);
  8900. }
  8901. // Go through the root directories for this project, and select the one with the smallest
  8902. // relative path representation.
  8903. const relativeContextFilePath = this.rootDirs.reduce((previous, rootDir) => {
  8904. const candidate = checker.relative(checker.absoluteFrom(rootDir), checker.absoluteFrom(containingFile));
  8905. if (previous === undefined || candidate.length < previous.length) {
  8906. return candidate;
  8907. }
  8908. else {
  8909. return previous;
  8910. }
  8911. }, undefined);
  8912. // Note that we could technically combine the `viewProvidersRequiringFactory` and
  8913. // `providersRequiringFactory` into a single set, but we keep the separate so that
  8914. // we can distinguish where an error is coming from when logging the diagnostics in `resolve`.
  8915. let viewProvidersRequiringFactory = null;
  8916. let providersRequiringFactory = null;
  8917. let wrappedViewProviders = null;
  8918. if (component.has('viewProviders')) {
  8919. const viewProviders = component.get('viewProviders');
  8920. viewProvidersRequiringFactory = checker.resolveProvidersRequiringFactory(viewProviders, this.reflector, this.evaluator);
  8921. wrappedViewProviders = new checker.WrappedNodeExpr(this.annotateForClosureCompiler
  8922. ? checker.wrapFunctionExpressionsInParens(viewProviders)
  8923. : viewProviders);
  8924. }
  8925. if (component.has('providers')) {
  8926. providersRequiringFactory = checker.resolveProvidersRequiringFactory(component.get('providers'), this.reflector, this.evaluator);
  8927. }
  8928. let resolvedImports = null;
  8929. let resolvedDeferredImports = null;
  8930. let rawImports = component.get('imports') ?? null;
  8931. let rawDeferredImports = component.get('deferredImports') ?? null;
  8932. if ((rawImports || rawDeferredImports) && !metadata.isStandalone) {
  8933. if (diagnostics === undefined) {
  8934. diagnostics = [];
  8935. }
  8936. const importsField = rawImports ? 'imports' : 'deferredImports';
  8937. diagnostics.push(checker.makeDiagnostic(checker.ErrorCode.COMPONENT_NOT_STANDALONE, component.get(importsField), `'${importsField}' is only valid on a component that is standalone.`, [
  8938. checker.makeRelatedInformation(node.name, `Did you forget to add 'standalone: true' to this @Component?`),
  8939. ]));
  8940. // Poison the component so that we don't spam further template type-checking errors that
  8941. // result from misconfigured imports.
  8942. isPoisoned = true;
  8943. }
  8944. else if (this.compilationMode !== checker.CompilationMode.LOCAL &&
  8945. (rawImports || rawDeferredImports)) {
  8946. const importResolvers = checker.combineResolvers([
  8947. createModuleWithProvidersResolver(this.reflector, this.isCore),
  8948. checker.createForwardRefResolver(this.isCore),
  8949. ]);
  8950. const importDiagnostics = [];
  8951. if (rawImports) {
  8952. const expr = rawImports;
  8953. const imported = this.evaluator.evaluate(expr, importResolvers);
  8954. const { imports: flattened, diagnostics } = validateAndFlattenComponentImports(imported, expr, false /* isDeferred */);
  8955. importDiagnostics.push(...diagnostics);
  8956. resolvedImports = flattened;
  8957. rawImports = expr;
  8958. }
  8959. if (rawDeferredImports) {
  8960. const expr = rawDeferredImports;
  8961. const imported = this.evaluator.evaluate(expr, importResolvers);
  8962. const { imports: flattened, diagnostics } = validateAndFlattenComponentImports(imported, expr, true /* isDeferred */);
  8963. importDiagnostics.push(...diagnostics);
  8964. resolvedDeferredImports = flattened;
  8965. rawDeferredImports = expr;
  8966. }
  8967. if (importDiagnostics.length > 0) {
  8968. isPoisoned = true;
  8969. if (diagnostics === undefined) {
  8970. diagnostics = [];
  8971. }
  8972. diagnostics.push(...importDiagnostics);
  8973. }
  8974. }
  8975. let schemas = null;
  8976. if (component.has('schemas') && !metadata.isStandalone) {
  8977. if (diagnostics === undefined) {
  8978. diagnostics = [];
  8979. }
  8980. diagnostics.push(checker.makeDiagnostic(checker.ErrorCode.COMPONENT_NOT_STANDALONE, component.get('schemas'), `'schemas' is only valid on a component that is standalone.`));
  8981. }
  8982. else if (this.compilationMode !== checker.CompilationMode.LOCAL && component.has('schemas')) {
  8983. schemas = extractSchemas(component.get('schemas'), this.evaluator, 'Component');
  8984. }
  8985. else if (metadata.isStandalone) {
  8986. schemas = [];
  8987. }
  8988. // Parse the template.
  8989. // If a preanalyze phase was executed, the template may already exist in parsed form, so check
  8990. // the preanalyzeTemplateCache.
  8991. // Extract a closure of the template parsing code so that it can be reparsed with different
  8992. // options if needed, like in the indexing pipeline.
  8993. let template;
  8994. if (this.preanalyzeTemplateCache.has(node)) {
  8995. // The template was parsed in preanalyze. Use it and delete it to save memory.
  8996. const preanalyzed = this.preanalyzeTemplateCache.get(node);
  8997. this.preanalyzeTemplateCache.delete(node);
  8998. template = preanalyzed;
  8999. }
  9000. else {
  9001. const templateDecl = parseTemplateDeclaration(node, decorator, component, containingFile, this.evaluator, this.depTracker, this.resourceLoader, this.defaultPreserveWhitespaces);
  9002. template = extractTemplate(node, templateDecl, this.evaluator, this.depTracker, this.resourceLoader, {
  9003. enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
  9004. i18nNormalizeLineEndingsInICUs: this.i18nNormalizeLineEndingsInICUs,
  9005. usePoisonedData: this.usePoisonedData,
  9006. enableBlockSyntax: this.enableBlockSyntax,
  9007. enableLetSyntax: this.enableLetSyntax,
  9008. preserveSignificantWhitespace: this.i18nPreserveSignificantWhitespace,
  9009. }, this.compilationMode);
  9010. if (this.compilationMode === checker.CompilationMode.LOCAL &&
  9011. template.errors &&
  9012. template.errors.length > 0) {
  9013. // Template errors are handled at the type check phase. But we skip this phase in local
  9014. // compilation mode. As a result we need to handle the errors now and add them to the diagnostics.
  9015. if (diagnostics === undefined) {
  9016. diagnostics = [];
  9017. }
  9018. diagnostics.push(...checker.getTemplateDiagnostics(template.errors,
  9019. // Type check ID is required as part of the ype check, mainly for mapping the
  9020. // diagnostic back to its source. But here we are generating the diagnostic outside
  9021. // of the type check context, and so we skip the template ID.
  9022. '', template.sourceMapping));
  9023. }
  9024. }
  9025. const templateResource = template.declaration.isInline
  9026. ? { path: null, expression: component.get('template') }
  9027. : {
  9028. path: checker.absoluteFrom(template.declaration.resolvedTemplateUrl),
  9029. expression: template.sourceMapping.node,
  9030. };
  9031. const relativeTemplatePath = getProjectRelativePath(templateResource.path ?? ts.getOriginalNode(node).getSourceFile().fileName, this.rootDirs, this.compilerHost);
  9032. // Figure out the set of styles. The ordering here is important: external resources (styleUrls)
  9033. // precede inline styles, and styles defined in the template override styles defined in the
  9034. // component.
  9035. let styles = [];
  9036. const externalStyles = [];
  9037. const styleResources = extractInlineStyleResources(component);
  9038. const styleUrls = [
  9039. ...extractComponentStyleUrls(this.evaluator, component),
  9040. ..._extractTemplateStyleUrls(template),
  9041. ];
  9042. for (const styleUrl of styleUrls) {
  9043. try {
  9044. const resourceUrl = this.resourceLoader.resolve(styleUrl.url, containingFile);
  9045. if (this.externalRuntimeStyles) {
  9046. // External runtime styles are not considered disk-based and may not actually exist on disk
  9047. externalStyles.push(resourceUrl);
  9048. continue;
  9049. }
  9050. if (styleUrl.source === 2 /* ResourceTypeForDiagnostics.StylesheetFromDecorator */ &&
  9051. ts.isStringLiteralLike(styleUrl.expression)) {
  9052. // Only string literal values from the decorator are considered style resources
  9053. styleResources.add({
  9054. path: checker.absoluteFrom(resourceUrl),
  9055. expression: styleUrl.expression,
  9056. });
  9057. }
  9058. const resourceStr = this.resourceLoader.load(resourceUrl);
  9059. styles.push(resourceStr);
  9060. if (this.depTracker !== null) {
  9061. this.depTracker.addResourceDependency(node.getSourceFile(), checker.absoluteFrom(resourceUrl));
  9062. }
  9063. }
  9064. catch {
  9065. if (this.depTracker !== null) {
  9066. // The analysis of this file cannot be re-used if one of the style URLs could
  9067. // not be resolved or loaded. Future builds should re-analyze and re-attempt
  9068. // resolution/loading.
  9069. this.depTracker.recordDependencyAnalysisFailure(node.getSourceFile());
  9070. }
  9071. if (diagnostics === undefined) {
  9072. diagnostics = [];
  9073. }
  9074. const resourceType = styleUrl.source === 2 /* ResourceTypeForDiagnostics.StylesheetFromDecorator */
  9075. ? 2 /* ResourceTypeForDiagnostics.StylesheetFromDecorator */
  9076. : 1 /* ResourceTypeForDiagnostics.StylesheetFromTemplate */;
  9077. diagnostics.push(makeResourceNotFoundError(styleUrl.url, styleUrl.expression, resourceType).toDiagnostic());
  9078. }
  9079. }
  9080. if (encapsulation === checker.ViewEncapsulation.ShadowDom && metadata.selector !== null) {
  9081. const selectorError = checkCustomElementSelectorForErrors(metadata.selector);
  9082. if (selectorError !== null) {
  9083. if (diagnostics === undefined) {
  9084. diagnostics = [];
  9085. }
  9086. diagnostics.push(checker.makeDiagnostic(checker.ErrorCode.COMPONENT_INVALID_SHADOW_DOM_SELECTOR, component.get('selector'), selectorError));
  9087. }
  9088. }
  9089. // If inline styles were preprocessed use those
  9090. let inlineStyles = null;
  9091. if (this.preanalyzeStylesCache.has(node)) {
  9092. inlineStyles = this.preanalyzeStylesCache.get(node);
  9093. this.preanalyzeStylesCache.delete(node);
  9094. if (inlineStyles?.length) {
  9095. if (this.externalRuntimeStyles) {
  9096. // When external runtime styles is enabled, a list of URLs is provided
  9097. externalStyles.push(...inlineStyles);
  9098. }
  9099. else {
  9100. styles.push(...inlineStyles);
  9101. }
  9102. }
  9103. }
  9104. else {
  9105. // Preprocessing is only supported asynchronously
  9106. // If no style cache entry is present asynchronous preanalyze was not executed.
  9107. // This protects against accidental differences in resource contents when preanalysis
  9108. // is not used with a provided transformResource hook on the ResourceHost.
  9109. if (this.resourceLoader.canPreprocess) {
  9110. throw new Error('Inline resource processing requires asynchronous preanalyze.');
  9111. }
  9112. if (component.has('styles')) {
  9113. const litStyles = checker.parseDirectiveStyles(component, this.evaluator, this.compilationMode);
  9114. if (litStyles !== null) {
  9115. inlineStyles = [...litStyles];
  9116. styles.push(...litStyles);
  9117. }
  9118. }
  9119. if (template.styles.length > 0) {
  9120. styles.push(...template.styles);
  9121. }
  9122. }
  9123. // Collect all explicitly deferred symbols from the `@Component.deferredImports` field
  9124. // (if it exists) and populate the `DeferredSymbolTracker` state. These operations are safe
  9125. // for the local compilation mode, since they don't require accessing/resolving symbols
  9126. // outside of the current source file.
  9127. let explicitlyDeferredTypes = null;
  9128. if (metadata.isStandalone && rawDeferredImports !== null) {
  9129. const deferredTypes = this.collectExplicitlyDeferredSymbols(rawDeferredImports);
  9130. for (const [deferredType, importDetails] of deferredTypes) {
  9131. explicitlyDeferredTypes ??= [];
  9132. explicitlyDeferredTypes.push({
  9133. symbolName: importDetails.name,
  9134. importPath: importDetails.from,
  9135. isDefaultImport: isDefaultImport(importDetails.node),
  9136. });
  9137. this.deferredSymbolTracker.markAsDeferrableCandidate(deferredType, importDetails.node, node, true /* isExplicitlyDeferred */);
  9138. }
  9139. }
  9140. const output = {
  9141. analysis: {
  9142. baseClass: checker.readBaseClass(node, this.reflector, this.evaluator),
  9143. inputs,
  9144. inputFieldNamesFromMetadataArray: directiveResult.inputFieldNamesFromMetadataArray,
  9145. outputs,
  9146. hostDirectives,
  9147. rawHostDirectives,
  9148. meta: {
  9149. ...metadata,
  9150. template,
  9151. encapsulation,
  9152. changeDetection,
  9153. interpolation: template.interpolationConfig ?? checker.DEFAULT_INTERPOLATION_CONFIG,
  9154. styles,
  9155. externalStyles,
  9156. // These will be replaced during the compilation step, after all `NgModule`s have been
  9157. // analyzed and the full compilation scope for the component can be realized.
  9158. animations,
  9159. viewProviders: wrappedViewProviders,
  9160. i18nUseExternalIds: this.i18nUseExternalIds,
  9161. relativeContextFilePath,
  9162. rawImports: rawImports !== null ? new checker.WrappedNodeExpr(rawImports) : undefined,
  9163. relativeTemplatePath,
  9164. },
  9165. typeCheckMeta: checker.extractDirectiveTypeCheckMeta(node, inputs, this.reflector),
  9166. classMetadata: this.includeClassMetadata
  9167. ? extractClassMetadata(node, this.reflector, this.isCore, this.annotateForClosureCompiler, (dec) => transformDecoratorResources(dec, component, styles, template))
  9168. : null,
  9169. classDebugInfo: extractClassDebugInfo(node, this.reflector, this.compilerHost, this.rootDirs,
  9170. /* forbidOrphanRenderering */ this.forbidOrphanRendering),
  9171. template,
  9172. providersRequiringFactory,
  9173. viewProvidersRequiringFactory,
  9174. inlineStyles,
  9175. styleUrls,
  9176. resources: {
  9177. styles: styleResources,
  9178. template: templateResource,
  9179. },
  9180. isPoisoned,
  9181. animationTriggerNames,
  9182. rawImports,
  9183. resolvedImports,
  9184. rawDeferredImports,
  9185. resolvedDeferredImports,
  9186. explicitlyDeferredTypes,
  9187. schemas,
  9188. decorator: decorator?.node ?? null,
  9189. },
  9190. diagnostics,
  9191. };
  9192. return output;
  9193. }
  9194. symbol(node, analysis) {
  9195. const typeParameters = extractSemanticTypeParameters(node);
  9196. return new ComponentSymbol(node, analysis.meta.selector, analysis.inputs, analysis.outputs, analysis.meta.exportAs, analysis.typeCheckMeta, typeParameters);
  9197. }
  9198. register(node, analysis) {
  9199. // Register this component's information with the `MetadataRegistry`. This ensures that
  9200. // the information about the component is available during the compile() phase.
  9201. const ref = new checker.Reference(node);
  9202. this.metaRegistry.registerDirectiveMetadata({
  9203. kind: checker.MetaKind.Directive,
  9204. matchSource: checker.MatchSource.Selector,
  9205. ref,
  9206. name: node.name.text,
  9207. selector: analysis.meta.selector,
  9208. exportAs: analysis.meta.exportAs,
  9209. inputs: analysis.inputs,
  9210. inputFieldNamesFromMetadataArray: analysis.inputFieldNamesFromMetadataArray,
  9211. outputs: analysis.outputs,
  9212. queries: analysis.meta.queries.map((query) => query.propertyName),
  9213. isComponent: true,
  9214. baseClass: analysis.baseClass,
  9215. hostDirectives: analysis.hostDirectives,
  9216. ...analysis.typeCheckMeta,
  9217. isPoisoned: analysis.isPoisoned,
  9218. isStructural: false,
  9219. isStandalone: analysis.meta.isStandalone,
  9220. isSignal: analysis.meta.isSignal,
  9221. imports: analysis.resolvedImports,
  9222. rawImports: analysis.rawImports,
  9223. deferredImports: analysis.resolvedDeferredImports,
  9224. animationTriggerNames: analysis.animationTriggerNames,
  9225. schemas: analysis.schemas,
  9226. decorator: analysis.decorator,
  9227. assumedToExportProviders: false,
  9228. ngContentSelectors: analysis.template.ngContentSelectors,
  9229. preserveWhitespaces: analysis.template.preserveWhitespaces ?? false,
  9230. isExplicitlyDeferred: false,
  9231. });
  9232. this.resourceRegistry.registerResources(analysis.resources, node);
  9233. this.injectableRegistry.registerInjectable(node, {
  9234. ctorDeps: analysis.meta.deps,
  9235. });
  9236. }
  9237. index(context, node, analysis) {
  9238. if (analysis.isPoisoned && !this.usePoisonedData) {
  9239. return null;
  9240. }
  9241. const scope = this.scopeReader.getScopeForComponent(node);
  9242. const selector = analysis.meta.selector;
  9243. const matcher = new checker.SelectorMatcher();
  9244. if (scope !== null) {
  9245. let { dependencies, isPoisoned } = scope.kind === checker.ComponentScopeKind.NgModule ? scope.compilation : scope;
  9246. if ((isPoisoned || (scope.kind === checker.ComponentScopeKind.NgModule && scope.exported.isPoisoned)) &&
  9247. !this.usePoisonedData) {
  9248. // Don't bother indexing components which had erroneous scopes, unless specifically
  9249. // requested.
  9250. return null;
  9251. }
  9252. for (const dep of dependencies) {
  9253. if (dep.kind === checker.MetaKind.Directive && dep.selector !== null) {
  9254. matcher.addSelectables(checker.CssSelector.parse(dep.selector), [
  9255. ...this.hostDirectivesResolver.resolve(dep),
  9256. dep,
  9257. ]);
  9258. }
  9259. }
  9260. }
  9261. const binder = new checker.R3TargetBinder(matcher);
  9262. const boundTemplate = binder.bind({ template: analysis.template.diagNodes });
  9263. context.addComponent({
  9264. declaration: node,
  9265. selector,
  9266. boundTemplate,
  9267. templateMeta: {
  9268. isInline: analysis.template.declaration.isInline,
  9269. file: analysis.template.file,
  9270. },
  9271. });
  9272. return null;
  9273. }
  9274. typeCheck(ctx, node, meta) {
  9275. if (this.typeCheckScopeRegistry === null || !ts.isClassDeclaration(node)) {
  9276. return;
  9277. }
  9278. if (meta.isPoisoned && !this.usePoisonedData) {
  9279. return;
  9280. }
  9281. const scope = this.typeCheckScopeRegistry.getTypeCheckScope(node);
  9282. if (scope.isPoisoned && !this.usePoisonedData) {
  9283. // Don't type-check components that had errors in their scopes, unless requested.
  9284. return;
  9285. }
  9286. const binder = new checker.R3TargetBinder(scope.matcher);
  9287. const templateContext = {
  9288. nodes: meta.template.diagNodes,
  9289. pipes: scope.pipes,
  9290. sourceMapping: meta.template.sourceMapping,
  9291. file: meta.template.file,
  9292. parseErrors: meta.template.errors,
  9293. preserveWhitespaces: meta.meta.template.preserveWhitespaces ?? false,
  9294. };
  9295. ctx.addDirective(new checker.Reference(node), binder, scope.schemas, templateContext, meta.meta.isStandalone);
  9296. }
  9297. extendedTemplateCheck(component, extendedTemplateChecker) {
  9298. return extendedTemplateChecker.getDiagnosticsForComponent(component);
  9299. }
  9300. templateSemanticsCheck(component, templateSemanticsChecker) {
  9301. return templateSemanticsChecker.getDiagnosticsForComponent(component);
  9302. }
  9303. resolve(node, analysis, symbol) {
  9304. const metadata = analysis.meta;
  9305. const diagnostics = [];
  9306. const context = checker.getSourceFile(node);
  9307. // Check if there are some import declarations that contain symbols used within
  9308. // the `@Component.deferredImports` field, but those imports contain other symbols
  9309. // and thus the declaration can not be removed. This diagnostics is shared between local and
  9310. // global compilation modes.
  9311. const nonRemovableImports = this.deferredSymbolTracker.getNonRemovableDeferredImports(context, node);
  9312. if (nonRemovableImports.length > 0) {
  9313. for (const importDecl of nonRemovableImports) {
  9314. const diagnostic = checker.makeDiagnostic(checker.ErrorCode.DEFERRED_DEPENDENCY_IMPORTED_EAGERLY, importDecl, `This import contains symbols that are used both inside and outside of the ` +
  9315. `\`@Component.deferredImports\` fields in the file. This renders all these ` +
  9316. `defer imports useless as this import remains and its module is eagerly loaded. ` +
  9317. `To fix this, make sure that all symbols from the import are *only* used within ` +
  9318. `\`@Component.deferredImports\` arrays and there are no other references to those ` +
  9319. `symbols present in this file.`);
  9320. diagnostics.push(diagnostic);
  9321. }
  9322. return { diagnostics };
  9323. }
  9324. let data;
  9325. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  9326. // Initial value in local compilation mode.
  9327. data = {
  9328. declarations: EMPTY_ARRAY,
  9329. declarationListEmitMode: !analysis.meta.isStandalone || analysis.rawImports !== null
  9330. ? 3 /* DeclarationListEmitMode.RuntimeResolved */
  9331. : 0 /* DeclarationListEmitMode.Direct */,
  9332. deferPerBlockDependencies: this.locateDeferBlocksWithoutScope(analysis.template),
  9333. deferBlockDepsEmitMode: 1 /* DeferBlockDepsEmitMode.PerComponent */,
  9334. deferrableDeclToImportDecl: new Map(),
  9335. deferPerComponentDependencies: analysis.explicitlyDeferredTypes ?? [],
  9336. };
  9337. if (this.localCompilationExtraImportsTracker === null) {
  9338. // In local compilation mode the resolve phase is only needed for generating extra imports.
  9339. // Otherwise we can skip it.
  9340. return { data };
  9341. }
  9342. }
  9343. else {
  9344. // Initial value in global compilation mode.
  9345. data = {
  9346. declarations: EMPTY_ARRAY,
  9347. declarationListEmitMode: 0 /* DeclarationListEmitMode.Direct */,
  9348. deferPerBlockDependencies: new Map(),
  9349. deferBlockDepsEmitMode: 0 /* DeferBlockDepsEmitMode.PerBlock */,
  9350. deferrableDeclToImportDecl: new Map(),
  9351. deferPerComponentDependencies: [],
  9352. };
  9353. }
  9354. if (this.semanticDepGraphUpdater !== null && analysis.baseClass instanceof checker.Reference) {
  9355. symbol.baseClass = this.semanticDepGraphUpdater.getSymbol(analysis.baseClass.node);
  9356. }
  9357. if (analysis.isPoisoned && !this.usePoisonedData) {
  9358. return {};
  9359. }
  9360. const scope = this.scopeReader.getScopeForComponent(node);
  9361. if (scope !== null) {
  9362. // Replace the empty components and directives from the analyze() step with a fully expanded
  9363. // scope. This is possible now because during resolve() the whole compilation unit has been
  9364. // fully analyzed.
  9365. //
  9366. // First it needs to be determined if actually importing the directives/pipes used in the
  9367. // template would create a cycle. Currently ngtsc refuses to generate cycles, so an option
  9368. // known as "remote scoping" is used if a cycle would be created. In remote scoping, the
  9369. // module file sets the directives/pipes on the ɵcmp of the component, without
  9370. // requiring new imports (but also in a way that breaks tree shaking).
  9371. //
  9372. // Determining this is challenging, because the TemplateDefinitionBuilder is responsible for
  9373. // matching directives and pipes in the template; however, that doesn't run until the actual
  9374. // compile() step. It's not possible to run template compilation sooner as it requires the
  9375. // ConstantPool for the overall file being compiled (which isn't available until the
  9376. // transform step).
  9377. //
  9378. // Instead, directives/pipes are matched independently here, using the R3TargetBinder. This
  9379. // is an alternative implementation of template matching which is used for template
  9380. // type-checking and will eventually replace matching in the TemplateDefinitionBuilder.
  9381. const isModuleScope = scope.kind === checker.ComponentScopeKind.NgModule;
  9382. // Dependencies coming from the regular `imports` field.
  9383. const dependencies = isModuleScope ? scope.compilation.dependencies : scope.dependencies;
  9384. // Dependencies from the `@Component.deferredImports` field.
  9385. const explicitlyDeferredDependencies = getExplicitlyDeferredDeps(scope);
  9386. // Mark the component is an NgModule-based component with its NgModule in a different file
  9387. // then mark this file for extra import generation
  9388. if (isModuleScope && context.fileName !== checker.getSourceFile(scope.ngModule).fileName) {
  9389. this.localCompilationExtraImportsTracker?.markFileForExtraImportGeneration(context);
  9390. }
  9391. // Make sure that `@Component.imports` and `@Component.deferredImports` do not have
  9392. // the same dependencies.
  9393. if (metadata.isStandalone &&
  9394. analysis.rawDeferredImports !== null &&
  9395. explicitlyDeferredDependencies.length > 0) {
  9396. const diagnostic = validateNoImportOverlap(dependencies, explicitlyDeferredDependencies, analysis.rawDeferredImports);
  9397. if (diagnostic !== null) {
  9398. diagnostics.push(diagnostic);
  9399. }
  9400. }
  9401. // Set up the R3TargetBinder.
  9402. const binder = createTargetBinder(dependencies);
  9403. let allDependencies = dependencies;
  9404. let deferBlockBinder = binder;
  9405. // If there are any explicitly deferred dependencies (via `@Component.deferredImports`),
  9406. // re-compute the list of dependencies and create a new binder for defer blocks. This
  9407. // is because we have deferred dependencies that are not in the standard imports list
  9408. // and need to be referenced later when determining what dependencies need to be in a
  9409. // defer function / instruction call. Otherwise they end up treated as a standard
  9410. // import, which is wrong.
  9411. if (explicitlyDeferredDependencies.length > 0) {
  9412. allDependencies = [...explicitlyDeferredDependencies, ...dependencies];
  9413. deferBlockBinder = createTargetBinder(allDependencies);
  9414. }
  9415. // Set up the pipes map that is later used to determine which dependencies are used in
  9416. // the template.
  9417. const pipes = extractPipes(allDependencies);
  9418. // Next, the component template AST is bound using the R3TargetBinder. This produces a
  9419. // BoundTarget, which is similar to a ts.TypeChecker.
  9420. const bound = binder.bind({ template: metadata.template.nodes });
  9421. // Find all defer blocks used in the template and for each block
  9422. // bind its own scope.
  9423. const deferBlocks = new Map();
  9424. for (const deferBlock of bound.getDeferBlocks()) {
  9425. deferBlocks.set(deferBlock, deferBlockBinder.bind({ template: deferBlock.children }));
  9426. }
  9427. // Register all Directives and Pipes used at the top level (outside
  9428. // of any defer blocks), which would be eagerly referenced.
  9429. const eagerlyUsed = new Set();
  9430. if (this.enableHmr) {
  9431. // In HMR we need to preserve all the dependencies, because they have to remain consistent
  9432. // with the initially-generated code no matter what the template looks like.
  9433. for (const dep of dependencies) {
  9434. if (dep.ref.node !== node) {
  9435. eagerlyUsed.add(dep.ref.node);
  9436. }
  9437. else {
  9438. const used = bound.getEagerlyUsedDirectives();
  9439. if (used.some((current) => current.ref.node === node)) {
  9440. eagerlyUsed.add(node);
  9441. }
  9442. }
  9443. }
  9444. }
  9445. else {
  9446. for (const dir of bound.getEagerlyUsedDirectives()) {
  9447. eagerlyUsed.add(dir.ref.node);
  9448. }
  9449. for (const name of bound.getEagerlyUsedPipes()) {
  9450. if (!pipes.has(name)) {
  9451. continue;
  9452. }
  9453. eagerlyUsed.add(pipes.get(name).ref.node);
  9454. }
  9455. }
  9456. // Set of Directives and Pipes used across the entire template,
  9457. // including all defer blocks.
  9458. const wholeTemplateUsed = new Set(eagerlyUsed);
  9459. for (const bound of deferBlocks.values()) {
  9460. for (const dir of bound.getUsedDirectives()) {
  9461. wholeTemplateUsed.add(dir.ref.node);
  9462. }
  9463. for (const name of bound.getUsedPipes()) {
  9464. if (!pipes.has(name)) {
  9465. continue;
  9466. }
  9467. wholeTemplateUsed.add(pipes.get(name).ref.node);
  9468. }
  9469. }
  9470. const declarations = new Map();
  9471. // Transform the dependencies list, filtering out unused dependencies.
  9472. for (const dep of allDependencies) {
  9473. // Only emit references to each dependency once.
  9474. if (declarations.has(dep.ref.node)) {
  9475. continue;
  9476. }
  9477. switch (dep.kind) {
  9478. case checker.MetaKind.Directive:
  9479. if (!wholeTemplateUsed.has(dep.ref.node) || dep.matchSource !== checker.MatchSource.Selector) {
  9480. continue;
  9481. }
  9482. const dirType = this.refEmitter.emit(dep.ref, context);
  9483. checker.assertSuccessfulReferenceEmit(dirType, node.name, dep.isComponent ? 'component' : 'directive');
  9484. declarations.set(dep.ref.node, {
  9485. kind: checker.R3TemplateDependencyKind.Directive,
  9486. ref: dep.ref,
  9487. type: dirType.expression,
  9488. importedFile: dirType.importedFile,
  9489. selector: dep.selector,
  9490. inputs: dep.inputs.propertyNames,
  9491. outputs: dep.outputs.propertyNames,
  9492. exportAs: dep.exportAs,
  9493. isComponent: dep.isComponent,
  9494. });
  9495. break;
  9496. case checker.MetaKind.Pipe:
  9497. if (!wholeTemplateUsed.has(dep.ref.node)) {
  9498. continue;
  9499. }
  9500. const pipeType = this.refEmitter.emit(dep.ref, context);
  9501. checker.assertSuccessfulReferenceEmit(pipeType, node.name, 'pipe');
  9502. declarations.set(dep.ref.node, {
  9503. kind: checker.R3TemplateDependencyKind.Pipe,
  9504. type: pipeType.expression,
  9505. name: dep.name,
  9506. ref: dep.ref,
  9507. importedFile: pipeType.importedFile,
  9508. });
  9509. break;
  9510. case checker.MetaKind.NgModule:
  9511. const ngModuleType = this.refEmitter.emit(dep.ref, context);
  9512. checker.assertSuccessfulReferenceEmit(ngModuleType, node.name, 'NgModule');
  9513. declarations.set(dep.ref.node, {
  9514. kind: checker.R3TemplateDependencyKind.NgModule,
  9515. type: ngModuleType.expression,
  9516. importedFile: ngModuleType.importedFile,
  9517. });
  9518. break;
  9519. }
  9520. }
  9521. const getSemanticReference = (decl) => this.semanticDepGraphUpdater.getSemanticReference(decl.ref.node, decl.type);
  9522. if (this.semanticDepGraphUpdater !== null) {
  9523. symbol.usedDirectives = Array.from(declarations.values())
  9524. .filter(isUsedDirective)
  9525. .map(getSemanticReference);
  9526. symbol.usedPipes = Array.from(declarations.values())
  9527. .filter(isUsedPipe)
  9528. .map(getSemanticReference);
  9529. }
  9530. const eagerDeclarations = Array.from(declarations.values()).filter((decl) => decl.kind === checker.R3TemplateDependencyKind.NgModule || eagerlyUsed.has(decl.ref.node));
  9531. // Process information related to defer blocks
  9532. if (this.compilationMode !== checker.CompilationMode.LOCAL) {
  9533. this.resolveDeferBlocks(node, deferBlocks, declarations, data, analysis, eagerlyUsed);
  9534. }
  9535. const cyclesFromDirectives = new Map();
  9536. const cyclesFromPipes = new Map();
  9537. // Scan through the directives/pipes actually used in the template and check whether any
  9538. // import which needs to be generated would create a cycle. This check is skipped for
  9539. // standalone components as the dependencies of a standalone component have already been
  9540. // imported directly by the user, so Angular won't introduce any imports that aren't already
  9541. // in the user's program.
  9542. if (!metadata.isStandalone) {
  9543. for (const usedDep of eagerDeclarations) {
  9544. const cycle = this._checkForCyclicImport(usedDep.importedFile, usedDep.type, context);
  9545. if (cycle !== null) {
  9546. switch (usedDep.kind) {
  9547. case checker.R3TemplateDependencyKind.Directive:
  9548. cyclesFromDirectives.set(usedDep, cycle);
  9549. break;
  9550. case checker.R3TemplateDependencyKind.Pipe:
  9551. cyclesFromPipes.set(usedDep, cycle);
  9552. break;
  9553. }
  9554. }
  9555. }
  9556. }
  9557. // Check whether any usages of standalone components in imports requires the dependencies
  9558. // array to be wrapped in a closure. This check is technically a heuristic as there's no
  9559. // direct way to check whether a `Reference` came from a `forwardRef`. Instead, we check if
  9560. // the reference is `synthetic`, implying it came from _any_ foreign function resolver,
  9561. // including the `forwardRef` resolver.
  9562. const standaloneImportMayBeForwardDeclared = analysis.resolvedImports !== null && analysis.resolvedImports.some((ref) => ref.synthetic);
  9563. const cycleDetected = cyclesFromDirectives.size !== 0 || cyclesFromPipes.size !== 0;
  9564. if (!cycleDetected) {
  9565. // No cycle was detected. Record the imports that need to be created in the cycle detector
  9566. // so that future cyclic import checks consider their production.
  9567. for (const { type, importedFile } of eagerDeclarations) {
  9568. this.maybeRecordSyntheticImport(importedFile, type, context);
  9569. }
  9570. // Check whether the dependencies arrays in ɵcmp need to be wrapped in a closure.
  9571. // This is required if any dependency reference is to a declaration in the same file
  9572. // but declared after this component.
  9573. const declarationIsForwardDeclared = eagerDeclarations.some((decl) => checker.isExpressionForwardReference(decl.type, node.name, context));
  9574. if (this.compilationMode !== checker.CompilationMode.LOCAL &&
  9575. (declarationIsForwardDeclared || standaloneImportMayBeForwardDeclared)) {
  9576. data.declarationListEmitMode = 1 /* DeclarationListEmitMode.Closure */;
  9577. }
  9578. data.declarations = eagerDeclarations;
  9579. // Register extra local imports.
  9580. if (this.compilationMode === checker.CompilationMode.LOCAL &&
  9581. this.localCompilationExtraImportsTracker !== null) {
  9582. // In global compilation mode `eagerDeclarations` contains "all" the component
  9583. // dependencies, whose import statements will be added to the file. In local compilation
  9584. // mode `eagerDeclarations` only includes the "local" dependencies, meaning those that are
  9585. // declared inside this compilation unit.Here the import info of these local dependencies
  9586. // are added to the tracker so that we can generate extra imports representing these local
  9587. // dependencies. For non-local dependencies we use another technique of adding some
  9588. // best-guess extra imports globally to all files using
  9589. // `localCompilationExtraImportsTracker.addGlobalImportFromIdentifier`.
  9590. for (const { type } of eagerDeclarations) {
  9591. if (type instanceof checker.ExternalExpr && type.value.moduleName) {
  9592. this.localCompilationExtraImportsTracker.addImportForFile(context, type.value.moduleName);
  9593. }
  9594. }
  9595. }
  9596. }
  9597. else {
  9598. if (this.cycleHandlingStrategy === 0 /* CycleHandlingStrategy.UseRemoteScoping */) {
  9599. // Declaring the directiveDefs/pipeDefs arrays directly would require imports that would
  9600. // create a cycle. Instead, mark this component as requiring remote scoping, so that the
  9601. // NgModule file will take care of setting the directives for the component.
  9602. this.scopeRegistry.setComponentRemoteScope(node, eagerDeclarations.filter(isUsedDirective).map((dir) => dir.ref), eagerDeclarations.filter(isUsedPipe).map((pipe) => pipe.ref));
  9603. symbol.isRemotelyScoped = true;
  9604. // If a semantic graph is being tracked, record the fact that this component is remotely
  9605. // scoped with the declaring NgModule symbol as the NgModule's emit becomes dependent on
  9606. // the directive/pipe usages of this component.
  9607. if (this.semanticDepGraphUpdater !== null &&
  9608. scope.kind === checker.ComponentScopeKind.NgModule &&
  9609. scope.ngModule !== null) {
  9610. const moduleSymbol = this.semanticDepGraphUpdater.getSymbol(scope.ngModule);
  9611. if (!(moduleSymbol instanceof NgModuleSymbol)) {
  9612. throw new Error(`AssertionError: Expected ${scope.ngModule.name} to be an NgModuleSymbol.`);
  9613. }
  9614. moduleSymbol.addRemotelyScopedComponent(symbol, symbol.usedDirectives, symbol.usedPipes);
  9615. }
  9616. }
  9617. else {
  9618. // We are not able to handle this cycle so throw an error.
  9619. const relatedMessages = [];
  9620. for (const [dir, cycle] of cyclesFromDirectives) {
  9621. relatedMessages.push(makeCyclicImportInfo(dir.ref, dir.isComponent ? 'component' : 'directive', cycle));
  9622. }
  9623. for (const [pipe, cycle] of cyclesFromPipes) {
  9624. relatedMessages.push(makeCyclicImportInfo(pipe.ref, 'pipe', cycle));
  9625. }
  9626. throw new checker.FatalDiagnosticError(checker.ErrorCode.IMPORT_CYCLE_DETECTED, node, 'One or more import cycles would need to be created to compile this component, ' +
  9627. 'which is not supported by the current compiler configuration.', relatedMessages);
  9628. }
  9629. }
  9630. }
  9631. else {
  9632. // If there is no scope, we can still use the binder to retrieve *some* information about the
  9633. // deferred blocks.
  9634. data.deferPerBlockDependencies = this.locateDeferBlocksWithoutScope(metadata.template);
  9635. }
  9636. // Run diagnostics only in global mode.
  9637. if (this.compilationMode !== checker.CompilationMode.LOCAL) {
  9638. // Validate `@Component.imports` and `@Component.deferredImports` fields.
  9639. if (analysis.resolvedImports !== null && analysis.rawImports !== null) {
  9640. const importDiagnostics = validateStandaloneImports(analysis.resolvedImports, analysis.rawImports, this.metaReader, this.scopeReader, false /* isDeferredImport */);
  9641. diagnostics.push(...importDiagnostics);
  9642. }
  9643. if (analysis.resolvedDeferredImports !== null && analysis.rawDeferredImports !== null) {
  9644. const importDiagnostics = validateStandaloneImports(analysis.resolvedDeferredImports, analysis.rawDeferredImports, this.metaReader, this.scopeReader, true /* isDeferredImport */);
  9645. diagnostics.push(...importDiagnostics);
  9646. }
  9647. if (analysis.providersRequiringFactory !== null &&
  9648. analysis.meta.providers instanceof checker.WrappedNodeExpr) {
  9649. const providerDiagnostics = checker.getProviderDiagnostics(analysis.providersRequiringFactory, analysis.meta.providers.node, this.injectableRegistry);
  9650. diagnostics.push(...providerDiagnostics);
  9651. }
  9652. if (analysis.viewProvidersRequiringFactory !== null &&
  9653. analysis.meta.viewProviders instanceof checker.WrappedNodeExpr) {
  9654. const viewProviderDiagnostics = checker.getProviderDiagnostics(analysis.viewProvidersRequiringFactory, analysis.meta.viewProviders.node, this.injectableRegistry);
  9655. diagnostics.push(...viewProviderDiagnostics);
  9656. }
  9657. const directiveDiagnostics = checker.getDirectiveDiagnostics(node, this.injectableRegistry, this.evaluator, this.reflector, this.scopeRegistry, this.strictCtorDeps, 'Component');
  9658. if (directiveDiagnostics !== null) {
  9659. diagnostics.push(...directiveDiagnostics);
  9660. }
  9661. const hostDirectivesDiagnostics = analysis.hostDirectives && analysis.rawHostDirectives
  9662. ? checker.validateHostDirectives(analysis.rawHostDirectives, analysis.hostDirectives, this.metaReader)
  9663. : null;
  9664. if (hostDirectivesDiagnostics !== null) {
  9665. diagnostics.push(...hostDirectivesDiagnostics);
  9666. }
  9667. }
  9668. if (diagnostics.length > 0) {
  9669. return { diagnostics };
  9670. }
  9671. return { data };
  9672. }
  9673. xi18n(ctx, node, analysis) {
  9674. ctx.updateFromTemplate(analysis.template.content, analysis.template.declaration.resolvedTemplateUrl, analysis.template.interpolationConfig ?? checker.DEFAULT_INTERPOLATION_CONFIG);
  9675. }
  9676. updateResources(node, analysis) {
  9677. const containingFile = node.getSourceFile().fileName;
  9678. // If the template is external, re-parse it.
  9679. const templateDecl = analysis.template.declaration;
  9680. if (!templateDecl.isInline) {
  9681. analysis.template = extractTemplate(node, templateDecl, this.evaluator, this.depTracker, this.resourceLoader, this.extractTemplateOptions, this.compilationMode);
  9682. }
  9683. // Update any external stylesheets and rebuild the combined 'styles' list.
  9684. // TODO(alxhub): write tests for styles when the primary compiler uses the updateResources
  9685. // path
  9686. let styles = [];
  9687. if (analysis.styleUrls !== null) {
  9688. for (const styleUrl of analysis.styleUrls) {
  9689. try {
  9690. const resolvedStyleUrl = this.resourceLoader.resolve(styleUrl.url, containingFile);
  9691. const styleText = this.resourceLoader.load(resolvedStyleUrl);
  9692. styles.push(styleText);
  9693. }
  9694. catch (e) {
  9695. // Resource resolve failures should already be in the diagnostics list from the analyze
  9696. // stage. We do not need to do anything with them when updating resources.
  9697. }
  9698. }
  9699. }
  9700. if (analysis.inlineStyles !== null) {
  9701. for (const styleText of analysis.inlineStyles) {
  9702. styles.push(styleText);
  9703. }
  9704. }
  9705. for (const styleText of analysis.template.styles) {
  9706. styles.push(styleText);
  9707. }
  9708. analysis.meta.styles = styles.filter((s) => s.trim().length > 0);
  9709. }
  9710. compileFull(node, analysis, resolution, pool) {
  9711. if (analysis.template.errors !== null && analysis.template.errors.length > 0) {
  9712. return [];
  9713. }
  9714. const perComponentDeferredDeps = this.canDeferDeps
  9715. ? this.resolveAllDeferredDependencies(resolution)
  9716. : null;
  9717. const defer = this.compileDeferBlocks(resolution);
  9718. const meta = {
  9719. ...analysis.meta,
  9720. ...resolution,
  9721. defer,
  9722. };
  9723. const fac = compileNgFactoryDefField(checker.toFactoryMetadata(meta, checker.FactoryTarget.Component));
  9724. if (perComponentDeferredDeps !== null) {
  9725. removeDeferrableTypesFromComponentDecorator(analysis, perComponentDeferredDeps);
  9726. }
  9727. const def = checker.compileComponentFromMetadata(meta, pool, checker.makeBindingParser());
  9728. const inputTransformFields = compileInputTransformFields(analysis.inputs);
  9729. const classMetadata = analysis.classMetadata !== null
  9730. ? compileComponentClassMetadata(analysis.classMetadata, perComponentDeferredDeps).toStmt()
  9731. : null;
  9732. const debugInfo = analysis.classDebugInfo !== null
  9733. ? compileClassDebugInfo(analysis.classDebugInfo).toStmt()
  9734. : null;
  9735. const hmrMeta = this.enableHmr
  9736. ? extractHmrMetatadata(node, this.reflector, this.evaluator, this.compilerHost, this.rootDirs, def, fac, defer, classMetadata, debugInfo)
  9737. : null;
  9738. const hmrInitializer = hmrMeta ? compileHmrInitializer(hmrMeta).toStmt() : null;
  9739. const deferrableImports = this.canDeferDeps
  9740. ? this.deferredSymbolTracker.getDeferrableImportDecls()
  9741. : null;
  9742. return checker.compileResults(fac, def, classMetadata, 'ɵcmp', inputTransformFields, deferrableImports, debugInfo, hmrInitializer);
  9743. }
  9744. compilePartial(node, analysis, resolution) {
  9745. if (analysis.template.errors !== null && analysis.template.errors.length > 0) {
  9746. return [];
  9747. }
  9748. const templateInfo = {
  9749. content: analysis.template.content,
  9750. sourceUrl: analysis.template.declaration.resolvedTemplateUrl,
  9751. isInline: analysis.template.declaration.isInline,
  9752. inlineTemplateLiteralExpression: analysis.template.sourceMapping.type === 'direct'
  9753. ? new checker.WrappedNodeExpr(analysis.template.sourceMapping.node)
  9754. : null,
  9755. };
  9756. const perComponentDeferredDeps = this.canDeferDeps
  9757. ? this.resolveAllDeferredDependencies(resolution)
  9758. : null;
  9759. const defer = this.compileDeferBlocks(resolution);
  9760. const meta = {
  9761. ...analysis.meta,
  9762. ...resolution,
  9763. defer,
  9764. };
  9765. const fac = compileDeclareFactory(checker.toFactoryMetadata(meta, checker.FactoryTarget.Component));
  9766. const inputTransformFields = compileInputTransformFields(analysis.inputs);
  9767. const def = compileDeclareComponentFromMetadata(meta, analysis.template, templateInfo);
  9768. const classMetadata = analysis.classMetadata !== null
  9769. ? compileComponentDeclareClassMetadata(analysis.classMetadata, perComponentDeferredDeps).toStmt()
  9770. : null;
  9771. const hmrMeta = this.enableHmr
  9772. ? extractHmrMetatadata(node, this.reflector, this.evaluator, this.compilerHost, this.rootDirs, def, fac, defer, classMetadata, null)
  9773. : null;
  9774. const hmrInitializer = hmrMeta ? compileHmrInitializer(hmrMeta).toStmt() : null;
  9775. const deferrableImports = this.canDeferDeps
  9776. ? this.deferredSymbolTracker.getDeferrableImportDecls()
  9777. : null;
  9778. return checker.compileResults(fac, def, classMetadata, 'ɵcmp', inputTransformFields, deferrableImports, null, hmrInitializer);
  9779. }
  9780. compileLocal(node, analysis, resolution, pool) {
  9781. // In the local compilation mode we can only rely on the information available
  9782. // within the `@Component.deferredImports` array, because in this mode compiler
  9783. // doesn't have information on which dependencies belong to which defer blocks.
  9784. const deferrableTypes = this.canDeferDeps ? analysis.explicitlyDeferredTypes : null;
  9785. const defer = this.compileDeferBlocks(resolution);
  9786. const meta = {
  9787. ...analysis.meta,
  9788. ...resolution,
  9789. defer,
  9790. };
  9791. if (deferrableTypes !== null) {
  9792. removeDeferrableTypesFromComponentDecorator(analysis, deferrableTypes);
  9793. }
  9794. const fac = compileNgFactoryDefField(checker.toFactoryMetadata(meta, checker.FactoryTarget.Component));
  9795. const def = checker.compileComponentFromMetadata(meta, pool, checker.makeBindingParser());
  9796. const inputTransformFields = compileInputTransformFields(analysis.inputs);
  9797. const classMetadata = analysis.classMetadata !== null
  9798. ? compileComponentClassMetadata(analysis.classMetadata, deferrableTypes).toStmt()
  9799. : null;
  9800. const debugInfo = analysis.classDebugInfo !== null
  9801. ? compileClassDebugInfo(analysis.classDebugInfo).toStmt()
  9802. : null;
  9803. const hmrMeta = this.enableHmr
  9804. ? extractHmrMetatadata(node, this.reflector, this.evaluator, this.compilerHost, this.rootDirs, def, fac, defer, classMetadata, debugInfo)
  9805. : null;
  9806. const hmrInitializer = hmrMeta ? compileHmrInitializer(hmrMeta).toStmt() : null;
  9807. const deferrableImports = this.canDeferDeps
  9808. ? this.deferredSymbolTracker.getDeferrableImportDecls()
  9809. : null;
  9810. return checker.compileResults(fac, def, classMetadata, 'ɵcmp', inputTransformFields, deferrableImports, debugInfo, hmrInitializer);
  9811. }
  9812. compileHmrUpdateDeclaration(node, analysis, resolution) {
  9813. if (analysis.template.errors !== null && analysis.template.errors.length > 0) {
  9814. return null;
  9815. }
  9816. // Create a brand-new constant pool since there shouldn't be any constant sharing.
  9817. const pool = new checker.ConstantPool();
  9818. const defer = this.compileDeferBlocks(resolution);
  9819. const meta = {
  9820. ...analysis.meta,
  9821. ...resolution,
  9822. defer,
  9823. };
  9824. const fac = compileNgFactoryDefField(checker.toFactoryMetadata(meta, checker.FactoryTarget.Component));
  9825. const def = checker.compileComponentFromMetadata(meta, pool, checker.makeBindingParser());
  9826. const classMetadata = analysis.classMetadata !== null
  9827. ? compileComponentClassMetadata(analysis.classMetadata, null).toStmt()
  9828. : null;
  9829. const debugInfo = analysis.classDebugInfo !== null
  9830. ? compileClassDebugInfo(analysis.classDebugInfo).toStmt()
  9831. : null;
  9832. const hmrMeta = this.enableHmr
  9833. ? extractHmrMetatadata(node, this.reflector, this.evaluator, this.compilerHost, this.rootDirs, def, fac, defer, classMetadata, debugInfo)
  9834. : null;
  9835. const res = checker.compileResults(fac, def, classMetadata, 'ɵcmp', null, null, debugInfo, null);
  9836. return hmrMeta === null || res.length === 0
  9837. ? null
  9838. : getHmrUpdateDeclaration(res, pool.statements, hmrMeta, node);
  9839. }
  9840. /**
  9841. * Locates defer blocks in case scope information is not available.
  9842. * For example, this happens in the local compilation mode.
  9843. */
  9844. locateDeferBlocksWithoutScope(template) {
  9845. const deferBlocks = new Map();
  9846. const directivelessBinder = new checker.R3TargetBinder(new checker.SelectorMatcher());
  9847. const bound = directivelessBinder.bind({ template: template.nodes });
  9848. const deferredBlocks = bound.getDeferBlocks();
  9849. for (const block of deferredBlocks) {
  9850. // We can't determine the dependencies without a scope so we leave them empty.
  9851. deferBlocks.set(block, []);
  9852. }
  9853. return deferBlocks;
  9854. }
  9855. /**
  9856. * Computes a list of deferrable symbols based on dependencies from
  9857. * the `@Component.imports` field and their usage in `@defer` blocks.
  9858. */
  9859. resolveAllDeferredDependencies(resolution) {
  9860. const seenDeps = new Set();
  9861. const deferrableTypes = [];
  9862. // Go over all dependencies of all defer blocks and update the value of
  9863. // the `isDeferrable` flag and the `importPath` to reflect the current
  9864. // state after visiting all components during the `resolve` phase.
  9865. for (const [_, deps] of resolution.deferPerBlockDependencies) {
  9866. for (const deferBlockDep of deps) {
  9867. const node = deferBlockDep.declaration.node;
  9868. const importDecl = resolution.deferrableDeclToImportDecl.get(node) ?? null;
  9869. if (importDecl !== null && this.deferredSymbolTracker.canDefer(importDecl)) {
  9870. deferBlockDep.isDeferrable = true;
  9871. deferBlockDep.importPath = importDecl.moduleSpecifier.text;
  9872. deferBlockDep.isDefaultImport = isDefaultImport(importDecl);
  9873. // The same dependency may be used across multiple deferred blocks. De-duplicate it
  9874. // because it can throw off other logic further down the compilation pipeline.
  9875. // Note that the logic above needs to run even if the dependency is seen before,
  9876. // because the object literals are different between each block.
  9877. if (!seenDeps.has(node)) {
  9878. seenDeps.add(node);
  9879. deferrableTypes.push(deferBlockDep);
  9880. }
  9881. }
  9882. }
  9883. }
  9884. return deferrableTypes;
  9885. }
  9886. /**
  9887. * Collects deferrable symbols from the `@Component.deferredImports` field.
  9888. */
  9889. collectExplicitlyDeferredSymbols(rawDeferredImports) {
  9890. const deferredTypes = new Map();
  9891. if (!ts.isArrayLiteralExpression(rawDeferredImports)) {
  9892. return deferredTypes;
  9893. }
  9894. for (const element of rawDeferredImports.elements) {
  9895. const node = checker.tryUnwrapForwardRef(element, this.reflector) || element;
  9896. if (!ts.isIdentifier(node)) {
  9897. // Can't defer-load non-literal references.
  9898. continue;
  9899. }
  9900. const imp = this.reflector.getImportOfIdentifier(node);
  9901. if (imp !== null) {
  9902. deferredTypes.set(node, imp);
  9903. }
  9904. }
  9905. return deferredTypes;
  9906. }
  9907. /**
  9908. * Check whether adding an import from `origin` to the source-file corresponding to `expr` would
  9909. * create a cyclic import.
  9910. *
  9911. * @returns a `Cycle` object if a cycle would be created, otherwise `null`.
  9912. */
  9913. _checkForCyclicImport(importedFile, expr, origin) {
  9914. const imported = checker.resolveImportedFile(this.moduleResolver, importedFile, expr, origin);
  9915. if (imported === null) {
  9916. return null;
  9917. }
  9918. // Check whether the import is legal.
  9919. return this.cycleAnalyzer.wouldCreateCycle(origin, imported);
  9920. }
  9921. maybeRecordSyntheticImport(importedFile, expr, origin) {
  9922. const imported = checker.resolveImportedFile(this.moduleResolver, importedFile, expr, origin);
  9923. if (imported === null) {
  9924. return;
  9925. }
  9926. this.cycleAnalyzer.recordSyntheticImport(origin, imported);
  9927. }
  9928. /**
  9929. * Resolves information about defer blocks dependencies to make it
  9930. * available for the final `compile` step.
  9931. */
  9932. resolveDeferBlocks(componentClassDecl, deferBlocks, deferrableDecls, resolutionData, analysisData, eagerlyUsedDecls) {
  9933. // Collect all deferred decls from all defer blocks from the entire template
  9934. // to intersect with the information from the `imports` field of a particular
  9935. // Component.
  9936. const allDeferredDecls = new Set();
  9937. for (const [deferBlock, bound] of deferBlocks) {
  9938. const usedDirectives = new Set(bound.getEagerlyUsedDirectives().map((d) => d.ref.node));
  9939. const usedPipes = new Set(bound.getEagerlyUsedPipes());
  9940. let deps;
  9941. if (resolutionData.deferPerBlockDependencies.has(deferBlock)) {
  9942. deps = resolutionData.deferPerBlockDependencies.get(deferBlock);
  9943. }
  9944. else {
  9945. deps = [];
  9946. resolutionData.deferPerBlockDependencies.set(deferBlock, deps);
  9947. }
  9948. for (const decl of Array.from(deferrableDecls.values())) {
  9949. if (decl.kind === checker.R3TemplateDependencyKind.NgModule) {
  9950. continue;
  9951. }
  9952. if (decl.kind === checker.R3TemplateDependencyKind.Directive &&
  9953. !usedDirectives.has(decl.ref.node)) {
  9954. continue;
  9955. }
  9956. if (decl.kind === checker.R3TemplateDependencyKind.Pipe && !usedPipes.has(decl.name)) {
  9957. continue;
  9958. }
  9959. // Collect initial information about this dependency.
  9960. // `isDeferrable`, `importPath` and `isDefaultImport` will be
  9961. // added later during the `compile` step.
  9962. deps.push({
  9963. typeReference: decl.type,
  9964. symbolName: decl.ref.node.name.text,
  9965. isDeferrable: false,
  9966. importPath: null,
  9967. isDefaultImport: false,
  9968. declaration: decl.ref,
  9969. });
  9970. allDeferredDecls.add(decl.ref.node);
  9971. }
  9972. }
  9973. // For standalone components with the `imports` and `deferredImports` fields -
  9974. // inspect the list of referenced symbols and mark the ones used in defer blocks
  9975. // as potential candidates for defer loading.
  9976. if (analysisData.meta.isStandalone) {
  9977. if (analysisData.rawImports !== null) {
  9978. this.registerDeferrableCandidates(componentClassDecl, analysisData.rawImports, false /* isDeferredImport */, allDeferredDecls, eagerlyUsedDecls, resolutionData);
  9979. }
  9980. if (analysisData.rawDeferredImports !== null) {
  9981. this.registerDeferrableCandidates(componentClassDecl, analysisData.rawDeferredImports, true /* isDeferredImport */, allDeferredDecls, eagerlyUsedDecls, resolutionData);
  9982. }
  9983. }
  9984. }
  9985. /**
  9986. * Inspects provided imports expression (either `@Component.imports` or
  9987. * `@Component.deferredImports`) and registers imported types as deferrable
  9988. * candidates.
  9989. */
  9990. registerDeferrableCandidates(componentClassDecl, importsExpr, isDeferredImport, allDeferredDecls, eagerlyUsedDecls, resolutionData) {
  9991. if (!ts.isArrayLiteralExpression(importsExpr)) {
  9992. return;
  9993. }
  9994. for (const element of importsExpr.elements) {
  9995. const node = checker.tryUnwrapForwardRef(element, this.reflector) || element;
  9996. if (!ts.isIdentifier(node)) {
  9997. // Can't defer-load non-literal references.
  9998. continue;
  9999. }
  10000. const imp = this.reflector.getImportOfIdentifier(node);
  10001. if (imp === null) {
  10002. // Can't defer-load symbols which aren't imported.
  10003. continue;
  10004. }
  10005. const decl = this.reflector.getDeclarationOfIdentifier(node);
  10006. if (decl === null) {
  10007. // Can't defer-load symbols which don't exist.
  10008. continue;
  10009. }
  10010. if (!checker.isNamedClassDeclaration(decl.node)) {
  10011. // Can't defer-load symbols which aren't classes.
  10012. continue;
  10013. }
  10014. // Are we even trying to defer-load this symbol?
  10015. if (!allDeferredDecls.has(decl.node)) {
  10016. continue;
  10017. }
  10018. if (eagerlyUsedDecls.has(decl.node)) {
  10019. // Can't defer-load symbols that are eagerly referenced as a dependency
  10020. // in a template outside of a defer block.
  10021. continue;
  10022. }
  10023. // Is it a standalone directive/component?
  10024. const dirMeta = this.metaReader.getDirectiveMetadata(new checker.Reference(decl.node));
  10025. if (dirMeta !== null && !dirMeta.isStandalone) {
  10026. continue;
  10027. }
  10028. // Is it a standalone pipe?
  10029. const pipeMeta = this.metaReader.getPipeMetadata(new checker.Reference(decl.node));
  10030. if (pipeMeta !== null && !pipeMeta.isStandalone) {
  10031. continue;
  10032. }
  10033. if (dirMeta === null && pipeMeta === null) {
  10034. // This is not a directive or a pipe.
  10035. continue;
  10036. }
  10037. // Keep track of how this class made it into the current source file
  10038. // (which ts.ImportDeclaration was used for this symbol).
  10039. resolutionData.deferrableDeclToImportDecl.set(decl.node, imp.node);
  10040. this.deferredSymbolTracker.markAsDeferrableCandidate(node, imp.node, componentClassDecl, isDeferredImport);
  10041. }
  10042. }
  10043. compileDeferBlocks(resolution) {
  10044. const { deferBlockDepsEmitMode: mode, deferPerBlockDependencies: perBlockDeps, deferPerComponentDependencies: perComponentDeps, } = resolution;
  10045. if (mode === 0 /* DeferBlockDepsEmitMode.PerBlock */) {
  10046. if (!perBlockDeps) {
  10047. throw new Error('Internal error: deferPerBlockDependencies must be present when compiling in PerBlock mode');
  10048. }
  10049. const blocks = new Map();
  10050. for (const [block, dependencies] of perBlockDeps) {
  10051. blocks.set(block, dependencies.length === 0 ? null : checker.compileDeferResolverFunction({ mode, dependencies }));
  10052. }
  10053. return { mode, blocks };
  10054. }
  10055. if (mode === 1 /* DeferBlockDepsEmitMode.PerComponent */) {
  10056. if (!perComponentDeps) {
  10057. throw new Error('Internal error: deferPerComponentDependencies must be present in PerComponent mode');
  10058. }
  10059. return {
  10060. mode,
  10061. dependenciesFn: perComponentDeps.length === 0
  10062. ? null
  10063. : checker.compileDeferResolverFunction({ mode, dependencies: perComponentDeps }),
  10064. };
  10065. }
  10066. throw new Error(`Invalid deferBlockDepsEmitMode. Cannot compile deferred block metadata.`);
  10067. }
  10068. }
  10069. /**
  10070. * Creates an instance of a target binder based on provided dependencies.
  10071. */
  10072. function createTargetBinder(dependencies) {
  10073. const matcher = new checker.SelectorMatcher();
  10074. for (const dep of dependencies) {
  10075. if (dep.kind === checker.MetaKind.Directive && dep.selector !== null) {
  10076. matcher.addSelectables(checker.CssSelector.parse(dep.selector), [dep]);
  10077. }
  10078. }
  10079. return new checker.R3TargetBinder(matcher);
  10080. }
  10081. /**
  10082. * Returns the list of dependencies from `@Component.deferredImports` if provided.
  10083. */
  10084. function getExplicitlyDeferredDeps(scope) {
  10085. return scope.kind === checker.ComponentScopeKind.NgModule
  10086. ? []
  10087. : scope.deferredDependencies;
  10088. }
  10089. function extractPipes(dependencies) {
  10090. const pipes = new Map();
  10091. for (const dep of dependencies) {
  10092. if (dep.kind === checker.MetaKind.Pipe) {
  10093. pipes.set(dep.name, dep);
  10094. }
  10095. }
  10096. return pipes;
  10097. }
  10098. /**
  10099. * Drop references to existing imports for deferrable symbols that should be present
  10100. * in the `setClassMetadataAsync` call. Otherwise, an import declaration gets retained.
  10101. */
  10102. function removeDeferrableTypesFromComponentDecorator(analysis, deferrableTypes) {
  10103. if (analysis.classMetadata) {
  10104. const deferrableSymbols = new Set(deferrableTypes.map((t) => t.symbolName));
  10105. const rewrittenDecoratorsNode = removeIdentifierReferences(analysis.classMetadata.decorators.node, deferrableSymbols);
  10106. analysis.classMetadata.decorators = new checker.WrappedNodeExpr(rewrittenDecoratorsNode);
  10107. }
  10108. }
  10109. /**
  10110. * Validates that `@Component.imports` and `@Component.deferredImports` do not have
  10111. * overlapping dependencies.
  10112. */
  10113. function validateNoImportOverlap(eagerDeps, deferredDeps, rawDeferredImports) {
  10114. let diagnostic = null;
  10115. const eagerDepsSet = new Set();
  10116. for (const eagerDep of eagerDeps) {
  10117. eagerDepsSet.add(eagerDep.ref.node);
  10118. }
  10119. for (const deferredDep of deferredDeps) {
  10120. if (eagerDepsSet.has(deferredDep.ref.node)) {
  10121. const classInfo = deferredDep.ref.debugName
  10122. ? `The \`${deferredDep.ref.debugName}\``
  10123. : 'One of the dependencies';
  10124. diagnostic = checker.makeDiagnostic(checker.ErrorCode.DEFERRED_DEPENDENCY_IMPORTED_EAGERLY, getDiagnosticNode(deferredDep.ref, rawDeferredImports), `\`${classInfo}\` is imported via both \`@Component.imports\` and ` +
  10125. `\`@Component.deferredImports\`. To fix this, make sure that ` +
  10126. `dependencies are imported only once.`);
  10127. break;
  10128. }
  10129. }
  10130. return diagnostic;
  10131. }
  10132. function validateStandaloneImports(importRefs, importExpr, metaReader, scopeReader, isDeferredImport) {
  10133. const diagnostics = [];
  10134. for (const ref of importRefs) {
  10135. const dirMeta = metaReader.getDirectiveMetadata(ref);
  10136. if (dirMeta !== null) {
  10137. if (!dirMeta.isStandalone) {
  10138. // Directly importing a directive that's not standalone is an error.
  10139. diagnostics.push(makeNotStandaloneDiagnostic(scopeReader, ref, importExpr, dirMeta.isComponent ? 'component' : 'directive'));
  10140. }
  10141. continue;
  10142. }
  10143. const pipeMeta = metaReader.getPipeMetadata(ref);
  10144. if (pipeMeta !== null) {
  10145. if (!pipeMeta.isStandalone) {
  10146. diagnostics.push(makeNotStandaloneDiagnostic(scopeReader, ref, importExpr, 'pipe'));
  10147. }
  10148. continue;
  10149. }
  10150. const ngModuleMeta = metaReader.getNgModuleMetadata(ref);
  10151. if (!isDeferredImport && ngModuleMeta !== null) {
  10152. // Importing NgModules is always legal in `@Component.imports`,
  10153. // but not supported in `@Component.deferredImports`.
  10154. continue;
  10155. }
  10156. // Make an error?
  10157. const error = isDeferredImport
  10158. ? makeUnknownComponentDeferredImportDiagnostic(ref, importExpr)
  10159. : makeUnknownComponentImportDiagnostic(ref, importExpr);
  10160. diagnostics.push(error);
  10161. }
  10162. return diagnostics;
  10163. }
  10164. /** Returns whether an ImportDeclaration is a default import. */
  10165. function isDefaultImport(node) {
  10166. return node.importClause !== undefined && node.importClause.namedBindings === undefined;
  10167. }
  10168. /**
  10169. * Adapts the `compileInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
  10170. */
  10171. class InjectableDecoratorHandler {
  10172. reflector;
  10173. evaluator;
  10174. isCore;
  10175. strictCtorDeps;
  10176. injectableRegistry;
  10177. perf;
  10178. includeClassMetadata;
  10179. compilationMode;
  10180. errorOnDuplicateProv;
  10181. constructor(reflector, evaluator, isCore, strictCtorDeps, injectableRegistry, perf, includeClassMetadata, compilationMode,
  10182. /**
  10183. * What to do if the injectable already contains a ɵprov property.
  10184. *
  10185. * If true then an error diagnostic is reported.
  10186. * If false then there is no error and a new ɵprov property is not added.
  10187. */
  10188. errorOnDuplicateProv = true) {
  10189. this.reflector = reflector;
  10190. this.evaluator = evaluator;
  10191. this.isCore = isCore;
  10192. this.strictCtorDeps = strictCtorDeps;
  10193. this.injectableRegistry = injectableRegistry;
  10194. this.perf = perf;
  10195. this.includeClassMetadata = includeClassMetadata;
  10196. this.compilationMode = compilationMode;
  10197. this.errorOnDuplicateProv = errorOnDuplicateProv;
  10198. }
  10199. precedence = checker.HandlerPrecedence.SHARED;
  10200. name = 'InjectableDecoratorHandler';
  10201. detect(node, decorators) {
  10202. if (!decorators) {
  10203. return undefined;
  10204. }
  10205. const decorator = checker.findAngularDecorator(decorators, 'Injectable', this.isCore);
  10206. if (decorator !== undefined) {
  10207. return {
  10208. trigger: decorator.node,
  10209. decorator: decorator,
  10210. metadata: decorator,
  10211. };
  10212. }
  10213. else {
  10214. return undefined;
  10215. }
  10216. }
  10217. analyze(node, decorator) {
  10218. this.perf.eventCount(checker.PerfEvent.AnalyzeInjectable);
  10219. const meta = extractInjectableMetadata(node, decorator, this.reflector);
  10220. const decorators = this.reflector.getDecoratorsOfDeclaration(node);
  10221. return {
  10222. analysis: {
  10223. meta,
  10224. ctorDeps: extractInjectableCtorDeps(node, meta, decorator, this.reflector, this.isCore, this.strictCtorDeps),
  10225. classMetadata: this.includeClassMetadata
  10226. ? extractClassMetadata(node, this.reflector, this.isCore)
  10227. : null,
  10228. // Avoid generating multiple factories if a class has
  10229. // more Angular decorators, apart from Injectable.
  10230. needsFactory: !decorators ||
  10231. decorators.every((current) => !checker.isAngularCore(current) || current.name === 'Injectable'),
  10232. },
  10233. };
  10234. }
  10235. symbol() {
  10236. return null;
  10237. }
  10238. register(node, analysis) {
  10239. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  10240. return;
  10241. }
  10242. this.injectableRegistry.registerInjectable(node, {
  10243. ctorDeps: analysis.ctorDeps,
  10244. });
  10245. }
  10246. resolve(node, analysis) {
  10247. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  10248. return {};
  10249. }
  10250. if (requiresValidCtor(analysis.meta)) {
  10251. const diagnostic = checker.checkInheritanceOfInjectable(node, this.injectableRegistry, this.reflector, this.evaluator, this.strictCtorDeps, 'Injectable');
  10252. if (diagnostic !== null) {
  10253. return {
  10254. diagnostics: [diagnostic],
  10255. };
  10256. }
  10257. }
  10258. return {};
  10259. }
  10260. compileFull(node, analysis) {
  10261. return this.compile(compileNgFactoryDefField, (meta) => checker.compileInjectable(meta, false), compileClassMetadata, node, analysis);
  10262. }
  10263. compilePartial(node, analysis) {
  10264. return this.compile(compileDeclareFactory, compileDeclareInjectableFromMetadata, compileDeclareClassMetadata, node, analysis);
  10265. }
  10266. compileLocal(node, analysis) {
  10267. return this.compile(compileNgFactoryDefField, (meta) => checker.compileInjectable(meta, false), compileClassMetadata, node, analysis);
  10268. }
  10269. compile(compileFactoryFn, compileInjectableFn, compileClassMetadataFn, node, analysis) {
  10270. const results = [];
  10271. if (analysis.needsFactory) {
  10272. const meta = analysis.meta;
  10273. const factoryRes = compileFactoryFn(checker.toFactoryMetadata({ ...meta, deps: analysis.ctorDeps }, checker.FactoryTarget.Injectable));
  10274. if (analysis.classMetadata !== null) {
  10275. factoryRes.statements.push(compileClassMetadataFn(analysis.classMetadata).toStmt());
  10276. }
  10277. results.push(factoryRes);
  10278. }
  10279. const ɵprov = this.reflector.getMembersOfClass(node).find((member) => member.name === 'ɵprov');
  10280. if (ɵprov !== undefined && this.errorOnDuplicateProv) {
  10281. throw new checker.FatalDiagnosticError(checker.ErrorCode.INJECTABLE_DUPLICATE_PROV, ɵprov.nameNode || ɵprov.node || node, 'Injectables cannot contain a static ɵprov property, because the compiler is going to generate one.');
  10282. }
  10283. if (ɵprov === undefined) {
  10284. // Only add a new ɵprov if there is not one already
  10285. const res = compileInjectableFn(analysis.meta);
  10286. results.push({
  10287. name: 'ɵprov',
  10288. initializer: res.expression,
  10289. statements: res.statements,
  10290. type: res.type,
  10291. deferrableImports: null,
  10292. });
  10293. }
  10294. return results;
  10295. }
  10296. }
  10297. /**
  10298. * Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the
  10299. * input metadata needed to run `compileInjectable`.
  10300. *
  10301. * A `null` return value indicates this is @Injectable has invalid data.
  10302. */
  10303. function extractInjectableMetadata(clazz, decorator, reflector) {
  10304. const name = clazz.name.text;
  10305. const type = checker.wrapTypeReference(reflector, clazz);
  10306. const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0;
  10307. if (decorator.args === null) {
  10308. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
  10309. }
  10310. if (decorator.args.length === 0) {
  10311. return {
  10312. name,
  10313. type,
  10314. typeArgumentCount,
  10315. providedIn: checker.createMayBeForwardRefExpression(new checker.LiteralExpr(null), 0 /* ForwardRefHandling.None */),
  10316. };
  10317. }
  10318. else if (decorator.args.length === 1) {
  10319. const metaNode = decorator.args[0];
  10320. // Firstly make sure the decorator argument is an inline literal - if not, it's illegal to
  10321. // transport references from one location to another. This is the problem that lowering
  10322. // used to solve - if this restriction proves too undesirable we can re-implement lowering.
  10323. if (!ts.isObjectLiteralExpression(metaNode)) {
  10324. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_ARG_NOT_LITERAL, metaNode, `@Injectable argument must be an object literal`);
  10325. }
  10326. // Resolve the fields of the literal into a map of field name to expression.
  10327. const meta = checker.reflectObjectLiteral(metaNode);
  10328. const providedIn = meta.has('providedIn')
  10329. ? getProviderExpression(meta.get('providedIn'), reflector)
  10330. : checker.createMayBeForwardRefExpression(new checker.LiteralExpr(null), 0 /* ForwardRefHandling.None */);
  10331. let deps = undefined;
  10332. if ((meta.has('useClass') || meta.has('useFactory')) && meta.has('deps')) {
  10333. const depsExpr = meta.get('deps');
  10334. if (!ts.isArrayLiteralExpression(depsExpr)) {
  10335. throw new checker.FatalDiagnosticError(checker.ErrorCode.VALUE_NOT_LITERAL, depsExpr, `@Injectable deps metadata must be an inline array`);
  10336. }
  10337. deps = depsExpr.elements.map((dep) => getDep(dep, reflector));
  10338. }
  10339. const result = { name, type, typeArgumentCount, providedIn };
  10340. if (meta.has('useValue')) {
  10341. result.useValue = getProviderExpression(meta.get('useValue'), reflector);
  10342. }
  10343. else if (meta.has('useExisting')) {
  10344. result.useExisting = getProviderExpression(meta.get('useExisting'), reflector);
  10345. }
  10346. else if (meta.has('useClass')) {
  10347. result.useClass = getProviderExpression(meta.get('useClass'), reflector);
  10348. result.deps = deps;
  10349. }
  10350. else if (meta.has('useFactory')) {
  10351. result.useFactory = new checker.WrappedNodeExpr(meta.get('useFactory'));
  10352. result.deps = deps;
  10353. }
  10354. return result;
  10355. }
  10356. else {
  10357. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_ARITY_WRONG, decorator.args[2], 'Too many arguments to @Injectable');
  10358. }
  10359. }
  10360. /**
  10361. * Get the `R3ProviderExpression` for this `expression`.
  10362. *
  10363. * The `useValue`, `useExisting` and `useClass` properties might be wrapped in a `ForwardRef`, which
  10364. * needs to be unwrapped. This function will do that unwrapping and set a flag on the returned
  10365. * object to indicate whether the value needed unwrapping.
  10366. */
  10367. function getProviderExpression(expression, reflector) {
  10368. const forwardRefValue = checker.tryUnwrapForwardRef(expression, reflector);
  10369. return checker.createMayBeForwardRefExpression(new checker.WrappedNodeExpr(forwardRefValue ?? expression), forwardRefValue !== null ? 2 /* ForwardRefHandling.Unwrapped */ : 0 /* ForwardRefHandling.None */);
  10370. }
  10371. function extractInjectableCtorDeps(clazz, meta, decorator, reflector, isCore, strictCtorDeps) {
  10372. if (decorator.args === null) {
  10373. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
  10374. }
  10375. let ctorDeps = null;
  10376. if (decorator.args.length === 0) {
  10377. // Ideally, using @Injectable() would have the same effect as using @Injectable({...}), and be
  10378. // subject to the same validation. However, existing Angular code abuses @Injectable, applying
  10379. // it to things like abstract classes with constructors that were never meant for use with
  10380. // Angular's DI.
  10381. //
  10382. // To deal with this, @Injectable() without an argument is more lenient, and if the
  10383. // constructor signature does not work for DI then a factory definition (ɵfac) that throws is
  10384. // generated.
  10385. if (strictCtorDeps && !checker.isAbstractClassDeclaration(clazz)) {
  10386. ctorDeps = checker.getValidConstructorDependencies(clazz, reflector, isCore);
  10387. }
  10388. else {
  10389. ctorDeps = checker.unwrapConstructorDependencies(checker.getConstructorDependencies(clazz, reflector, isCore));
  10390. }
  10391. return ctorDeps;
  10392. }
  10393. else if (decorator.args.length === 1) {
  10394. const rawCtorDeps = checker.getConstructorDependencies(clazz, reflector, isCore);
  10395. if (strictCtorDeps && !checker.isAbstractClassDeclaration(clazz) && requiresValidCtor(meta)) {
  10396. // Since use* was not provided for a concrete class, validate the deps according to
  10397. // strictCtorDeps.
  10398. ctorDeps = checker.validateConstructorDependencies(clazz, rawCtorDeps);
  10399. }
  10400. else {
  10401. ctorDeps = checker.unwrapConstructorDependencies(rawCtorDeps);
  10402. }
  10403. }
  10404. return ctorDeps;
  10405. }
  10406. function requiresValidCtor(meta) {
  10407. return (meta.useValue === undefined &&
  10408. meta.useExisting === undefined &&
  10409. meta.useClass === undefined &&
  10410. meta.useFactory === undefined);
  10411. }
  10412. function getDep(dep, reflector) {
  10413. const meta = {
  10414. token: new checker.WrappedNodeExpr(dep),
  10415. attributeNameType: null,
  10416. host: false,
  10417. optional: false,
  10418. self: false,
  10419. skipSelf: false,
  10420. };
  10421. function maybeUpdateDecorator(dec, reflector, token) {
  10422. const source = reflector.getImportOfIdentifier(dec);
  10423. if (source === null || source.from !== '@angular/core') {
  10424. return false;
  10425. }
  10426. switch (source.name) {
  10427. case 'Inject':
  10428. if (token !== undefined) {
  10429. meta.token = new checker.WrappedNodeExpr(token);
  10430. }
  10431. break;
  10432. case 'Optional':
  10433. meta.optional = true;
  10434. break;
  10435. case 'SkipSelf':
  10436. meta.skipSelf = true;
  10437. break;
  10438. case 'Self':
  10439. meta.self = true;
  10440. break;
  10441. default:
  10442. return false;
  10443. }
  10444. return true;
  10445. }
  10446. if (ts.isArrayLiteralExpression(dep)) {
  10447. dep.elements.forEach((el) => {
  10448. let isDecorator = false;
  10449. if (ts.isIdentifier(el)) {
  10450. isDecorator = maybeUpdateDecorator(el, reflector);
  10451. }
  10452. else if (ts.isNewExpression(el) && ts.isIdentifier(el.expression)) {
  10453. const token = (el.arguments && el.arguments.length > 0 && el.arguments[0]) || undefined;
  10454. isDecorator = maybeUpdateDecorator(el.expression, reflector, token);
  10455. }
  10456. if (!isDecorator) {
  10457. meta.token = new checker.WrappedNodeExpr(el);
  10458. }
  10459. });
  10460. }
  10461. return meta;
  10462. }
  10463. /**
  10464. * Represents an Angular pipe.
  10465. */
  10466. class PipeSymbol extends SemanticSymbol {
  10467. name;
  10468. constructor(decl, name) {
  10469. super(decl);
  10470. this.name = name;
  10471. }
  10472. isPublicApiAffected(previousSymbol) {
  10473. if (!(previousSymbol instanceof PipeSymbol)) {
  10474. return true;
  10475. }
  10476. return this.name !== previousSymbol.name;
  10477. }
  10478. isTypeCheckApiAffected(previousSymbol) {
  10479. return this.isPublicApiAffected(previousSymbol);
  10480. }
  10481. }
  10482. class PipeDecoratorHandler {
  10483. reflector;
  10484. evaluator;
  10485. metaRegistry;
  10486. scopeRegistry;
  10487. injectableRegistry;
  10488. isCore;
  10489. perf;
  10490. includeClassMetadata;
  10491. compilationMode;
  10492. generateExtraImportsInLocalMode;
  10493. strictStandalone;
  10494. implicitStandaloneValue;
  10495. constructor(reflector, evaluator, metaRegistry, scopeRegistry, injectableRegistry, isCore, perf, includeClassMetadata, compilationMode, generateExtraImportsInLocalMode, strictStandalone, implicitStandaloneValue) {
  10496. this.reflector = reflector;
  10497. this.evaluator = evaluator;
  10498. this.metaRegistry = metaRegistry;
  10499. this.scopeRegistry = scopeRegistry;
  10500. this.injectableRegistry = injectableRegistry;
  10501. this.isCore = isCore;
  10502. this.perf = perf;
  10503. this.includeClassMetadata = includeClassMetadata;
  10504. this.compilationMode = compilationMode;
  10505. this.generateExtraImportsInLocalMode = generateExtraImportsInLocalMode;
  10506. this.strictStandalone = strictStandalone;
  10507. this.implicitStandaloneValue = implicitStandaloneValue;
  10508. }
  10509. precedence = checker.HandlerPrecedence.PRIMARY;
  10510. name = 'PipeDecoratorHandler';
  10511. detect(node, decorators) {
  10512. if (!decorators) {
  10513. return undefined;
  10514. }
  10515. const decorator = checker.findAngularDecorator(decorators, 'Pipe', this.isCore);
  10516. if (decorator !== undefined) {
  10517. return {
  10518. trigger: decorator.node,
  10519. decorator: decorator,
  10520. metadata: decorator,
  10521. };
  10522. }
  10523. else {
  10524. return undefined;
  10525. }
  10526. }
  10527. analyze(clazz, decorator) {
  10528. this.perf.eventCount(checker.PerfEvent.AnalyzePipe);
  10529. const name = clazz.name.text;
  10530. const type = checker.wrapTypeReference(this.reflector, clazz);
  10531. if (decorator.args === null) {
  10532. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_NOT_CALLED, decorator.node, `@Pipe must be called`);
  10533. }
  10534. if (decorator.args.length !== 1) {
  10535. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_ARITY_WRONG, decorator.node, '@Pipe must have exactly one argument');
  10536. }
  10537. const meta = checker.unwrapExpression(decorator.args[0]);
  10538. if (!ts.isObjectLiteralExpression(meta)) {
  10539. throw new checker.FatalDiagnosticError(checker.ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, '@Pipe must have a literal argument');
  10540. }
  10541. const pipe = checker.reflectObjectLiteral(meta);
  10542. if (!pipe.has('name')) {
  10543. throw new checker.FatalDiagnosticError(checker.ErrorCode.PIPE_MISSING_NAME, meta, `@Pipe decorator is missing name field`);
  10544. }
  10545. const pipeNameExpr = pipe.get('name');
  10546. const pipeName = this.evaluator.evaluate(pipeNameExpr);
  10547. if (typeof pipeName !== 'string') {
  10548. throw checker.createValueHasWrongTypeError(pipeNameExpr, pipeName, `@Pipe.name must be a string`);
  10549. }
  10550. let pure = true;
  10551. if (pipe.has('pure')) {
  10552. const expr = pipe.get('pure');
  10553. const pureValue = this.evaluator.evaluate(expr);
  10554. if (typeof pureValue !== 'boolean') {
  10555. throw checker.createValueHasWrongTypeError(expr, pureValue, `@Pipe.pure must be a boolean`);
  10556. }
  10557. pure = pureValue;
  10558. }
  10559. let isStandalone = this.implicitStandaloneValue;
  10560. if (pipe.has('standalone')) {
  10561. const expr = pipe.get('standalone');
  10562. const resolved = this.evaluator.evaluate(expr);
  10563. if (typeof resolved !== 'boolean') {
  10564. throw checker.createValueHasWrongTypeError(expr, resolved, `standalone flag must be a boolean`);
  10565. }
  10566. isStandalone = resolved;
  10567. if (!isStandalone && this.strictStandalone) {
  10568. throw new checker.FatalDiagnosticError(checker.ErrorCode.NON_STANDALONE_NOT_ALLOWED, expr, `Only standalone pipes are allowed when 'strictStandalone' is enabled.`);
  10569. }
  10570. }
  10571. return {
  10572. analysis: {
  10573. meta: {
  10574. name,
  10575. type,
  10576. typeArgumentCount: this.reflector.getGenericArityOfClass(clazz) || 0,
  10577. pipeName,
  10578. deps: checker.getValidConstructorDependencies(clazz, this.reflector, this.isCore),
  10579. pure,
  10580. isStandalone,
  10581. },
  10582. classMetadata: this.includeClassMetadata
  10583. ? extractClassMetadata(clazz, this.reflector, this.isCore)
  10584. : null,
  10585. pipeNameExpr,
  10586. decorator: decorator?.node ?? null,
  10587. },
  10588. };
  10589. }
  10590. symbol(node, analysis) {
  10591. return new PipeSymbol(node, analysis.meta.pipeName);
  10592. }
  10593. register(node, analysis) {
  10594. const ref = new checker.Reference(node);
  10595. this.metaRegistry.registerPipeMetadata({
  10596. kind: checker.MetaKind.Pipe,
  10597. ref,
  10598. name: analysis.meta.pipeName,
  10599. nameExpr: analysis.pipeNameExpr,
  10600. isStandalone: analysis.meta.isStandalone,
  10601. decorator: analysis.decorator,
  10602. isExplicitlyDeferred: false,
  10603. isPure: analysis.meta.pure,
  10604. });
  10605. this.injectableRegistry.registerInjectable(node, {
  10606. ctorDeps: analysis.meta.deps,
  10607. });
  10608. }
  10609. resolve(node) {
  10610. if (this.compilationMode === checker.CompilationMode.LOCAL) {
  10611. return {};
  10612. }
  10613. const duplicateDeclData = this.scopeRegistry.getDuplicateDeclarations(node);
  10614. if (duplicateDeclData !== null) {
  10615. // This pipe was declared twice (or more).
  10616. return {
  10617. diagnostics: [checker.makeDuplicateDeclarationError(node, duplicateDeclData, 'Pipe')],
  10618. };
  10619. }
  10620. return {};
  10621. }
  10622. compileFull(node, analysis) {
  10623. const fac = compileNgFactoryDefField(checker.toFactoryMetadata(analysis.meta, checker.FactoryTarget.Pipe));
  10624. const def = checker.compilePipeFromMetadata(analysis.meta);
  10625. const classMetadata = analysis.classMetadata !== null
  10626. ? compileClassMetadata(analysis.classMetadata).toStmt()
  10627. : null;
  10628. return checker.compileResults(fac, def, classMetadata, 'ɵpipe', null, null /* deferrableImports */);
  10629. }
  10630. compilePartial(node, analysis) {
  10631. const fac = compileDeclareFactory(checker.toFactoryMetadata(analysis.meta, checker.FactoryTarget.Pipe));
  10632. const def = compileDeclarePipeFromMetadata(analysis.meta);
  10633. const classMetadata = analysis.classMetadata !== null
  10634. ? compileDeclareClassMetadata(analysis.classMetadata).toStmt()
  10635. : null;
  10636. return checker.compileResults(fac, def, classMetadata, 'ɵpipe', null, null /* deferrableImports */);
  10637. }
  10638. compileLocal(node, analysis) {
  10639. const fac = compileNgFactoryDefField(checker.toFactoryMetadata(analysis.meta, checker.FactoryTarget.Pipe));
  10640. const def = checker.compilePipeFromMetadata(analysis.meta);
  10641. const classMetadata = analysis.classMetadata !== null
  10642. ? compileClassMetadata(analysis.classMetadata).toStmt()
  10643. : null;
  10644. return checker.compileResults(fac, def, classMetadata, 'ɵpipe', null, null /* deferrableImports */);
  10645. }
  10646. }
  10647. /**
  10648. * @module
  10649. * @description
  10650. * Entry point for all public APIs of the compiler-cli package.
  10651. */
  10652. new checker.Version('19.2.13');
  10653. /**
  10654. * Whether a given decorator should be treated as an Angular decorator.
  10655. * Either it's used in @angular/core, or it's imported from there.
  10656. */
  10657. function isAngularDecorator(decorator, isCore) {
  10658. return isCore || (decorator.import !== null && decorator.import.from === '@angular/core');
  10659. }
  10660. /**
  10661. * Extracts the type of the decorator (the function or expression invoked), as well as all the
  10662. * arguments passed to the decorator. Returns an AST with the form:
  10663. *
  10664. * // For @decorator(arg1, arg2)
  10665. * { type: decorator, args: [arg1, arg2] }
  10666. */
  10667. function extractMetadataFromSingleDecorator(decorator, diagnostics) {
  10668. const metadataProperties = [];
  10669. const expr = decorator.expression;
  10670. switch (expr.kind) {
  10671. case ts.SyntaxKind.Identifier:
  10672. // The decorator was a plain @Foo.
  10673. metadataProperties.push(ts.factory.createPropertyAssignment('type', expr));
  10674. break;
  10675. case ts.SyntaxKind.CallExpression:
  10676. // The decorator was a call, like @Foo(bar).
  10677. const call = expr;
  10678. metadataProperties.push(ts.factory.createPropertyAssignment('type', call.expression));
  10679. if (call.arguments.length) {
  10680. const args = [];
  10681. for (const arg of call.arguments) {
  10682. args.push(arg);
  10683. }
  10684. const argsArrayLiteral = ts.factory.createArrayLiteralExpression(ts.factory.createNodeArray(args, true));
  10685. metadataProperties.push(ts.factory.createPropertyAssignment('args', argsArrayLiteral));
  10686. }
  10687. break;
  10688. default:
  10689. diagnostics.push({
  10690. file: decorator.getSourceFile(),
  10691. start: decorator.getStart(),
  10692. length: decorator.getEnd() - decorator.getStart(),
  10693. messageText: `${ts.SyntaxKind[decorator.kind]} not implemented in gathering decorator metadata.`,
  10694. category: ts.DiagnosticCategory.Error,
  10695. code: 0,
  10696. });
  10697. break;
  10698. }
  10699. return ts.factory.createObjectLiteralExpression(metadataProperties);
  10700. }
  10701. /**
  10702. * createCtorParametersClassProperty creates a static 'ctorParameters' property containing
  10703. * downleveled decorator information.
  10704. *
  10705. * The property contains an arrow function that returns an array of object literals of the shape:
  10706. * static ctorParameters = () => [{
  10707. * type: SomeClass|undefined, // the type of the param that's decorated, if it's a value.
  10708. * decorators: [{
  10709. * type: DecoratorFn, // the type of the decorator that's invoked.
  10710. * args: [ARGS], // the arguments passed to the decorator.
  10711. * }]
  10712. * }];
  10713. */
  10714. function createCtorParametersClassProperty(diagnostics, entityNameToExpression, ctorParameters, isClosureCompilerEnabled) {
  10715. const params = [];
  10716. for (const ctorParam of ctorParameters) {
  10717. if (!ctorParam.type && ctorParam.decorators.length === 0) {
  10718. params.push(ts.factory.createNull());
  10719. continue;
  10720. }
  10721. const paramType = ctorParam.type
  10722. ? typeReferenceToExpression(entityNameToExpression, ctorParam.type)
  10723. : undefined;
  10724. const members = [
  10725. ts.factory.createPropertyAssignment('type', paramType || ts.factory.createIdentifier('undefined')),
  10726. ];
  10727. const decorators = [];
  10728. for (const deco of ctorParam.decorators) {
  10729. decorators.push(extractMetadataFromSingleDecorator(deco, diagnostics));
  10730. }
  10731. if (decorators.length) {
  10732. members.push(ts.factory.createPropertyAssignment('decorators', ts.factory.createArrayLiteralExpression(decorators)));
  10733. }
  10734. params.push(ts.factory.createObjectLiteralExpression(members));
  10735. }
  10736. const initializer = ts.factory.createArrowFunction(undefined, undefined, [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createArrayLiteralExpression(params, true));
  10737. const ctorProp = ts.factory.createPropertyDeclaration([ts.factory.createToken(ts.SyntaxKind.StaticKeyword)], 'ctorParameters', undefined, undefined, initializer);
  10738. return ctorProp;
  10739. }
  10740. /**
  10741. * Returns an expression representing the (potentially) value part for the given node.
  10742. *
  10743. * This is a partial re-implementation of TypeScript's serializeTypeReferenceNode. This is a
  10744. * workaround for https://github.com/Microsoft/TypeScript/issues/17516 (serializeTypeReferenceNode
  10745. * not being exposed). In practice this implementation is sufficient for Angular's use of type
  10746. * metadata.
  10747. */
  10748. function typeReferenceToExpression(entityNameToExpression, node) {
  10749. let kind = node.kind;
  10750. if (ts.isLiteralTypeNode(node)) {
  10751. // Treat literal types like their base type (boolean, string, number).
  10752. kind = node.literal.kind;
  10753. }
  10754. switch (kind) {
  10755. case ts.SyntaxKind.FunctionType:
  10756. case ts.SyntaxKind.ConstructorType:
  10757. return ts.factory.createIdentifier('Function');
  10758. case ts.SyntaxKind.ArrayType:
  10759. case ts.SyntaxKind.TupleType:
  10760. return ts.factory.createIdentifier('Array');
  10761. case ts.SyntaxKind.TypePredicate:
  10762. case ts.SyntaxKind.TrueKeyword:
  10763. case ts.SyntaxKind.FalseKeyword:
  10764. case ts.SyntaxKind.BooleanKeyword:
  10765. return ts.factory.createIdentifier('Boolean');
  10766. case ts.SyntaxKind.StringLiteral:
  10767. case ts.SyntaxKind.StringKeyword:
  10768. return ts.factory.createIdentifier('String');
  10769. case ts.SyntaxKind.ObjectKeyword:
  10770. return ts.factory.createIdentifier('Object');
  10771. case ts.SyntaxKind.NumberKeyword:
  10772. case ts.SyntaxKind.NumericLiteral:
  10773. return ts.factory.createIdentifier('Number');
  10774. case ts.SyntaxKind.TypeReference:
  10775. const typeRef = node;
  10776. // Ignore any generic types, just return the base type.
  10777. return entityNameToExpression(typeRef.typeName);
  10778. case ts.SyntaxKind.UnionType:
  10779. const childTypeNodes = node.types.filter((t) => !(ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword));
  10780. return childTypeNodes.length === 1
  10781. ? typeReferenceToExpression(entityNameToExpression, childTypeNodes[0])
  10782. : undefined;
  10783. default:
  10784. return undefined;
  10785. }
  10786. }
  10787. /**
  10788. * Checks whether a given symbol refers to a value that exists at runtime (as distinct from a type).
  10789. *
  10790. * Expands aliases, which is important for the case where
  10791. * import * as x from 'some-module';
  10792. * and x is now a value (the module object).
  10793. */
  10794. function symbolIsRuntimeValue(typeChecker, symbol) {
  10795. if (symbol.flags & ts.SymbolFlags.Alias) {
  10796. symbol = typeChecker.getAliasedSymbol(symbol);
  10797. }
  10798. // Note that const enums are a special case, because
  10799. // while they have a value, they don't exist at runtime.
  10800. return (symbol.flags & ts.SymbolFlags.Value & ts.SymbolFlags.ConstEnumExcludes) !== 0;
  10801. }
  10802. /**
  10803. * Gets a transformer for downleveling Angular constructor parameter and property decorators.
  10804. *
  10805. * Note that Angular class decorators are never processed as those rely on side effects that
  10806. * would otherwise no longer be executed. i.e. the creation of a component definition.
  10807. *
  10808. * @param typeChecker Reference to the program's type checker.
  10809. * @param host Reflection host that is used for determining decorators.
  10810. * @param diagnostics List which will be populated with diagnostics if any.
  10811. * @param isCore Whether the current TypeScript program is for the `@angular/core` package.
  10812. * @param isClosureCompilerEnabled Whether closure annotations need to be added where needed.
  10813. * @param shouldTransformClass Optional function to check if a given class should be transformed.
  10814. */
  10815. function getDownlevelDecoratorsTransform(typeChecker, host, diagnostics, isCore, isClosureCompilerEnabled, shouldTransformClass) {
  10816. /**
  10817. * createPropDecoratorsClassProperty creates a static 'propDecorators'
  10818. * property containing type information for every property that has a
  10819. * decorator applied.
  10820. *
  10821. * static propDecorators: {[key: string]: {type: Function, args?:
  10822. * any[]}[]} = { propA: [{type: MyDecorator, args: [1, 2]}, ...],
  10823. * ...
  10824. * };
  10825. */
  10826. function createPropDecoratorsClassProperty(diagnostics, properties) {
  10827. // `static propDecorators: {[key: string]: ` + {type: Function, args?:
  10828. // any[]}[] + `} = {\n`);
  10829. const entries = [];
  10830. for (const [name, decorators] of properties.entries()) {
  10831. entries.push(ts.factory.createPropertyAssignment(name, ts.factory.createArrayLiteralExpression(decorators.map((deco) => extractMetadataFromSingleDecorator(deco, diagnostics)))));
  10832. }
  10833. const initializer = ts.factory.createObjectLiteralExpression(entries, true);
  10834. const prop = ts.factory.createPropertyDeclaration([ts.factory.createToken(ts.SyntaxKind.StaticKeyword)], 'propDecorators', undefined, undefined, initializer);
  10835. return prop;
  10836. }
  10837. return (context) => {
  10838. // Ensure that referenced type symbols are not elided by TypeScript. Imports for
  10839. // such parameter type symbols previously could be type-only, but now might be also
  10840. // used in the `ctorParameters` static property as a value. We want to make sure
  10841. // that TypeScript does not elide imports for such type references. Read more
  10842. // about this in the description for `loadIsReferencedAliasDeclarationPatch`.
  10843. const referencedParameterTypes = checker.loadIsReferencedAliasDeclarationPatch(context);
  10844. /**
  10845. * Converts an EntityName (from a type annotation) to an expression (accessing a value).
  10846. *
  10847. * For a given qualified name, this walks depth first to find the leftmost identifier,
  10848. * and then converts the path into a property access that can be used as expression.
  10849. */
  10850. function entityNameToExpression(name) {
  10851. const symbol = typeChecker.getSymbolAtLocation(name);
  10852. // Check if the entity name references a symbol that is an actual value. If it is not, it
  10853. // cannot be referenced by an expression, so return undefined.
  10854. if (!symbol ||
  10855. !symbolIsRuntimeValue(typeChecker, symbol) ||
  10856. !symbol.declarations ||
  10857. symbol.declarations.length === 0) {
  10858. return undefined;
  10859. }
  10860. // If we deal with a qualified name, build up a property access expression
  10861. // that could be used in the JavaScript output.
  10862. if (ts.isQualifiedName(name)) {
  10863. const containerExpr = entityNameToExpression(name.left);
  10864. if (containerExpr === undefined) {
  10865. return undefined;
  10866. }
  10867. return ts.factory.createPropertyAccessExpression(containerExpr, name.right);
  10868. }
  10869. const decl = symbol.declarations[0];
  10870. // If the given entity name has been resolved to an alias import declaration,
  10871. // ensure that the alias declaration is not elided by TypeScript, and use its
  10872. // name identifier to reference it at runtime.
  10873. if (checker.isAliasImportDeclaration(decl)) {
  10874. referencedParameterTypes?.add(decl);
  10875. // If the entity name resolves to an alias import declaration, we reference the
  10876. // entity based on the alias import name. This ensures that TypeScript properly
  10877. // resolves the link to the import. Cloning the original entity name identifier
  10878. // could lead to an incorrect resolution at local scope. e.g. Consider the following
  10879. // snippet: `constructor(Dep: Dep) {}`. In such a case, the local `Dep` identifier
  10880. // would resolve to the actual parameter name, and not to the desired import.
  10881. // This happens because the entity name identifier symbol is internally considered
  10882. // as type-only and therefore TypeScript tries to resolve it as value manually.
  10883. // We can help TypeScript and avoid this non-reliable resolution by using an identifier
  10884. // that is not type-only and is directly linked to the import alias declaration.
  10885. if (decl.name !== undefined) {
  10886. return ts.setOriginalNode(ts.factory.createIdentifier(decl.name.text), decl.name);
  10887. }
  10888. }
  10889. // Clone the original entity name identifier so that it can be used to reference
  10890. // its value at runtime. This is used when the identifier is resolving to a file
  10891. // local declaration (otherwise it would resolve to an alias import declaration).
  10892. return ts.setOriginalNode(ts.factory.createIdentifier(name.text), name);
  10893. }
  10894. /**
  10895. * Transforms a class element. Returns a three tuple of name, transformed element, and
  10896. * decorators found. Returns an undefined name if there are no decorators to lower on the
  10897. * element, or the element has an exotic name.
  10898. */
  10899. function transformClassElement(element) {
  10900. element = ts.visitEachChild(element, decoratorDownlevelVisitor, context);
  10901. const decoratorsToKeep = [];
  10902. const toLower = [];
  10903. const decorators = host.getDecoratorsOfDeclaration(element) || [];
  10904. for (const decorator of decorators) {
  10905. // We only deal with concrete nodes in TypeScript sources, so we don't
  10906. // need to handle synthetically created decorators.
  10907. const decoratorNode = decorator.node;
  10908. if (!isAngularDecorator(decorator, isCore)) {
  10909. decoratorsToKeep.push(decoratorNode);
  10910. continue;
  10911. }
  10912. toLower.push(decoratorNode);
  10913. }
  10914. if (!toLower.length)
  10915. return [undefined, element, []];
  10916. if (!element.name || !ts.isIdentifier(element.name)) {
  10917. // Method has a weird name, e.g.
  10918. // [Symbol.foo]() {...}
  10919. diagnostics.push({
  10920. file: element.getSourceFile(),
  10921. start: element.getStart(),
  10922. length: element.getEnd() - element.getStart(),
  10923. messageText: `Cannot process decorators for class element with non-analyzable name.`,
  10924. category: ts.DiagnosticCategory.Error,
  10925. code: 0,
  10926. });
  10927. return [undefined, element, []];
  10928. }
  10929. const elementModifiers = ts.canHaveModifiers(element) ? ts.getModifiers(element) : undefined;
  10930. let modifiers;
  10931. if (decoratorsToKeep.length || elementModifiers?.length) {
  10932. modifiers = ts.setTextRange(ts.factory.createNodeArray([...decoratorsToKeep, ...(elementModifiers || [])]), element.modifiers);
  10933. }
  10934. return [element.name.text, cloneClassElementWithModifiers(element, modifiers), toLower];
  10935. }
  10936. /**
  10937. * Transforms a constructor. Returns the transformed constructor and the list of parameter
  10938. * information collected, consisting of decorators and optional type.
  10939. */
  10940. function transformConstructor(ctor) {
  10941. ctor = ts.visitEachChild(ctor, decoratorDownlevelVisitor, context);
  10942. const newParameters = [];
  10943. const oldParameters = ctor.parameters;
  10944. const parametersInfo = [];
  10945. for (const param of oldParameters) {
  10946. const decoratorsToKeep = [];
  10947. const paramInfo = { decorators: [], type: null };
  10948. const decorators = host.getDecoratorsOfDeclaration(param) || [];
  10949. for (const decorator of decorators) {
  10950. // We only deal with concrete nodes in TypeScript sources, so we don't
  10951. // need to handle synthetically created decorators.
  10952. const decoratorNode = decorator.node;
  10953. if (!isAngularDecorator(decorator, isCore)) {
  10954. decoratorsToKeep.push(decoratorNode);
  10955. continue;
  10956. }
  10957. paramInfo.decorators.push(decoratorNode);
  10958. }
  10959. if (param.type) {
  10960. // param has a type provided, e.g. "foo: Bar".
  10961. // The type will be emitted as a value expression in entityNameToExpression, which takes
  10962. // care not to emit anything for types that cannot be expressed as a value (e.g.
  10963. // interfaces).
  10964. paramInfo.type = param.type;
  10965. }
  10966. parametersInfo.push(paramInfo);
  10967. // Must pass 'undefined' to avoid emitting decorator metadata.
  10968. let modifiers;
  10969. const paramModifiers = ts.getModifiers(param);
  10970. if (decoratorsToKeep.length || paramModifiers?.length) {
  10971. modifiers = [...decoratorsToKeep, ...(paramModifiers || [])];
  10972. }
  10973. const newParam = ts.factory.updateParameterDeclaration(param, modifiers, param.dotDotDotToken, param.name, param.questionToken, param.type, param.initializer);
  10974. newParameters.push(newParam);
  10975. }
  10976. const updated = ts.factory.updateConstructorDeclaration(ctor, ts.getModifiers(ctor), newParameters, ctor.body);
  10977. return [updated, parametersInfo];
  10978. }
  10979. /**
  10980. * Transforms a single class declaration:
  10981. * - dispatches to strip decorators on members
  10982. * - converts decorators on the class to annotations
  10983. * - creates a ctorParameters property
  10984. * - creates a propDecorators property
  10985. */
  10986. function transformClassDeclaration(classDecl) {
  10987. const newMembers = [];
  10988. const decoratedProperties = new Map();
  10989. let classParameters = null;
  10990. for (const member of classDecl.members) {
  10991. switch (member.kind) {
  10992. case ts.SyntaxKind.PropertyDeclaration:
  10993. case ts.SyntaxKind.GetAccessor:
  10994. case ts.SyntaxKind.SetAccessor:
  10995. case ts.SyntaxKind.MethodDeclaration: {
  10996. const [name, newMember, decorators] = transformClassElement(member);
  10997. newMembers.push(newMember);
  10998. if (name)
  10999. decoratedProperties.set(name, decorators);
  11000. continue;
  11001. }
  11002. case ts.SyntaxKind.Constructor: {
  11003. const ctor = member;
  11004. if (!ctor.body)
  11005. break;
  11006. const [newMember, parametersInfo] = transformConstructor(member);
  11007. classParameters = parametersInfo;
  11008. newMembers.push(newMember);
  11009. continue;
  11010. }
  11011. }
  11012. newMembers.push(ts.visitEachChild(member, decoratorDownlevelVisitor, context));
  11013. }
  11014. // Note: The `ReflectionHost.getDecoratorsOfDeclaration()` method will not
  11015. // return all decorators, but only ones that could be possible Angular decorators.
  11016. const possibleAngularDecorators = host.getDecoratorsOfDeclaration(classDecl) || [];
  11017. // Keep track if we come across an Angular class decorator. This is used
  11018. // to determine whether constructor parameters should be captured or not.
  11019. const hasAngularDecorator = possibleAngularDecorators.some((d) => isAngularDecorator(d, isCore));
  11020. if (classParameters) {
  11021. if (hasAngularDecorator || classParameters.some((p) => !!p.decorators.length)) {
  11022. // Capture constructor parameters if the class has Angular decorator applied,
  11023. // or if any of the parameters has decorators applied directly.
  11024. newMembers.push(createCtorParametersClassProperty(diagnostics, entityNameToExpression, classParameters));
  11025. }
  11026. }
  11027. if (decoratedProperties.size) {
  11028. newMembers.push(createPropDecoratorsClassProperty(diagnostics, decoratedProperties));
  11029. }
  11030. const members = ts.setTextRange(ts.factory.createNodeArray(newMembers, classDecl.members.hasTrailingComma), classDecl.members);
  11031. return ts.factory.updateClassDeclaration(classDecl, classDecl.modifiers, classDecl.name, classDecl.typeParameters, classDecl.heritageClauses, members);
  11032. }
  11033. /**
  11034. * Transformer visitor that looks for Angular decorators and replaces them with
  11035. * downleveled static properties. Also collects constructor type metadata for
  11036. * class declaration that are decorated with an Angular decorator.
  11037. */
  11038. function decoratorDownlevelVisitor(node) {
  11039. if (ts.isClassDeclaration(node) &&
  11040. (shouldTransformClass === undefined || shouldTransformClass(node))) {
  11041. return transformClassDeclaration(node);
  11042. }
  11043. return ts.visitEachChild(node, decoratorDownlevelVisitor, context);
  11044. }
  11045. return (sf) => {
  11046. // Downlevel decorators and constructor parameter types. We will keep track of all
  11047. // referenced constructor parameter types so that we can instruct TypeScript to
  11048. // not elide their imports if they previously were only type-only.
  11049. return ts.visitEachChild(sf, decoratorDownlevelVisitor, context);
  11050. };
  11051. };
  11052. }
  11053. function cloneClassElementWithModifiers(node, modifiers) {
  11054. let clone;
  11055. if (ts.isMethodDeclaration(node)) {
  11056. clone = ts.factory.createMethodDeclaration(modifiers, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body);
  11057. }
  11058. else if (ts.isPropertyDeclaration(node)) {
  11059. clone = ts.factory.createPropertyDeclaration(modifiers, node.name, node.questionToken, node.type, node.initializer);
  11060. }
  11061. else if (ts.isGetAccessor(node)) {
  11062. clone = ts.factory.createGetAccessorDeclaration(modifiers, node.name, node.parameters, node.type, node.body);
  11063. }
  11064. else if (ts.isSetAccessor(node)) {
  11065. clone = ts.factory.createSetAccessorDeclaration(modifiers, node.name, node.parameters, node.body);
  11066. }
  11067. else {
  11068. throw new Error(`Unsupported decorated member with kind ${ts.SyntaxKind[node.kind]}`);
  11069. }
  11070. return ts.setOriginalNode(clone, node);
  11071. }
  11072. /**
  11073. * Creates an import and access for a given Angular core import while
  11074. * ensuring the decorator symbol access can be traced back to an Angular core
  11075. * import in order to make the synthetic decorator compatible with the JIT
  11076. * decorator downlevel transform.
  11077. */
  11078. function createSyntheticAngularCoreDecoratorAccess(factory, importManager, ngClassDecorator, sourceFile, decoratorName) {
  11079. const classDecoratorIdentifier = ts.isIdentifier(ngClassDecorator.identifier)
  11080. ? ngClassDecorator.identifier
  11081. : ngClassDecorator.identifier.expression;
  11082. return factory.createPropertyAccessExpression(importManager.addImport({
  11083. exportModuleSpecifier: '@angular/core',
  11084. exportSymbolName: null,
  11085. requestedFile: sourceFile,
  11086. }),
  11087. // The synthetic identifier may be checked later by the downlevel decorators
  11088. // transform to resolve to an Angular import using `getSymbolAtLocation`. We trick
  11089. // the transform to think it's not synthetic and comes from Angular core.
  11090. ts.setOriginalNode(factory.createIdentifier(decoratorName), classDecoratorIdentifier));
  11091. }
  11092. /** Casts the given expression as `any`. */
  11093. function castAsAny(factory, expr) {
  11094. return factory.createAsExpression(expr, factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
  11095. }
  11096. /**
  11097. * Transform that will automatically add an `@Input` decorator for all signal
  11098. * inputs in Angular classes. The decorator will capture metadata of the signal
  11099. * input, derived from the `input()/input.required()` initializer.
  11100. *
  11101. * This transform is useful for JIT environments where signal inputs would like to be
  11102. * used. e.g. for Angular CLI unit testing. In such environments, signal inputs are not
  11103. * statically retrievable at runtime. JIT compilation needs to know about all possible inputs
  11104. * before instantiating directives. A decorator exposes this information to the class without
  11105. * the class needing to be instantiated.
  11106. */
  11107. const signalInputsTransform = (member, sourceFile, host, factory, importTracker, importManager, classDecorator, isCore) => {
  11108. // If the field already is decorated, we handle this gracefully and skip it.
  11109. if (host
  11110. .getDecoratorsOfDeclaration(member.node)
  11111. ?.some((d) => checker.isAngularDecorator(d, 'Input', isCore))) {
  11112. return member.node;
  11113. }
  11114. const inputMapping = checker.tryParseSignalInputMapping(member, host, importTracker);
  11115. if (inputMapping === null) {
  11116. return member.node;
  11117. }
  11118. const fields = {
  11119. 'isSignal': factory.createTrue(),
  11120. 'alias': factory.createStringLiteral(inputMapping.bindingPropertyName),
  11121. 'required': inputMapping.required ? factory.createTrue() : factory.createFalse(),
  11122. // For signal inputs, transforms are captured by the input signal. The runtime will
  11123. // determine whether a transform needs to be run via the input signal, so the `transform`
  11124. // option is always `undefined`.
  11125. 'transform': factory.createIdentifier('undefined'),
  11126. };
  11127. const newDecorator = factory.createDecorator(factory.createCallExpression(createSyntheticAngularCoreDecoratorAccess(factory, importManager, classDecorator, sourceFile, 'Input'), undefined, [
  11128. // Cast to `any` because `isSignal` will be private, and in case this
  11129. // transform is used directly as a pre-compilation step, the decorator should
  11130. // not fail. It is already validated now due to us parsing the input metadata.
  11131. castAsAny(factory, factory.createObjectLiteralExpression(Object.entries(fields).map(([name, value]) => factory.createPropertyAssignment(name, value)))),
  11132. ]));
  11133. return factory.updatePropertyDeclaration(member.node, [newDecorator, ...(member.node.modifiers ?? [])], member.name, member.node.questionToken, member.node.type, member.node.initializer);
  11134. };
  11135. /**
  11136. * Transform that automatically adds `@Input` and `@Output` to members initialized as `model()`.
  11137. * It is useful for JIT environments where models can't be recognized based on the initializer.
  11138. */
  11139. const signalModelTransform = (member, sourceFile, host, factory, importTracker, importManager, classDecorator, isCore) => {
  11140. if (host.getDecoratorsOfDeclaration(member.node)?.some((d) => {
  11141. return checker.isAngularDecorator(d, 'Input', isCore) || checker.isAngularDecorator(d, 'Output', isCore);
  11142. })) {
  11143. return member.node;
  11144. }
  11145. const modelMapping = checker.tryParseSignalModelMapping(member, host, importTracker);
  11146. if (modelMapping === null) {
  11147. return member.node;
  11148. }
  11149. const inputConfig = factory.createObjectLiteralExpression([
  11150. factory.createPropertyAssignment('isSignal', modelMapping.input.isSignal ? factory.createTrue() : factory.createFalse()),
  11151. factory.createPropertyAssignment('alias', factory.createStringLiteral(modelMapping.input.bindingPropertyName)),
  11152. factory.createPropertyAssignment('required', modelMapping.input.required ? factory.createTrue() : factory.createFalse()),
  11153. ]);
  11154. const inputDecorator = createDecorator('Input',
  11155. // Config is cast to `any` because `isSignal` will be private, and in case this
  11156. // transform is used directly as a pre-compilation step, the decorator should
  11157. // not fail. It is already validated now due to us parsing the input metadata.
  11158. factory.createAsExpression(inputConfig, factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)), classDecorator, factory, sourceFile, importManager);
  11159. const outputDecorator = createDecorator('Output', factory.createStringLiteral(modelMapping.output.bindingPropertyName), classDecorator, factory, sourceFile, importManager);
  11160. return factory.updatePropertyDeclaration(member.node, [inputDecorator, outputDecorator, ...(member.node.modifiers ?? [])], member.node.name, member.node.questionToken, member.node.type, member.node.initializer);
  11161. };
  11162. function createDecorator(name, config, classDecorator, factory, sourceFile, importManager) {
  11163. const callTarget = createSyntheticAngularCoreDecoratorAccess(factory, importManager, classDecorator, sourceFile, name);
  11164. return factory.createDecorator(factory.createCallExpression(callTarget, undefined, [config]));
  11165. }
  11166. /**
  11167. * Transform that will automatically add an `@Output` decorator for all initializer API
  11168. * outputs in Angular classes. The decorator will capture metadata of the output, such
  11169. * as the alias.
  11170. *
  11171. * This transform is useful for JIT environments. In such environments, such outputs are not
  11172. * statically retrievable at runtime. JIT compilation needs to know about all possible outputs
  11173. * before instantiating directives. A decorator exposes this information to the class without
  11174. * the class needing to be instantiated.
  11175. */
  11176. const initializerApiOutputTransform = (member, sourceFile, host, factory, importTracker, importManager, classDecorator, isCore) => {
  11177. // If the field already is decorated, we handle this gracefully and skip it.
  11178. if (host
  11179. .getDecoratorsOfDeclaration(member.node)
  11180. ?.some((d) => checker.isAngularDecorator(d, 'Output', isCore))) {
  11181. return member.node;
  11182. }
  11183. const output = checker.tryParseInitializerBasedOutput(member, host, importTracker);
  11184. if (output === null) {
  11185. return member.node;
  11186. }
  11187. const newDecorator = factory.createDecorator(factory.createCallExpression(createSyntheticAngularCoreDecoratorAccess(factory, importManager, classDecorator, sourceFile, 'Output'), undefined, [factory.createStringLiteral(output.metadata.bindingPropertyName)]));
  11188. return factory.updatePropertyDeclaration(member.node, [newDecorator, ...(member.node.modifiers ?? [])], member.node.name, member.node.questionToken, member.node.type, member.node.initializer);
  11189. };
  11190. /** Maps a query function to its decorator. */
  11191. const queryFunctionToDecorator = {
  11192. viewChild: 'ViewChild',
  11193. viewChildren: 'ViewChildren',
  11194. contentChild: 'ContentChild',
  11195. contentChildren: 'ContentChildren',
  11196. };
  11197. /**
  11198. * Transform that will automatically add query decorators for all signal-based
  11199. * queries in Angular classes. The decorator will capture metadata of the signal
  11200. * query, derived from the initializer-based API call.
  11201. *
  11202. * This transform is useful for JIT environments where signal queries would like to be
  11203. * used. e.g. for Angular CLI unit testing. In such environments, signal queries are not
  11204. * statically retrievable at runtime. JIT compilation needs to know about all possible queries
  11205. * before instantiating directives to construct the definition. A decorator exposes this
  11206. * information to the class without the class needing to be instantiated.
  11207. */
  11208. const queryFunctionsTransforms = (member, sourceFile, host, factory, importTracker, importManager, classDecorator, isCore) => {
  11209. const decorators = host.getDecoratorsOfDeclaration(member.node);
  11210. // If the field already is decorated, we handle this gracefully and skip it.
  11211. const queryDecorators = decorators && checker.getAngularDecorators(decorators, checker.queryDecoratorNames, isCore);
  11212. if (queryDecorators !== null && queryDecorators.length > 0) {
  11213. return member.node;
  11214. }
  11215. const queryDefinition = checker.tryParseSignalQueryFromInitializer(member, host, importTracker);
  11216. if (queryDefinition === null) {
  11217. return member.node;
  11218. }
  11219. const callArgs = queryDefinition.call.arguments;
  11220. const newDecorator = factory.createDecorator(factory.createCallExpression(createSyntheticAngularCoreDecoratorAccess(factory, importManager, classDecorator, sourceFile, queryFunctionToDecorator[queryDefinition.name]), undefined,
  11221. // All positional arguments of the query functions can be mostly re-used as is
  11222. // for the decorator. i.e. predicate is always first argument. Options are second.
  11223. [
  11224. queryDefinition.call.arguments[0],
  11225. // Note: Casting as `any` because `isSignal` is not publicly exposed and this
  11226. // transform might pre-transform TS sources.
  11227. castAsAny(factory, factory.createObjectLiteralExpression([
  11228. ...(callArgs.length > 1 ? [factory.createSpreadAssignment(callArgs[1])] : []),
  11229. factory.createPropertyAssignment('isSignal', factory.createTrue()),
  11230. ])),
  11231. ]));
  11232. return factory.updatePropertyDeclaration(member.node, [newDecorator, ...(member.node.modifiers ?? [])], member.node.name, member.node.questionToken, member.node.type, member.node.initializer);
  11233. };
  11234. /** Decorators for classes that should be transformed. */
  11235. const decoratorsWithInputs = ['Directive', 'Component'];
  11236. /**
  11237. * List of possible property transforms.
  11238. * The first one matched on a class member will apply.
  11239. */
  11240. const propertyTransforms = [
  11241. signalInputsTransform,
  11242. initializerApiOutputTransform,
  11243. queryFunctionsTransforms,
  11244. signalModelTransform,
  11245. ];
  11246. /**
  11247. * Creates an AST transform that looks for Angular classes and transforms
  11248. * initializer-based declared members to work with JIT compilation.
  11249. *
  11250. * For example, an `input()` member may be transformed to add an `@Input`
  11251. * decorator for JIT.
  11252. *
  11253. * @param host Reflection host
  11254. * @param importTracker Import tracker for efficient import checking.
  11255. * @param isCore Whether this transforms runs against `@angular/core`.
  11256. * @param shouldTransformClass Optional function to check if a given class should be transformed.
  11257. */
  11258. function getInitializerApiJitTransform(host, importTracker, isCore, shouldTransformClass) {
  11259. return (ctx) => {
  11260. return (sourceFile) => {
  11261. const importManager = new checker.ImportManager();
  11262. sourceFile = ts.visitNode(sourceFile, createTransformVisitor(ctx, host, importManager, importTracker, isCore, shouldTransformClass), ts.isSourceFile);
  11263. return importManager.transformTsFile(ctx, sourceFile);
  11264. };
  11265. };
  11266. }
  11267. function createTransformVisitor(ctx, host, importManager, importTracker, isCore, shouldTransformClass) {
  11268. const visitor = (node) => {
  11269. if (ts.isClassDeclaration(node) && node.name !== undefined) {
  11270. const originalNode = ts.getOriginalNode(node, ts.isClassDeclaration);
  11271. // Note: Attempt to detect the `angularDecorator` on the original node of the class.
  11272. // That is because e.g. Tsickle or other transforms might have transformed the node
  11273. // already to transform decorators.
  11274. const angularDecorator = host
  11275. .getDecoratorsOfDeclaration(originalNode)
  11276. ?.find((d) => decoratorsWithInputs.some((name) => checker.isAngularDecorator(d, name, isCore)));
  11277. if (angularDecorator !== undefined &&
  11278. (shouldTransformClass === undefined || shouldTransformClass(node))) {
  11279. let hasChanged = false;
  11280. const sourceFile = originalNode.getSourceFile();
  11281. const members = node.members.map((memberNode) => {
  11282. if (!ts.isPropertyDeclaration(memberNode)) {
  11283. return memberNode;
  11284. }
  11285. const member = checker.reflectClassMember(memberNode);
  11286. if (member === null) {
  11287. return memberNode;
  11288. }
  11289. // Find the first matching transform and update the class member.
  11290. for (const transform of propertyTransforms) {
  11291. const newNode = transform({ ...member, node: memberNode }, sourceFile, host, ctx.factory, importTracker, importManager, angularDecorator, isCore);
  11292. if (newNode !== member.node) {
  11293. hasChanged = true;
  11294. return newNode;
  11295. }
  11296. }
  11297. return memberNode;
  11298. });
  11299. if (hasChanged) {
  11300. return ctx.factory.updateClassDeclaration(node, node.modifiers, node.name, node.typeParameters, node.heritageClauses, members);
  11301. }
  11302. }
  11303. }
  11304. return ts.visitEachChild(node, visitor, ctx);
  11305. };
  11306. return visitor;
  11307. }
  11308. /**
  11309. * JIT transform for Angular applications. Used by the Angular CLI for unit tests and
  11310. * explicit JIT applications.
  11311. *
  11312. * The transforms include:
  11313. *
  11314. * - A transform for downleveling Angular decorators and Angular-decorated class constructor
  11315. * parameters for dependency injection. This transform can be used by the CLI for JIT-mode
  11316. * compilation where constructor parameters and associated Angular decorators should be
  11317. * downleveled so that apps are not exposed to the ES2015 temporal dead zone limitation
  11318. * in TypeScript. See https://github.com/angular/angular-cli/pull/14473 for more details.
  11319. *
  11320. * - A transform for adding `@Input` to signal inputs. Signal inputs cannot be recognized
  11321. * at runtime using reflection. That is because the class would need to be instantiated-
  11322. * but is not possible before creation. To fix this for JIT, a decorator is automatically
  11323. * added that will declare the input as a signal input while also capturing the necessary
  11324. * metadata
  11325. */
  11326. function angularJitApplicationTransform(program, isCore = false, shouldTransformClass) {
  11327. const typeChecker = program.getTypeChecker();
  11328. const reflectionHost = new checker.TypeScriptReflectionHost(typeChecker);
  11329. const importTracker = new ImportedSymbolsTracker();
  11330. const downlevelDecoratorTransform = getDownlevelDecoratorsTransform(typeChecker, reflectionHost, [], isCore,
  11331. /* enableClosureCompiler */ false, shouldTransformClass);
  11332. const initializerApisJitTransform = getInitializerApiJitTransform(reflectionHost, importTracker, isCore, shouldTransformClass);
  11333. return (ctx) => {
  11334. return (sourceFile) => {
  11335. sourceFile = initializerApisJitTransform(ctx)(sourceFile);
  11336. sourceFile = downlevelDecoratorTransform(ctx)(sourceFile);
  11337. return sourceFile;
  11338. };
  11339. };
  11340. }
  11341. const UNKNOWN_ERROR_CODE = 500;
  11342. exports.EmitFlags = void 0;
  11343. (function (EmitFlags) {
  11344. EmitFlags[EmitFlags["DTS"] = 1] = "DTS";
  11345. EmitFlags[EmitFlags["JS"] = 2] = "JS";
  11346. EmitFlags[EmitFlags["Metadata"] = 4] = "Metadata";
  11347. EmitFlags[EmitFlags["I18nBundle"] = 8] = "I18nBundle";
  11348. EmitFlags[EmitFlags["Codegen"] = 16] = "Codegen";
  11349. EmitFlags[EmitFlags["Default"] = 19] = "Default";
  11350. EmitFlags[EmitFlags["All"] = 31] = "All";
  11351. })(exports.EmitFlags || (exports.EmitFlags = {}));
  11352. function i18nGetExtension(formatName) {
  11353. const format = formatName.toLowerCase();
  11354. switch (format) {
  11355. case 'xmb':
  11356. return 'xmb';
  11357. case 'xlf':
  11358. case 'xlif':
  11359. case 'xliff':
  11360. case 'xlf2':
  11361. case 'xliff2':
  11362. return 'xlf';
  11363. }
  11364. throw new Error(`Unsupported format "${formatName}"`);
  11365. }
  11366. function i18nExtract(formatName, outFile, host, options, bundle, pathResolve = p__namespace.resolve) {
  11367. formatName = formatName || 'xlf';
  11368. // Checks the format and returns the extension
  11369. const ext = i18nGetExtension(formatName);
  11370. const content = i18nSerialize(bundle, formatName, options);
  11371. const dstFile = outFile || `messages.${ext}`;
  11372. const dstPath = pathResolve(options.outDir || options.basePath, dstFile);
  11373. host.writeFile(dstPath, content, false, undefined, []);
  11374. return [dstPath];
  11375. }
  11376. function i18nSerialize(bundle, formatName, options) {
  11377. const format = formatName.toLowerCase();
  11378. let serializer;
  11379. switch (format) {
  11380. case 'xmb':
  11381. serializer = new checker.Xmb();
  11382. break;
  11383. case 'xliff2':
  11384. case 'xlf2':
  11385. serializer = new Xliff2();
  11386. break;
  11387. case 'xlf':
  11388. case 'xliff':
  11389. default:
  11390. serializer = new Xliff();
  11391. }
  11392. return bundle.write(serializer, getPathNormalizer(options.basePath));
  11393. }
  11394. function getPathNormalizer(basePath) {
  11395. // normalize source paths by removing the base path and always using "/" as a separator
  11396. return (sourcePath) => {
  11397. sourcePath = basePath ? p__namespace.relative(basePath, sourcePath) : sourcePath;
  11398. return sourcePath.split(p__namespace.sep).join('/');
  11399. };
  11400. }
  11401. /**
  11402. * Converts a `string` version into an array of numbers
  11403. * @example
  11404. * toNumbers('2.0.1'); // returns [2, 0, 1]
  11405. */
  11406. function toNumbers(value) {
  11407. // Drop any suffixes starting with `-` so that versions like `1.2.3-rc.5` are treated as `1.2.3`.
  11408. const suffixIndex = value.lastIndexOf('-');
  11409. return value
  11410. .slice(0, suffixIndex === -1 ? value.length : suffixIndex)
  11411. .split('.')
  11412. .map((segment) => {
  11413. const parsed = parseInt(segment, 10);
  11414. if (isNaN(parsed)) {
  11415. throw Error(`Unable to parse version string ${value}.`);
  11416. }
  11417. return parsed;
  11418. });
  11419. }
  11420. /**
  11421. * Compares two arrays of positive numbers with lexicographical order in mind.
  11422. *
  11423. * However - unlike lexicographical order - for arrays of different length we consider:
  11424. * [1, 2, 3] = [1, 2, 3, 0] instead of [1, 2, 3] < [1, 2, 3, 0]
  11425. *
  11426. * @param a The 'left hand' array in the comparison test
  11427. * @param b The 'right hand' in the comparison test
  11428. * @returns {-1|0|1} The comparison result: 1 if a is greater, -1 if b is greater, 0 is the two
  11429. * arrays are equals
  11430. */
  11431. function compareNumbers(a, b) {
  11432. const max = Math.max(a.length, b.length);
  11433. const min = Math.min(a.length, b.length);
  11434. for (let i = 0; i < min; i++) {
  11435. if (a[i] > b[i])
  11436. return 1;
  11437. if (a[i] < b[i])
  11438. return -1;
  11439. }
  11440. if (min !== max) {
  11441. const longestArray = a.length === max ? a : b;
  11442. // The result to return in case the to arrays are considered different (1 if a is greater,
  11443. // -1 if b is greater)
  11444. const comparisonResult = a.length === max ? 1 : -1;
  11445. // Check that at least one of the remaining elements is greater than 0 to consider that the two
  11446. // arrays are different (e.g. [1, 0] and [1] are considered the same but not [1, 0, 1] and [1])
  11447. for (let i = min; i < max; i++) {
  11448. if (longestArray[i] > 0) {
  11449. return comparisonResult;
  11450. }
  11451. }
  11452. }
  11453. return 0;
  11454. }
  11455. /**
  11456. * Compares two versions
  11457. *
  11458. * @param v1 The 'left hand' version in the comparison test
  11459. * @param v2 The 'right hand' version in the comparison test
  11460. * @returns {-1|0|1} The comparison result: 1 if v1 is greater, -1 if v2 is greater, 0 is the two
  11461. * versions are equals
  11462. */
  11463. function compareVersions(v1, v2) {
  11464. return compareNumbers(toNumbers(v1), toNumbers(v2));
  11465. }
  11466. /**
  11467. * Minimum supported TypeScript version
  11468. * ∀ supported typescript version v, v >= MIN_TS_VERSION
  11469. *
  11470. * Note: this check is disabled in g3, search for
  11471. * `angularCompilerOptions.disableTypeScriptVersionCheck` config param value in g3.
  11472. */
  11473. const MIN_TS_VERSION = '5.5.0';
  11474. /**
  11475. * Supremum of supported TypeScript versions
  11476. * ∀ supported typescript version v, v < MAX_TS_VERSION
  11477. * MAX_TS_VERSION is not considered as a supported TypeScript version
  11478. *
  11479. * Note: this check is disabled in g3, search for
  11480. * `angularCompilerOptions.disableTypeScriptVersionCheck` config param value in g3.
  11481. */
  11482. const MAX_TS_VERSION = '5.9.0';
  11483. /**
  11484. * The currently used version of TypeScript, which can be adjusted for testing purposes using
  11485. * `setTypeScriptVersionForTesting` and `restoreTypeScriptVersionForTesting` below.
  11486. */
  11487. let tsVersion = ts.version;
  11488. /**
  11489. * Checks whether a given version ∈ [minVersion, maxVersion[.
  11490. * An error will be thrown when the given version ∉ [minVersion, maxVersion[.
  11491. *
  11492. * @param version The version on which the check will be performed
  11493. * @param minVersion The lower bound version. A valid version needs to be greater than minVersion
  11494. * @param maxVersion The upper bound version. A valid version needs to be strictly less than
  11495. * maxVersion
  11496. *
  11497. * @throws Will throw an error if the given version ∉ [minVersion, maxVersion[
  11498. */
  11499. function checkVersion(version, minVersion, maxVersion) {
  11500. if (compareVersions(version, minVersion) < 0 || compareVersions(version, maxVersion) >= 0) {
  11501. throw new Error(`The Angular Compiler requires TypeScript >=${minVersion} and <${maxVersion} but ${version} was found instead.`);
  11502. }
  11503. }
  11504. function verifySupportedTypeScriptVersion() {
  11505. checkVersion(tsVersion, MIN_TS_VERSION, MAX_TS_VERSION);
  11506. }
  11507. /**
  11508. * Analyzes a `ts.Program` for cycles.
  11509. */
  11510. class CycleAnalyzer {
  11511. importGraph;
  11512. /**
  11513. * Cycle detection is requested with the same `from` source file for all used directives and pipes
  11514. * within a component, which makes it beneficial to cache the results as long as the `from` source
  11515. * file has not changed. This avoids visiting the import graph that is reachable from multiple
  11516. * directives/pipes more than once.
  11517. */
  11518. cachedResults = null;
  11519. constructor(importGraph) {
  11520. this.importGraph = importGraph;
  11521. }
  11522. /**
  11523. * Check for a cycle to be created in the `ts.Program` by adding an import between `from` and
  11524. * `to`.
  11525. *
  11526. * @returns a `Cycle` object if an import between `from` and `to` would create a cycle; `null`
  11527. * otherwise.
  11528. */
  11529. wouldCreateCycle(from, to) {
  11530. // Try to reuse the cached results as long as the `from` source file is the same.
  11531. if (this.cachedResults === null || this.cachedResults.from !== from) {
  11532. this.cachedResults = new CycleResults(from, this.importGraph);
  11533. }
  11534. // Import of 'from' -> 'to' is illegal if an edge 'to' -> 'from' already exists.
  11535. return this.cachedResults.wouldBeCyclic(to) ? new Cycle(this.importGraph, from, to) : null;
  11536. }
  11537. /**
  11538. * Record a synthetic import from `from` to `to`.
  11539. *
  11540. * This is an import that doesn't exist in the `ts.Program` but will be considered as part of the
  11541. * import graph for cycle creation.
  11542. */
  11543. recordSyntheticImport(from, to) {
  11544. this.cachedResults = null;
  11545. this.importGraph.addSyntheticImport(from, to);
  11546. }
  11547. }
  11548. const NgCyclicResult = Symbol('NgCyclicResult');
  11549. /**
  11550. * Stores the results of cycle detection in a memory efficient manner. A symbol is attached to
  11551. * source files that indicate what the cyclic analysis result is, as indicated by two markers that
  11552. * are unique to this instance. This alleviates memory pressure in large import graphs, as each
  11553. * execution is able to store its results in the same memory location (i.e. in the symbol
  11554. * on the source file) as earlier executions.
  11555. */
  11556. class CycleResults {
  11557. from;
  11558. importGraph;
  11559. cyclic = {};
  11560. acyclic = {};
  11561. constructor(from, importGraph) {
  11562. this.from = from;
  11563. this.importGraph = importGraph;
  11564. }
  11565. wouldBeCyclic(sf) {
  11566. const cached = this.getCachedResult(sf);
  11567. if (cached !== null) {
  11568. // The result for this source file has already been computed, so return its result.
  11569. return cached;
  11570. }
  11571. if (sf === this.from) {
  11572. // We have reached the source file that we want to create an import from, which means that
  11573. // doing so would create a cycle.
  11574. return true;
  11575. }
  11576. // Assume for now that the file will be acyclic; this prevents infinite recursion in the case
  11577. // that `sf` is visited again as part of an existing cycle in the graph.
  11578. this.markAcyclic(sf);
  11579. const imports = this.importGraph.importsOf(sf);
  11580. for (const imported of imports) {
  11581. if (this.wouldBeCyclic(imported)) {
  11582. this.markCyclic(sf);
  11583. return true;
  11584. }
  11585. }
  11586. return false;
  11587. }
  11588. /**
  11589. * Returns whether the source file is already known to be cyclic, or `null` if the result is not
  11590. * yet known.
  11591. */
  11592. getCachedResult(sf) {
  11593. const result = sf[NgCyclicResult];
  11594. if (result === this.cyclic) {
  11595. return true;
  11596. }
  11597. else if (result === this.acyclic) {
  11598. return false;
  11599. }
  11600. else {
  11601. // Either the symbol is missing or its value does not correspond with one of the current
  11602. // result markers. As such, the result is unknown.
  11603. return null;
  11604. }
  11605. }
  11606. markCyclic(sf) {
  11607. sf[NgCyclicResult] = this.cyclic;
  11608. }
  11609. markAcyclic(sf) {
  11610. sf[NgCyclicResult] = this.acyclic;
  11611. }
  11612. }
  11613. /**
  11614. * Represents an import cycle between `from` and `to` in the program.
  11615. *
  11616. * This class allows us to do the work to compute the cyclic path between `from` and `to` only if
  11617. * needed.
  11618. */
  11619. class Cycle {
  11620. importGraph;
  11621. from;
  11622. to;
  11623. constructor(importGraph, from, to) {
  11624. this.importGraph = importGraph;
  11625. this.from = from;
  11626. this.to = to;
  11627. }
  11628. /**
  11629. * Compute an array of source-files that illustrates the cyclic path between `from` and `to`.
  11630. *
  11631. * Note that a `Cycle` will not be created unless a path is available between `to` and `from`,
  11632. * so `findPath()` will never return `null`.
  11633. */
  11634. getPath() {
  11635. return [this.from, ...this.importGraph.findPath(this.to, this.from)];
  11636. }
  11637. }
  11638. /**
  11639. * A cached graph of imports in the `ts.Program`.
  11640. *
  11641. * The `ImportGraph` keeps track of dependencies (imports) of individual `ts.SourceFile`s. Only
  11642. * dependencies within the same program are tracked; imports into packages on NPM are not.
  11643. */
  11644. class ImportGraph {
  11645. checker;
  11646. perf;
  11647. imports = new Map();
  11648. constructor(checker, perf) {
  11649. this.checker = checker;
  11650. this.perf = perf;
  11651. }
  11652. /**
  11653. * List the direct (not transitive) imports of a given `ts.SourceFile`.
  11654. *
  11655. * This operation is cached.
  11656. */
  11657. importsOf(sf) {
  11658. if (!this.imports.has(sf)) {
  11659. this.imports.set(sf, this.scanImports(sf));
  11660. }
  11661. return this.imports.get(sf);
  11662. }
  11663. /**
  11664. * Find an import path from the `start` SourceFile to the `end` SourceFile.
  11665. *
  11666. * This function implements a breadth first search that results in finding the
  11667. * shortest path between the `start` and `end` points.
  11668. *
  11669. * @param start the starting point of the path.
  11670. * @param end the ending point of the path.
  11671. * @returns an array of source files that connect the `start` and `end` source files, or `null` if
  11672. * no path could be found.
  11673. */
  11674. findPath(start, end) {
  11675. if (start === end) {
  11676. // Escape early for the case where `start` and `end` are the same.
  11677. return [start];
  11678. }
  11679. const found = new Set([start]);
  11680. const queue = [new Found(start, null)];
  11681. while (queue.length > 0) {
  11682. const current = queue.shift();
  11683. const imports = this.importsOf(current.sourceFile);
  11684. for (const importedFile of imports) {
  11685. if (!found.has(importedFile)) {
  11686. const next = new Found(importedFile, current);
  11687. if (next.sourceFile === end) {
  11688. // We have hit the target `end` path so we can stop here.
  11689. return next.toPath();
  11690. }
  11691. found.add(importedFile);
  11692. queue.push(next);
  11693. }
  11694. }
  11695. }
  11696. return null;
  11697. }
  11698. /**
  11699. * Add a record of an import from `sf` to `imported`, that's not present in the original
  11700. * `ts.Program` but will be remembered by the `ImportGraph`.
  11701. */
  11702. addSyntheticImport(sf, imported) {
  11703. if (isLocalFile(imported)) {
  11704. this.importsOf(sf).add(imported);
  11705. }
  11706. }
  11707. scanImports(sf) {
  11708. return this.perf.inPhase(checker.PerfPhase.CycleDetection, () => {
  11709. const imports = new Set();
  11710. // Look through the source file for import and export statements.
  11711. for (const stmt of sf.statements) {
  11712. if ((!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt)) ||
  11713. stmt.moduleSpecifier === undefined) {
  11714. continue;
  11715. }
  11716. if (ts.isImportDeclaration(stmt) &&
  11717. stmt.importClause !== undefined &&
  11718. isTypeOnlyImportClause(stmt.importClause)) {
  11719. // Exclude type-only imports as they are always elided, so they don't contribute to
  11720. // cycles.
  11721. continue;
  11722. }
  11723. const symbol = this.checker.getSymbolAtLocation(stmt.moduleSpecifier);
  11724. if (symbol === undefined || symbol.valueDeclaration === undefined) {
  11725. // No symbol could be found to skip over this import/export.
  11726. continue;
  11727. }
  11728. const moduleFile = symbol.valueDeclaration;
  11729. if (ts.isSourceFile(moduleFile) && isLocalFile(moduleFile)) {
  11730. // Record this local import.
  11731. imports.add(moduleFile);
  11732. }
  11733. }
  11734. return imports;
  11735. });
  11736. }
  11737. }
  11738. function isLocalFile(sf) {
  11739. return !sf.isDeclarationFile;
  11740. }
  11741. function isTypeOnlyImportClause(node) {
  11742. // The clause itself is type-only (e.g. `import type {foo} from '...'`).
  11743. if (node.isTypeOnly) {
  11744. return true;
  11745. }
  11746. // All the specifiers in the cause are type-only (e.g. `import {type a, type b} from '...'`).
  11747. if (node.namedBindings !== undefined &&
  11748. ts.isNamedImports(node.namedBindings) &&
  11749. node.namedBindings.elements.every((specifier) => specifier.isTypeOnly)) {
  11750. return true;
  11751. }
  11752. return false;
  11753. }
  11754. /**
  11755. * A helper class to track which SourceFiles are being processed when searching for a path in
  11756. * `getPath()` above.
  11757. */
  11758. class Found {
  11759. sourceFile;
  11760. parent;
  11761. constructor(sourceFile, parent) {
  11762. this.sourceFile = sourceFile;
  11763. this.parent = parent;
  11764. }
  11765. /**
  11766. * Back track through this found SourceFile and its ancestors to generate an array of
  11767. * SourceFiles that form am import path between two SourceFiles.
  11768. */
  11769. toPath() {
  11770. const array = [];
  11771. let current = this;
  11772. while (current !== null) {
  11773. array.push(current.sourceFile);
  11774. current = current.parent;
  11775. }
  11776. // Pushing and then reversing, O(n), rather than unshifting repeatedly, O(n^2), avoids
  11777. // manipulating the array on every iteration: https://stackoverflow.com/a/26370620
  11778. return array.reverse();
  11779. }
  11780. }
  11781. /** Type of top-level documentation entry. */
  11782. var EntryType;
  11783. (function (EntryType) {
  11784. EntryType["Block"] = "block";
  11785. EntryType["Component"] = "component";
  11786. EntryType["Constant"] = "constant";
  11787. EntryType["Decorator"] = "decorator";
  11788. EntryType["Directive"] = "directive";
  11789. EntryType["Element"] = "element";
  11790. EntryType["Enum"] = "enum";
  11791. EntryType["Function"] = "function";
  11792. EntryType["Interface"] = "interface";
  11793. EntryType["NgModule"] = "ng_module";
  11794. EntryType["Pipe"] = "pipe";
  11795. EntryType["TypeAlias"] = "type_alias";
  11796. EntryType["UndecoratedClass"] = "undecorated_class";
  11797. EntryType["InitializerApiFunction"] = "initializer_api_function";
  11798. })(EntryType || (EntryType = {}));
  11799. /** Types of class members */
  11800. var MemberType;
  11801. (function (MemberType) {
  11802. MemberType["Property"] = "property";
  11803. MemberType["Method"] = "method";
  11804. MemberType["Getter"] = "getter";
  11805. MemberType["Setter"] = "setter";
  11806. MemberType["EnumItem"] = "enum_item";
  11807. })(MemberType || (MemberType = {}));
  11808. var DecoratorType;
  11809. (function (DecoratorType) {
  11810. DecoratorType["Class"] = "class";
  11811. DecoratorType["Member"] = "member";
  11812. DecoratorType["Parameter"] = "parameter";
  11813. })(DecoratorType || (DecoratorType = {}));
  11814. /** Informational tags applicable to class members. */
  11815. var MemberTags;
  11816. (function (MemberTags) {
  11817. MemberTags["Abstract"] = "abstract";
  11818. MemberTags["Static"] = "static";
  11819. MemberTags["Readonly"] = "readonly";
  11820. MemberTags["Protected"] = "protected";
  11821. MemberTags["Optional"] = "optional";
  11822. MemberTags["Input"] = "input";
  11823. MemberTags["Output"] = "output";
  11824. MemberTags["Inherited"] = "override";
  11825. })(MemberTags || (MemberTags = {}));
  11826. /** Gets whether a symbol's name indicates it is an Angular-private API. */
  11827. function isAngularPrivateName(name) {
  11828. const firstChar = name[0] ?? '';
  11829. return firstChar === 'ɵ' || firstChar === '_';
  11830. }
  11831. /** Gets a list of all the generic type parameters for a declaration. */
  11832. function extractGenerics(declaration) {
  11833. return (declaration.typeParameters?.map((typeParam) => ({
  11834. name: typeParam.name.getText(),
  11835. constraint: typeParam.constraint?.getText(),
  11836. default: typeParam.default?.getText(),
  11837. })) ?? []);
  11838. }
  11839. /**
  11840. * RegExp to match the `@` character follow by any Angular decorator, used to escape Angular
  11841. * decorators in JsDoc blocks so that they're not parsed as JsDoc tags.
  11842. */
  11843. const decoratorExpression = /@(?=(Injectable|Component|Directive|Pipe|NgModule|Input|Output|HostBinding|HostListener|Inject|Optional|Self|Host|SkipSelf|ViewChild|ViewChildren|ContentChild|ContentChildren))/g;
  11844. /** Gets the set of JsDoc tags applied to a node. */
  11845. function extractJsDocTags(node) {
  11846. const escapedNode = getEscapedNode(node);
  11847. return ts.getJSDocTags(escapedNode).map((t) => {
  11848. return {
  11849. name: t.tagName.getText(),
  11850. comment: unescapeAngularDecorators(ts.getTextOfJSDocComment(t.comment) ?? ''),
  11851. };
  11852. });
  11853. }
  11854. /**
  11855. * Gets the JsDoc description for a node. If the node does not have
  11856. * a description, returns the empty string.
  11857. */
  11858. function extractJsDocDescription(node) {
  11859. const escapedNode = getEscapedNode(node);
  11860. // If the node is a top-level statement (const, class, function, etc.), we will get
  11861. // a `ts.JSDoc` here. If the node is a `ts.ParameterDeclaration`, we will get
  11862. // a `ts.JSDocParameterTag`.
  11863. const commentOrTag = ts.getJSDocCommentsAndTags(escapedNode).find((d) => {
  11864. return ts.isJSDoc(d) || ts.isJSDocParameterTag(d);
  11865. });
  11866. const comment = commentOrTag?.comment ?? '';
  11867. const description = typeof comment === 'string' ? comment : (ts.getTextOfJSDocComment(comment) ?? '');
  11868. return unescapeAngularDecorators(description);
  11869. }
  11870. /**
  11871. * Gets the raw JsDoc applied to a node.
  11872. * If the node does not have a JsDoc block, returns the empty string.
  11873. */
  11874. function extractRawJsDoc(node) {
  11875. // Assume that any node has at most one JsDoc block.
  11876. const comment = ts.getJSDocCommentsAndTags(node).find(ts.isJSDoc)?.getFullText() ?? '';
  11877. return unescapeAngularDecorators(comment);
  11878. }
  11879. /**
  11880. * Gets an "escaped" version of the node by copying its raw JsDoc into a new source file
  11881. * on top of a dummy class declaration. For the purposes of JsDoc extraction, we don't actually
  11882. * care about the node itself, only its JsDoc block.
  11883. */
  11884. function getEscapedNode(node) {
  11885. // TODO(jelbourn): It's unclear whether we need to escape @param JsDoc, since they're unlikely
  11886. // to have an Angular decorator on the beginning of a line. If we do need to escape them,
  11887. // it will require some more complicated copying below.
  11888. if (ts.isParameter(node)) {
  11889. return node;
  11890. }
  11891. const rawComment = extractRawJsDoc(node);
  11892. const escaped = escapeAngularDecorators(rawComment);
  11893. const file = ts.createSourceFile('x.ts', `${escaped}class X {}`, ts.ScriptTarget.ES2020, true);
  11894. return file.statements.find((s) => ts.isClassDeclaration(s));
  11895. }
  11896. /** Escape the `@` character for Angular decorators. */
  11897. function escapeAngularDecorators(comment) {
  11898. return comment.replace(decoratorExpression, '_NG_AT_');
  11899. }
  11900. /** Unescapes the `@` character for Angular decorators. */
  11901. function unescapeAngularDecorators(comment) {
  11902. return comment.replace(/_NG_AT_/g, '@');
  11903. }
  11904. /** Gets the string representation of a node's resolved type. */
  11905. function extractResolvedTypeString(node, checker) {
  11906. return checker.typeToString(checker.getTypeAtLocation(node), undefined, ts.TypeFormatFlags.NoTruncation);
  11907. }
  11908. class FunctionExtractor {
  11909. name;
  11910. exportDeclaration;
  11911. typeChecker;
  11912. constructor(name, exportDeclaration, typeChecker) {
  11913. this.name = name;
  11914. this.exportDeclaration = exportDeclaration;
  11915. this.typeChecker = typeChecker;
  11916. }
  11917. extract() {
  11918. // TODO: is there any real situation in which the signature would not be available here?
  11919. // Is void a better type?
  11920. const signature = this.typeChecker.getSignatureFromDeclaration(this.exportDeclaration);
  11921. const returnType = signature ? extractReturnType(signature, this.typeChecker) : 'unknown';
  11922. const implementation = findImplementationOfFunction(this.exportDeclaration, this.typeChecker) ??
  11923. this.exportDeclaration;
  11924. const type = this.typeChecker.getTypeAtLocation(this.exportDeclaration);
  11925. const overloads = ts.isConstructorDeclaration(this.exportDeclaration)
  11926. ? constructorOverloads(this.exportDeclaration, this.typeChecker)
  11927. : extractCallSignatures(this.name, this.typeChecker, type);
  11928. const jsdocsTags = extractJsDocTags(implementation);
  11929. const description = extractJsDocDescription(implementation);
  11930. return {
  11931. name: this.name,
  11932. signatures: overloads,
  11933. implementation: {
  11934. params: extractAllParams(implementation.parameters, this.typeChecker),
  11935. isNewType: ts.isConstructSignatureDeclaration(implementation),
  11936. returnType,
  11937. returnDescription: jsdocsTags.find((tag) => tag.name === 'returns')?.comment,
  11938. generics: extractGenerics(implementation),
  11939. name: this.name,
  11940. description,
  11941. entryType: EntryType.Function,
  11942. jsdocTags: jsdocsTags,
  11943. rawComment: extractRawJsDoc(implementation),
  11944. },
  11945. entryType: EntryType.Function,
  11946. description,
  11947. jsdocTags: jsdocsTags,
  11948. rawComment: extractRawJsDoc(implementation),
  11949. };
  11950. }
  11951. }
  11952. function constructorOverloads(constructorDeclaration, typeChecker) {
  11953. const classDeclaration = constructorDeclaration.parent;
  11954. const constructorNode = classDeclaration.members.filter((member) => {
  11955. return ts.isConstructorDeclaration(member) && !member.body;
  11956. });
  11957. return constructorNode.map((n) => {
  11958. return {
  11959. name: 'constructor',
  11960. params: extractAllParams(n.parameters, typeChecker),
  11961. returnType: typeChecker.getTypeAtLocation(classDeclaration)?.symbol.name,
  11962. description: extractJsDocDescription(n),
  11963. entryType: EntryType.Function,
  11964. jsdocTags: extractJsDocTags(n),
  11965. rawComment: extractRawJsDoc(n),
  11966. generics: extractGenerics(n),
  11967. isNewType: false,
  11968. };
  11969. });
  11970. }
  11971. /** Extracts parameters of the given parameter declaration AST nodes. */
  11972. function extractAllParams(params, typeChecker) {
  11973. return params.map((param) => ({
  11974. name: param.name.getText(),
  11975. description: extractJsDocDescription(param),
  11976. type: extractResolvedTypeString(param, typeChecker),
  11977. isOptional: !!(param.questionToken || param.initializer),
  11978. isRestParam: !!param.dotDotDotToken,
  11979. }));
  11980. }
  11981. /** Filters the list signatures to valid function and initializer API signatures. */
  11982. function filterSignatureDeclarations(signatures) {
  11983. const result = [];
  11984. for (const signature of signatures) {
  11985. const decl = signature.getDeclaration();
  11986. if (ts.isFunctionDeclaration(decl) ||
  11987. ts.isCallSignatureDeclaration(decl) ||
  11988. ts.isMethodDeclaration(decl) ||
  11989. ts.isConstructSignatureDeclaration(decl)) {
  11990. result.push({ signature, decl });
  11991. }
  11992. }
  11993. return result;
  11994. }
  11995. function extractCallSignatures(name, typeChecker, type) {
  11996. return filterSignatureDeclarations(type.getCallSignatures()).map(({ decl, signature }) => ({
  11997. name,
  11998. entryType: EntryType.Function,
  11999. description: extractJsDocDescription(decl),
  12000. generics: extractGenerics(decl),
  12001. isNewType: false,
  12002. jsdocTags: extractJsDocTags(decl),
  12003. params: extractAllParams(decl.parameters, typeChecker),
  12004. rawComment: extractRawJsDoc(decl),
  12005. returnType: extractReturnType(signature, typeChecker),
  12006. }));
  12007. }
  12008. function extractReturnType(signature, typeChecker) {
  12009. // Handling Type Predicates
  12010. if (signature?.declaration?.type && ts.isTypePredicateNode(signature.declaration.type)) {
  12011. return signature.declaration.type.getText();
  12012. }
  12013. return typeChecker.typeToString(typeChecker.getReturnTypeOfSignature(signature), undefined,
  12014. // This ensures that e.g. `T | undefined` is not reduced to `T`.
  12015. ts.TypeFormatFlags.NoTypeReduction | ts.TypeFormatFlags.NoTruncation);
  12016. }
  12017. /** Finds the implementation of the given function declaration overload signature. */
  12018. function findImplementationOfFunction(node, typeChecker) {
  12019. if (node.body !== undefined || node.name === undefined) {
  12020. return node;
  12021. }
  12022. const symbol = typeChecker.getSymbolAtLocation(node.name);
  12023. const implementation = symbol?.declarations?.find((s) => ts.isFunctionDeclaration(s) && s.body !== undefined);
  12024. return implementation;
  12025. }
  12026. /**
  12027. * Check if the member has a JSDoc @internal or a @internal is a normal comment
  12028. */
  12029. function isInternal(member) {
  12030. return (extractJsDocTags(member).some((tag) => tag.name === 'internal') ||
  12031. hasLeadingInternalComment(member));
  12032. }
  12033. /*
  12034. * Check if the member has a comment block with @internal
  12035. */
  12036. function hasLeadingInternalComment(member) {
  12037. const memberText = member.getSourceFile().text;
  12038. return (ts.reduceEachLeadingCommentRange(memberText, member.getFullStart(), (pos, end, kind, hasTrailingNewLine, containsInternal) => {
  12039. return containsInternal || memberText.slice(pos, end).includes('@internal');
  12040. },
  12041. /* state */ false,
  12042. /* initial */ false) ?? false);
  12043. }
  12044. /** Extractor to pull info for API reference documentation for a TypeScript class or interface. */
  12045. class ClassExtractor {
  12046. declaration;
  12047. typeChecker;
  12048. constructor(declaration, typeChecker) {
  12049. this.declaration = declaration;
  12050. this.typeChecker = typeChecker;
  12051. }
  12052. /** Extract docs info specific to classes. */
  12053. extract() {
  12054. return {
  12055. name: this.declaration.name.text,
  12056. isAbstract: this.isAbstract(),
  12057. entryType: ts.isInterfaceDeclaration(this.declaration)
  12058. ? EntryType.Interface
  12059. : EntryType.UndecoratedClass,
  12060. members: this.extractSignatures().concat(this.extractAllClassMembers()),
  12061. generics: extractGenerics(this.declaration),
  12062. description: extractJsDocDescription(this.declaration),
  12063. jsdocTags: extractJsDocTags(this.declaration),
  12064. rawComment: extractRawJsDoc(this.declaration),
  12065. extends: this.extractInheritance(this.declaration),
  12066. implements: this.extractInterfaceConformance(this.declaration),
  12067. };
  12068. }
  12069. /** Extracts doc info for a class's members. */
  12070. extractAllClassMembers() {
  12071. const members = [];
  12072. for (const member of this.getMemberDeclarations()) {
  12073. if (this.isMemberExcluded(member))
  12074. continue;
  12075. const memberEntry = this.extractClassMember(member);
  12076. if (memberEntry) {
  12077. members.push(memberEntry);
  12078. }
  12079. }
  12080. return members;
  12081. }
  12082. /** Extract docs for a class's members (methods and properties). */
  12083. extractClassMember(memberDeclaration) {
  12084. if (this.isMethod(memberDeclaration)) {
  12085. return this.extractMethod(memberDeclaration);
  12086. }
  12087. else if (this.isProperty(memberDeclaration) &&
  12088. !this.hasPrivateComputedProperty(memberDeclaration)) {
  12089. return this.extractClassProperty(memberDeclaration);
  12090. }
  12091. else if (ts.isAccessor(memberDeclaration)) {
  12092. return this.extractGetterSetter(memberDeclaration);
  12093. }
  12094. else if (ts.isConstructorDeclaration(memberDeclaration) &&
  12095. memberDeclaration.parameters.length > 0) {
  12096. return this.extractConstructor(memberDeclaration);
  12097. }
  12098. // We only expect methods, properties, and accessors. If we encounter something else,
  12099. // return undefined and let the rest of the program filter it out.
  12100. return undefined;
  12101. }
  12102. /** Extract docs for all call signatures in the current class/interface. */
  12103. extractSignatures() {
  12104. return this.computeAllSignatureDeclarations().map((s) => this.extractSignature(s));
  12105. }
  12106. /** Extracts docs for a class method. */
  12107. extractMethod(methodDeclaration) {
  12108. const functionExtractor = new FunctionExtractor(methodDeclaration.name.getText(), methodDeclaration, this.typeChecker);
  12109. return {
  12110. ...functionExtractor.extract(),
  12111. memberType: MemberType.Method,
  12112. memberTags: this.getMemberTags(methodDeclaration),
  12113. };
  12114. }
  12115. /** Extracts docs for a signature element (usually inside an interface). */
  12116. extractSignature(signature) {
  12117. // No name for the function if we are dealing with call signatures.
  12118. // For construct signatures we are using `new` as the name of the function for now.
  12119. // TODO: Consider exposing a new entry type for signature types.
  12120. const functionExtractor = new FunctionExtractor(ts.isConstructSignatureDeclaration(signature) ? 'new' : '', signature, this.typeChecker);
  12121. return {
  12122. ...functionExtractor.extract(),
  12123. memberType: MemberType.Method,
  12124. memberTags: [],
  12125. };
  12126. }
  12127. /** Extracts doc info for a property declaration. */
  12128. extractClassProperty(propertyDeclaration) {
  12129. return {
  12130. name: propertyDeclaration.name.getText(),
  12131. type: extractResolvedTypeString(propertyDeclaration, this.typeChecker),
  12132. memberType: MemberType.Property,
  12133. memberTags: this.getMemberTags(propertyDeclaration),
  12134. description: extractJsDocDescription(propertyDeclaration),
  12135. jsdocTags: extractJsDocTags(propertyDeclaration),
  12136. };
  12137. }
  12138. /** Extracts doc info for an accessor member (getter/setter). */
  12139. extractGetterSetter(accessor) {
  12140. return {
  12141. ...this.extractClassProperty(accessor),
  12142. memberType: ts.isGetAccessor(accessor) ? MemberType.Getter : MemberType.Setter,
  12143. };
  12144. }
  12145. extractConstructor(constructorDeclaration) {
  12146. const functionExtractor = new FunctionExtractor('constructor', constructorDeclaration, this.typeChecker);
  12147. return {
  12148. ...functionExtractor.extract(),
  12149. memberType: MemberType.Method,
  12150. memberTags: this.getMemberTags(constructorDeclaration),
  12151. };
  12152. }
  12153. extractInheritance(declaration) {
  12154. if (!declaration.heritageClauses) {
  12155. return undefined;
  12156. }
  12157. for (const clause of declaration.heritageClauses) {
  12158. if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
  12159. // We are assuming a single class can only extend one class.
  12160. const types = clause.types;
  12161. if (types.length > 0) {
  12162. const baseClass = types[0];
  12163. return baseClass.getText();
  12164. }
  12165. }
  12166. }
  12167. return undefined;
  12168. }
  12169. extractInterfaceConformance(declaration) {
  12170. const implementClause = declaration.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword);
  12171. return implementClause?.types.map((m) => m.getText()) ?? [];
  12172. }
  12173. /** Gets the tags for a member (protected, readonly, static, etc.) */
  12174. getMemberTags(member) {
  12175. const tags = this.getMemberTagsFromModifiers(member.modifiers ?? []);
  12176. if (member.questionToken) {
  12177. tags.push(MemberTags.Optional);
  12178. }
  12179. if (member.parent !== this.declaration) {
  12180. tags.push(MemberTags.Inherited);
  12181. }
  12182. return tags;
  12183. }
  12184. /** Computes all signature declarations of the class/interface. */
  12185. computeAllSignatureDeclarations() {
  12186. const type = this.typeChecker.getTypeAtLocation(this.declaration);
  12187. const signatures = [...type.getCallSignatures(), ...type.getConstructSignatures()];
  12188. const result = [];
  12189. for (const signature of signatures) {
  12190. const decl = signature.getDeclaration();
  12191. if (this.isDocumentableSignature(decl) && this.isDocumentableMember(decl)) {
  12192. result.push(decl);
  12193. }
  12194. }
  12195. return result;
  12196. }
  12197. /** Gets all member declarations, including inherited members. */
  12198. getMemberDeclarations() {
  12199. // We rely on TypeScript to resolve all the inherited members to their
  12200. // ultimate form via `getProperties`. This is important because child
  12201. // classes may narrow types or add method overloads.
  12202. const type = this.typeChecker.getTypeAtLocation(this.declaration);
  12203. const members = type.getProperties();
  12204. const constructor = type.getSymbol()?.members?.get(ts.InternalSymbolName.Constructor);
  12205. // While the properties of the declaration type represent the properties that exist
  12206. // on a class *instance*, static members are properties on the class symbol itself.
  12207. const typeOfConstructor = this.typeChecker.getTypeOfSymbol(type.symbol);
  12208. const staticMembers = typeOfConstructor.getProperties();
  12209. const result = [];
  12210. for (const member of [...(constructor ? [constructor] : []), ...members, ...staticMembers]) {
  12211. // A member may have multiple declarations in the case of function overloads.
  12212. const memberDeclarations = this.filterMethodOverloads(member.getDeclarations() ?? []);
  12213. for (const memberDeclaration of memberDeclarations) {
  12214. if (this.isDocumentableMember(memberDeclaration)) {
  12215. result.push(memberDeclaration);
  12216. }
  12217. }
  12218. }
  12219. return result;
  12220. }
  12221. /** The result only contains properties, method implementations and abstracts */
  12222. filterMethodOverloads(declarations) {
  12223. return declarations.filter((declaration, index) => {
  12224. // Check if the declaration is a function or method
  12225. if (ts.isFunctionDeclaration(declaration) ||
  12226. ts.isMethodDeclaration(declaration) ||
  12227. ts.isConstructorDeclaration(declaration)) {
  12228. // TypeScript ensures that all declarations for a given abstract method appear consecutively.
  12229. const nextDeclaration = declarations[index + 1];
  12230. const isNextMethodWithSameName = nextDeclaration &&
  12231. ((ts.isMethodDeclaration(nextDeclaration) &&
  12232. nextDeclaration.name.getText() === declaration.name?.getText()) ||
  12233. (ts.isConstructorDeclaration(nextDeclaration) &&
  12234. ts.isConstructorDeclaration(declaration)));
  12235. // Return only the last occurrence of a method to avoid overload duplication.
  12236. // Subsequent overloads or implementations are handled separately by the function extractor.
  12237. return !isNextMethodWithSameName;
  12238. }
  12239. // Include non-method declarations, such as properties, without filtering.
  12240. return true;
  12241. });
  12242. }
  12243. /** Get the tags for a member that come from the declaration modifiers. */
  12244. getMemberTagsFromModifiers(mods) {
  12245. const tags = [];
  12246. for (const mod of mods) {
  12247. const tag = this.getTagForMemberModifier(mod);
  12248. if (tag)
  12249. tags.push(tag);
  12250. }
  12251. return tags;
  12252. }
  12253. /** Gets the doc tag corresponding to a class member modifier (readonly, protected, etc.). */
  12254. getTagForMemberModifier(mod) {
  12255. switch (mod.kind) {
  12256. case ts.SyntaxKind.StaticKeyword:
  12257. return MemberTags.Static;
  12258. case ts.SyntaxKind.ReadonlyKeyword:
  12259. return MemberTags.Readonly;
  12260. case ts.SyntaxKind.ProtectedKeyword:
  12261. return MemberTags.Protected;
  12262. case ts.SyntaxKind.AbstractKeyword:
  12263. return MemberTags.Abstract;
  12264. default:
  12265. return undefined;
  12266. }
  12267. }
  12268. /**
  12269. * Gets whether a given class member should be excluded from public API docs.
  12270. * This is the case if:
  12271. * - The member does not have a name
  12272. * - The member is neither a method nor property
  12273. * - The member is private
  12274. * - The member has a name that marks it as Angular-internal.
  12275. * - The member is marked as internal via JSDoc.
  12276. */
  12277. isMemberExcluded(member) {
  12278. if (ts.isConstructorDeclaration(member)) {
  12279. // A constructor has no name
  12280. return false;
  12281. }
  12282. return (!member.name ||
  12283. !this.isDocumentableMember(member) ||
  12284. (!ts.isCallSignatureDeclaration(member) &&
  12285. member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.PrivateKeyword)) ||
  12286. member.name.getText() === 'prototype' ||
  12287. isAngularPrivateName(member.name.getText()) ||
  12288. isInternal(member));
  12289. }
  12290. /** Gets whether a class member is a method, property, or accessor. */
  12291. isDocumentableMember(member) {
  12292. return (this.isMethod(member) ||
  12293. this.isProperty(member) ||
  12294. ts.isAccessor(member) ||
  12295. ts.isConstructorDeclaration(member) ||
  12296. // Signatures are documentable if they are part of an interface.
  12297. ts.isCallSignatureDeclaration(member));
  12298. }
  12299. /** Check if the parameter is a constructor parameter with a public modifier */
  12300. isPublicConstructorParameterProperty(node) {
  12301. if (ts.isParameterPropertyDeclaration(node, node.parent) && node.modifiers) {
  12302. return node.modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.PublicKeyword);
  12303. }
  12304. return false;
  12305. }
  12306. /** Gets whether a member is a property. */
  12307. isProperty(member) {
  12308. // Classes have declarations, interface have signatures
  12309. return (ts.isPropertyDeclaration(member) ||
  12310. ts.isPropertySignature(member) ||
  12311. this.isPublicConstructorParameterProperty(member));
  12312. }
  12313. /** Gets whether a member is a method. */
  12314. isMethod(member) {
  12315. // Classes have declarations, interface have signatures
  12316. return ts.isMethodDeclaration(member) || ts.isMethodSignature(member);
  12317. }
  12318. /** Gets whether the given signature declaration is documentable. */
  12319. isDocumentableSignature(signature) {
  12320. return (ts.isConstructSignatureDeclaration(signature) || ts.isCallSignatureDeclaration(signature));
  12321. }
  12322. /** Gets whether the declaration for this extractor is abstract. */
  12323. isAbstract() {
  12324. const modifiers = this.declaration.modifiers ?? [];
  12325. return modifiers.some((mod) => mod.kind === ts.SyntaxKind.AbstractKeyword);
  12326. }
  12327. /**
  12328. * Check wether a member has a private computed property name like [ɵWRITABLE_SIGNAL]
  12329. *
  12330. * This will prevent exposing private computed properties in the docs.
  12331. */
  12332. hasPrivateComputedProperty(property) {
  12333. return (ts.isComputedPropertyName(property.name) && property.name.expression.getText().startsWith('ɵ'));
  12334. }
  12335. }
  12336. /** Extractor to pull info for API reference documentation for an Angular directive. */
  12337. class DirectiveExtractor extends ClassExtractor {
  12338. reference;
  12339. metadata;
  12340. constructor(declaration, reference, metadata, checker) {
  12341. super(declaration, checker);
  12342. this.reference = reference;
  12343. this.metadata = metadata;
  12344. }
  12345. /** Extract docs info for directives and components (including underlying class info). */
  12346. extract() {
  12347. return {
  12348. ...super.extract(),
  12349. isStandalone: this.metadata.isStandalone,
  12350. selector: this.metadata.selector ?? '',
  12351. exportAs: this.metadata.exportAs ?? [],
  12352. entryType: this.metadata.isComponent ? EntryType.Component : EntryType.Directive,
  12353. };
  12354. }
  12355. /** Extracts docs info for a directive property, including input/output metadata. */
  12356. extractClassProperty(propertyDeclaration) {
  12357. const entry = super.extractClassProperty(propertyDeclaration);
  12358. const inputMetadata = this.getInputMetadata(propertyDeclaration);
  12359. if (inputMetadata) {
  12360. entry.memberTags.push(MemberTags.Input);
  12361. entry.inputAlias = inputMetadata.bindingPropertyName;
  12362. entry.isRequiredInput = inputMetadata.required;
  12363. }
  12364. const outputMetadata = this.getOutputMetadata(propertyDeclaration);
  12365. if (outputMetadata) {
  12366. entry.memberTags.push(MemberTags.Output);
  12367. entry.outputAlias = outputMetadata.bindingPropertyName;
  12368. }
  12369. return entry;
  12370. }
  12371. /** Gets the input metadata for a directive property. */
  12372. getInputMetadata(prop) {
  12373. const propName = prop.name.getText();
  12374. return this.metadata.inputs?.getByClassPropertyName(propName) ?? undefined;
  12375. }
  12376. /** Gets the output metadata for a directive property. */
  12377. getOutputMetadata(prop) {
  12378. const propName = prop.name.getText();
  12379. return this.metadata?.outputs?.getByClassPropertyName(propName) ?? undefined;
  12380. }
  12381. }
  12382. /** Extractor to pull info for API reference documentation for an Angular pipe. */
  12383. class PipeExtractor extends ClassExtractor {
  12384. reference;
  12385. metadata;
  12386. constructor(declaration, reference, metadata, typeChecker) {
  12387. super(declaration, typeChecker);
  12388. this.reference = reference;
  12389. this.metadata = metadata;
  12390. }
  12391. extract() {
  12392. return {
  12393. ...super.extract(),
  12394. pipeName: this.metadata.name,
  12395. entryType: EntryType.Pipe,
  12396. isStandalone: this.metadata.isStandalone,
  12397. usage: extractPipeSyntax(this.metadata, this.declaration),
  12398. isPure: this.metadata.isPure,
  12399. };
  12400. }
  12401. }
  12402. /** Extractor to pull info for API reference documentation for an Angular pipe. */
  12403. class NgModuleExtractor extends ClassExtractor {
  12404. reference;
  12405. metadata;
  12406. constructor(declaration, reference, metadata, typeChecker) {
  12407. super(declaration, typeChecker);
  12408. this.reference = reference;
  12409. this.metadata = metadata;
  12410. }
  12411. extract() {
  12412. return {
  12413. ...super.extract(),
  12414. entryType: EntryType.NgModule,
  12415. };
  12416. }
  12417. }
  12418. /** Extracts documentation info for a class, potentially including Angular-specific info. */
  12419. function extractClass(classDeclaration, metadataReader, typeChecker) {
  12420. const ref = new checker.Reference(classDeclaration);
  12421. let extractor;
  12422. let directiveMetadata = metadataReader.getDirectiveMetadata(ref);
  12423. let pipeMetadata = metadataReader.getPipeMetadata(ref);
  12424. let ngModuleMetadata = metadataReader.getNgModuleMetadata(ref);
  12425. if (directiveMetadata) {
  12426. extractor = new DirectiveExtractor(classDeclaration, ref, directiveMetadata, typeChecker);
  12427. }
  12428. else if (pipeMetadata) {
  12429. extractor = new PipeExtractor(classDeclaration, ref, pipeMetadata, typeChecker);
  12430. }
  12431. else if (ngModuleMetadata) {
  12432. extractor = new NgModuleExtractor(classDeclaration, ref, ngModuleMetadata, typeChecker);
  12433. }
  12434. else {
  12435. extractor = new ClassExtractor(classDeclaration, typeChecker);
  12436. }
  12437. return extractor.extract();
  12438. }
  12439. /** Extracts documentation info for an interface. */
  12440. function extractInterface(declaration, typeChecker) {
  12441. const extractor = new ClassExtractor(declaration, typeChecker);
  12442. return extractor.extract();
  12443. }
  12444. function extractPipeSyntax(metadata, classDeclaration) {
  12445. const transformParams = classDeclaration.members.find((member) => {
  12446. return (ts.isMethodDeclaration(member) &&
  12447. member.name &&
  12448. ts.isIdentifier(member.name) &&
  12449. member.name.getText() === 'transform');
  12450. });
  12451. let paramNames = transformParams.parameters
  12452. // value is the first argument, it's already referenced before the pipe
  12453. .slice(1)
  12454. .map((param) => {
  12455. return param.name.getText();
  12456. });
  12457. return `{{ value_expression | ${metadata.name}${paramNames.length ? ':' + paramNames.join(':') : ''} }}`;
  12458. }
  12459. /** Name of the tag indicating that an object literal should be shown as an enum in docs. */
  12460. const LITERAL_AS_ENUM_TAG = 'object-literal-as-enum';
  12461. /** Extracts documentation entry for a constant. */
  12462. function extractConstant(declaration, typeChecker) {
  12463. // For constants specifically, we want to get the base type for any literal types.
  12464. // For example, TypeScript by default extracts `const PI = 3.14` as PI having a type of the
  12465. // literal `3.14`. We don't want this behavior for constants, since generally one wants the
  12466. // _value_ of the constant to be able to change between releases without changing the type.
  12467. // `VERSION` is a good example here; the version is always a `string`, but the actual value of
  12468. // the version string shouldn't matter to the type system.
  12469. const resolvedType = typeChecker.getBaseTypeOfLiteralType(typeChecker.getTypeAtLocation(declaration));
  12470. // In the TS AST, the leading comment for a variable declaration is actually
  12471. // on the ancestor `ts.VariableStatement` (since a single variable statement may
  12472. // contain multiple variable declarations).
  12473. const rawComment = extractRawJsDoc(declaration.parent.parent);
  12474. const jsdocTags = extractJsDocTags(declaration);
  12475. const description = extractJsDocDescription(declaration);
  12476. const name = declaration.name.getText();
  12477. // Some constants have to be treated as enums for documentation purposes.
  12478. if (jsdocTags.some((tag) => tag.name === LITERAL_AS_ENUM_TAG)) {
  12479. return {
  12480. name,
  12481. entryType: EntryType.Enum,
  12482. members: extractLiteralPropertiesAsEnumMembers(declaration),
  12483. rawComment,
  12484. description,
  12485. jsdocTags: jsdocTags.filter((tag) => tag.name !== LITERAL_AS_ENUM_TAG),
  12486. };
  12487. }
  12488. return {
  12489. name: name,
  12490. type: typeChecker.typeToString(resolvedType),
  12491. entryType: EntryType.Constant,
  12492. rawComment,
  12493. description,
  12494. jsdocTags,
  12495. };
  12496. }
  12497. /** Gets whether a given constant is an Angular-added const that should be ignored for docs. */
  12498. function isSyntheticAngularConstant(declaration) {
  12499. return declaration.name.getText() === 'USED_FOR_NG_TYPE_CHECKING';
  12500. }
  12501. /**
  12502. * Extracts the properties of a variable initialized as an object literal as if they were enum
  12503. * members. Will throw for any variables that can't be statically analyzed easily.
  12504. */
  12505. function extractLiteralPropertiesAsEnumMembers(declaration) {
  12506. let initializer = declaration.initializer;
  12507. // Unwrap `as` and parenthesized expressions.
  12508. while (initializer &&
  12509. (ts.isAsExpression(initializer) || ts.isParenthesizedExpression(initializer))) {
  12510. initializer = initializer.expression;
  12511. }
  12512. if (initializer === undefined || !ts.isObjectLiteralExpression(initializer)) {
  12513. throw new Error(`Declaration tagged with "${LITERAL_AS_ENUM_TAG}" must be initialized to an object literal, but received ${initializer ? ts.SyntaxKind[initializer.kind] : 'undefined'}`);
  12514. }
  12515. return initializer.properties.map((prop) => {
  12516. if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) {
  12517. throw new Error(`Property in declaration tagged with "${LITERAL_AS_ENUM_TAG}" must be a property assignment with a static name`);
  12518. }
  12519. if (!ts.isNumericLiteral(prop.initializer) && !ts.isStringLiteralLike(prop.initializer)) {
  12520. throw new Error(`Property in declaration tagged with "${LITERAL_AS_ENUM_TAG}" must be initialized to a number or string literal`);
  12521. }
  12522. return {
  12523. name: prop.name.text,
  12524. type: `${declaration.name.getText()}.${prop.name.text}`,
  12525. value: prop.initializer.getText(),
  12526. memberType: MemberType.EnumItem,
  12527. jsdocTags: extractJsDocTags(prop),
  12528. description: extractJsDocDescription(prop),
  12529. memberTags: [],
  12530. };
  12531. });
  12532. }
  12533. /** Extracts an API documentation entry for an Angular decorator. */
  12534. function extractorDecorator(declaration, typeChecker) {
  12535. const documentedNode = getDecoratorJsDocNode(declaration);
  12536. const decoratorType = getDecoratorType(declaration);
  12537. if (!decoratorType) {
  12538. throw new Error(`"${declaration.name.getText()} is not a decorator."`);
  12539. }
  12540. return {
  12541. name: declaration.name.getText(),
  12542. decoratorType: decoratorType,
  12543. entryType: EntryType.Decorator,
  12544. rawComment: extractRawJsDoc(documentedNode),
  12545. description: extractJsDocDescription(documentedNode),
  12546. jsdocTags: extractJsDocTags(documentedNode),
  12547. members: getDecoratorOptions(declaration, typeChecker),
  12548. };
  12549. }
  12550. /** Gets whether the given variable declaration is an Angular decorator declaration. */
  12551. function isDecoratorDeclaration(declaration) {
  12552. return !!getDecoratorType(declaration);
  12553. }
  12554. /** Gets whether an interface is the options interface for a decorator in the same file. */
  12555. function isDecoratorOptionsInterface(declaration) {
  12556. return declaration
  12557. .getSourceFile()
  12558. .statements.some((s) => ts.isVariableStatement(s) &&
  12559. s.declarationList.declarations.some((d) => isDecoratorDeclaration(d) && d.name.getText() === declaration.name.getText()));
  12560. }
  12561. /** Gets the type of decorator, or undefined if the declaration is not a decorator. */
  12562. function getDecoratorType(declaration) {
  12563. // All Angular decorators are initialized with one of `makeDecorator`, `makePropDecorator`,
  12564. // or `makeParamDecorator`.
  12565. const initializer = declaration.initializer?.getFullText() ?? '';
  12566. if (initializer.includes('makeDecorator'))
  12567. return DecoratorType.Class;
  12568. if (initializer.includes('makePropDecorator'))
  12569. return DecoratorType.Member;
  12570. if (initializer.includes('makeParamDecorator'))
  12571. return DecoratorType.Parameter;
  12572. return undefined;
  12573. }
  12574. /** Gets the doc entry for the options object for an Angular decorator */
  12575. function getDecoratorOptions(declaration, typeChecker) {
  12576. const name = declaration.name.getText();
  12577. // Every decorator has an interface with its options in the same SourceFile.
  12578. // Queries, however, are defined as a type alias pointing to an interface.
  12579. const optionsDeclaration = declaration.getSourceFile().statements.find((node) => {
  12580. return ((ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) &&
  12581. node.name.getText() === name);
  12582. });
  12583. if (!optionsDeclaration) {
  12584. throw new Error(`Decorator "${name}" has no corresponding options interface.`);
  12585. }
  12586. let optionsInterface;
  12587. if (ts.isTypeAliasDeclaration(optionsDeclaration)) {
  12588. // We hard-code the assumption that if the decorator's option type is a type alias,
  12589. // it resolves to a single interface (this is true for all query decorators at time of
  12590. // this writing).
  12591. const aliasedType = typeChecker.getTypeAtLocation(optionsDeclaration.type);
  12592. optionsInterface = (aliasedType.getSymbol()?.getDeclarations() ?? []).find((d) => ts.isInterfaceDeclaration(d));
  12593. }
  12594. else {
  12595. optionsInterface = optionsDeclaration;
  12596. }
  12597. if (!optionsInterface || !ts.isInterfaceDeclaration(optionsInterface)) {
  12598. throw new Error(`Options for decorator "${name}" is not an interface.`);
  12599. }
  12600. // Take advantage of the interface extractor to pull the appropriate member info.
  12601. // Hard code the knowledge that decorator options only have properties, never methods.
  12602. return extractInterface(optionsInterface, typeChecker).members;
  12603. }
  12604. /**
  12605. * Gets the call signature node that has the decorator's public JsDoc block.
  12606. *
  12607. * Every decorator has three parts:
  12608. * - A const that has the actual decorator.
  12609. * - An interface with the same name as the const that documents the decorator's options.
  12610. * - An interface suffixed with "Decorator" that has the decorator's call signature and JsDoc block.
  12611. *
  12612. * For the description and JsDoc tags, we need the interface suffixed with "Decorator".
  12613. */
  12614. function getDecoratorJsDocNode(declaration) {
  12615. const name = declaration.name.getText();
  12616. // Assume the existence of an interface in the same file with the same name
  12617. // suffixed with "Decorator".
  12618. const decoratorInterface = declaration.getSourceFile().statements.find((s) => {
  12619. return ts.isInterfaceDeclaration(s) && s.name.getText() === `${name}Decorator`;
  12620. });
  12621. if (!decoratorInterface || !ts.isInterfaceDeclaration(decoratorInterface)) {
  12622. throw new Error(`No interface "${name}Decorator" found.`);
  12623. }
  12624. // The public-facing JsDoc for each decorator is on one of its interface's call signatures.
  12625. const callSignature = decoratorInterface.members.find((node) => {
  12626. // The description block lives on one of the call signatures for this interface.
  12627. return ts.isCallSignatureDeclaration(node) && extractRawJsDoc(node);
  12628. });
  12629. if (!callSignature || !ts.isCallSignatureDeclaration(callSignature)) {
  12630. throw new Error(`No call signature with JsDoc on "${name}Decorator"`);
  12631. }
  12632. return callSignature;
  12633. }
  12634. /** Extracts documentation entry for an enum. */
  12635. function extractEnum(declaration, typeChecker) {
  12636. return {
  12637. name: declaration.name.getText(),
  12638. entryType: EntryType.Enum,
  12639. members: extractEnumMembers(declaration, typeChecker),
  12640. rawComment: extractRawJsDoc(declaration),
  12641. description: extractJsDocDescription(declaration),
  12642. jsdocTags: extractJsDocTags(declaration),
  12643. };
  12644. }
  12645. /** Extracts doc info for an enum's members. */
  12646. function extractEnumMembers(declaration, checker) {
  12647. return declaration.members.map((member) => ({
  12648. name: member.name.getText(),
  12649. type: extractResolvedTypeString(member, checker),
  12650. value: getEnumMemberValue(member),
  12651. memberType: MemberType.EnumItem,
  12652. jsdocTags: extractJsDocTags(member),
  12653. description: extractJsDocDescription(member),
  12654. memberTags: [],
  12655. }));
  12656. }
  12657. /** Gets the explicitly assigned value for an enum member, or an empty string if there is none. */
  12658. function getEnumMemberValue(memberNode) {
  12659. // If the enum member has a child number literal or string literal,
  12660. // we use that literal as the "value" of the member.
  12661. const literal = memberNode.getChildren().find((n) => {
  12662. return (ts.isNumericLiteral(n) ||
  12663. ts.isStringLiteral(n) ||
  12664. (ts.isPrefixUnaryExpression(n) &&
  12665. n.operator === ts.SyntaxKind.MinusToken &&
  12666. ts.isNumericLiteral(n.operand)));
  12667. });
  12668. return literal?.getText() ?? '';
  12669. }
  12670. /** JSDoc used to recognize an initializer API function. */
  12671. const initializerApiTag = 'initializerApiFunction';
  12672. /**
  12673. * Checks whether the given node corresponds to an initializer API function.
  12674. *
  12675. * An initializer API function is a function declaration or variable declaration
  12676. * that is explicitly annotated with `@initializerApiFunction`.
  12677. *
  12678. * Note: The node may be a function overload signature that is automatically
  12679. * resolved to its implementation to detect the JSDoc tag.
  12680. */
  12681. function isInitializerApiFunction(node, typeChecker) {
  12682. // If this is matching an overload signature, resolve to the implementation
  12683. // as it would hold the `@initializerApiFunction` tag.
  12684. if (ts.isFunctionDeclaration(node) && node.name !== undefined && node.body === undefined) {
  12685. const implementation = findImplementationOfFunction(node, typeChecker);
  12686. if (implementation !== undefined) {
  12687. node = implementation;
  12688. }
  12689. }
  12690. if (!ts.isFunctionDeclaration(node) && !ts.isVariableDeclaration(node)) {
  12691. return false;
  12692. }
  12693. let tagContainer = ts.isFunctionDeclaration(node) ? node : getContainerVariableStatement(node);
  12694. if (tagContainer === null) {
  12695. return false;
  12696. }
  12697. const tags = ts.getJSDocTags(tagContainer);
  12698. return tags.some((t) => t.tagName.text === initializerApiTag);
  12699. }
  12700. /**
  12701. * Extracts the given node as initializer API function and returns
  12702. * a docs entry that can be rendered to represent the API function.
  12703. */
  12704. function extractInitializerApiFunction(node, typeChecker) {
  12705. if (node.name === undefined || !ts.isIdentifier(node.name)) {
  12706. throw new Error(`Initializer API: Expected literal variable name.`);
  12707. }
  12708. const container = ts.isFunctionDeclaration(node) ? node : getContainerVariableStatement(node);
  12709. if (container === null) {
  12710. throw new Error('Initializer API: Could not find container AST node of variable.');
  12711. }
  12712. const name = node.name.text;
  12713. const type = typeChecker.getTypeAtLocation(node);
  12714. // Top-level call signatures. E.g. `input()`, `input<ReadT>(initialValue: ReadT)`. etc.
  12715. const callFunction = extractFunctionWithOverloads(name, type, typeChecker);
  12716. // Sub-functions like `input.required()`.
  12717. const subFunctions = [];
  12718. for (const property of type.getProperties()) {
  12719. const subName = property.getName();
  12720. const subDecl = property.getDeclarations()?.[0];
  12721. if (subDecl === undefined || !ts.isPropertySignature(subDecl)) {
  12722. throw new Error(`Initializer API: Could not resolve declaration of sub-property: ${name}.${subName}`);
  12723. }
  12724. const subType = typeChecker.getTypeAtLocation(subDecl);
  12725. subFunctions.push(extractFunctionWithOverloads(subName, subType, typeChecker));
  12726. }
  12727. let jsdocTags;
  12728. let description;
  12729. let rawComment;
  12730. // Extract container API documentation.
  12731. // The container description describes the overall function, while
  12732. // we allow the individual top-level call signatures to represent
  12733. // their individual overloads.
  12734. if (ts.isFunctionDeclaration(node)) {
  12735. const implementation = findImplementationOfFunction(node, typeChecker);
  12736. if (implementation === undefined) {
  12737. throw new Error(`Initializer API: Could not find implementation of function: ${name}`);
  12738. }
  12739. callFunction.implementation = {
  12740. name,
  12741. entryType: EntryType.Function,
  12742. isNewType: false,
  12743. description: extractJsDocDescription(implementation),
  12744. generics: extractGenerics(implementation),
  12745. jsdocTags: extractJsDocTags(implementation),
  12746. params: extractAllParams(implementation.parameters, typeChecker),
  12747. rawComment: extractRawJsDoc(implementation),
  12748. returnType: typeChecker.typeToString(typeChecker.getReturnTypeOfSignature(typeChecker.getSignatureFromDeclaration(implementation))),
  12749. };
  12750. jsdocTags = callFunction.implementation.jsdocTags;
  12751. description = callFunction.implementation.description;
  12752. rawComment = callFunction.implementation.description;
  12753. }
  12754. else {
  12755. jsdocTags = extractJsDocTags(container);
  12756. description = extractJsDocDescription(container);
  12757. rawComment = extractRawJsDoc(container);
  12758. }
  12759. // Extract additional docs metadata from the initializer API JSDoc tag.
  12760. const metadataTag = jsdocTags.find((t) => t.name === initializerApiTag);
  12761. if (metadataTag === undefined) {
  12762. throw new Error('Initializer API: Detected initializer API function does ' +
  12763. `not have "@initializerApiFunction" tag: ${name}`);
  12764. }
  12765. let parsedMetadata = undefined;
  12766. if (metadataTag.comment.trim() !== '') {
  12767. try {
  12768. parsedMetadata = JSON.parse(metadataTag.comment);
  12769. }
  12770. catch (e) {
  12771. throw new Error(`Could not parse initializer API function metadata: ${e}`);
  12772. }
  12773. }
  12774. return {
  12775. entryType: EntryType.InitializerApiFunction,
  12776. name,
  12777. description,
  12778. jsdocTags,
  12779. rawComment,
  12780. callFunction,
  12781. subFunctions,
  12782. __docsMetadata__: parsedMetadata,
  12783. };
  12784. }
  12785. /**
  12786. * Gets the container node of the given variable declaration.
  12787. *
  12788. * A variable declaration may be annotated with e.g. `@initializerApiFunction`,
  12789. * but the JSDoc tag is not attached to the node, but to the containing variable
  12790. * statement.
  12791. */
  12792. function getContainerVariableStatement(node) {
  12793. if (!ts.isVariableDeclarationList(node.parent)) {
  12794. return null;
  12795. }
  12796. if (!ts.isVariableStatement(node.parent.parent)) {
  12797. return null;
  12798. }
  12799. return node.parent.parent;
  12800. }
  12801. /**
  12802. * Extracts all given signatures and returns them as a function with
  12803. * overloads.
  12804. *
  12805. * The implementation of the function may be attached later, or may
  12806. * be non-existent. E.g. initializer APIs declared using an interface
  12807. * with call signatures do not have an associated implementation function
  12808. * that is statically retrievable. The constant holds the overall API description.
  12809. */
  12810. function extractFunctionWithOverloads(name, type, typeChecker) {
  12811. return {
  12812. name,
  12813. signatures: extractCallSignatures(name, typeChecker, type),
  12814. // Implementation may be populated later.
  12815. implementation: null,
  12816. };
  12817. }
  12818. /** Extract the documentation entry for a type alias. */
  12819. function extractTypeAlias(declaration) {
  12820. // TODO: this does not yet resolve type queries (`typeof`). We may want to
  12821. // fix this eventually, but for now it does not appear that any type aliases in
  12822. // Angular's public API rely on this.
  12823. return {
  12824. name: declaration.name.getText(),
  12825. type: declaration.type.getText(),
  12826. entryType: EntryType.TypeAlias,
  12827. generics: extractGenerics(declaration),
  12828. rawComment: extractRawJsDoc(declaration),
  12829. description: extractJsDocDescription(declaration),
  12830. jsdocTags: extractJsDocTags(declaration),
  12831. };
  12832. }
  12833. /**
  12834. * For a given SourceFile, it extracts all imported symbols from other Angular packages.
  12835. *
  12836. * @returns a map Symbol => Package, eg: ApplicationRef => @angular/core
  12837. */
  12838. function getImportedSymbols(sourceFile) {
  12839. const importSpecifiers = new Map();
  12840. function visit(node) {
  12841. if (ts.isImportDeclaration(node)) {
  12842. let moduleSpecifier = node.moduleSpecifier.getText(sourceFile).replace(/['"]/g, '');
  12843. if (moduleSpecifier.startsWith('@angular/')) {
  12844. const namedBindings = node.importClause?.namedBindings;
  12845. if (namedBindings && ts.isNamedImports(namedBindings)) {
  12846. namedBindings.elements.forEach((importSpecifier) => {
  12847. const importName = importSpecifier.name.text;
  12848. const importAlias = importSpecifier.propertyName
  12849. ? importSpecifier.propertyName.text
  12850. : undefined;
  12851. importSpecifiers.set(importAlias ?? importName, moduleSpecifier);
  12852. });
  12853. }
  12854. }
  12855. }
  12856. ts.forEachChild(node, visit);
  12857. }
  12858. visit(sourceFile);
  12859. return importSpecifiers;
  12860. }
  12861. /**
  12862. * Extracts all information from a source file that may be relevant for generating
  12863. * public API documentation.
  12864. */
  12865. class DocsExtractor {
  12866. typeChecker;
  12867. metadataReader;
  12868. constructor(typeChecker, metadataReader) {
  12869. this.typeChecker = typeChecker;
  12870. this.metadataReader = metadataReader;
  12871. }
  12872. /**
  12873. * Gets the set of all documentable entries from a source file, including
  12874. * declarations that are re-exported from this file as an entry-point.
  12875. *
  12876. * @param sourceFile The file from which to extract documentable entries.
  12877. */
  12878. extractAll(sourceFile, rootDir, privateModules) {
  12879. const entries = [];
  12880. const symbols = new Map();
  12881. const exportedDeclarations = this.getExportedDeclarations(sourceFile);
  12882. for (const [exportName, node] of exportedDeclarations) {
  12883. // Skip any symbols with an Angular-internal name.
  12884. if (isAngularPrivateName(exportName)) {
  12885. continue;
  12886. }
  12887. const entry = this.extractDeclaration(node);
  12888. if (entry && !isIgnoredDocEntry(entry)) {
  12889. // The source file parameter is the package entry: the index.ts
  12890. // We want the real source file of the declaration.
  12891. const realSourceFile = node.getSourceFile();
  12892. /**
  12893. * The `sourceFile` from `extractAll` is the main entry-point file of a package.
  12894. * Usually following a format like `export * from './public_api';`, simply re-exporting.
  12895. * It is necessary to pick-up every import from the actual source files
  12896. * where declarations are living, so that we can determine what symbols
  12897. * are actually referenced in the context of that particular declaration
  12898. * By doing this, the generation remains independent from other packages
  12899. */
  12900. const importedSymbols = getImportedSymbols(realSourceFile);
  12901. importedSymbols.forEach((moduleName, symbolName) => {
  12902. if (symbolName.startsWith('ɵ') || privateModules.has(moduleName)) {
  12903. return;
  12904. }
  12905. if (symbols.has(symbolName) && symbols.get(symbolName) !== moduleName) {
  12906. // If this ever throws, we need to improve the symbol extraction strategy
  12907. throw new Error(`Ambigous symbol \`${symbolName}\` exported by both ${symbols.get(symbolName)} & ${moduleName}`);
  12908. }
  12909. symbols.set(symbolName, moduleName);
  12910. });
  12911. // Set the source code references for the extracted entry.
  12912. entry.source = {
  12913. filePath: getRelativeFilePath(realSourceFile, rootDir),
  12914. // Start & End are off by 1
  12915. startLine: ts.getLineAndCharacterOfPosition(realSourceFile, node.getStart()).line + 1,
  12916. endLine: ts.getLineAndCharacterOfPosition(realSourceFile, node.getEnd()).line + 1,
  12917. };
  12918. // The exported name of an API may be different from its declaration name, so
  12919. // use the declaration name.
  12920. entries.push({ ...entry, name: exportName });
  12921. }
  12922. }
  12923. return { entries, symbols };
  12924. }
  12925. /** Extract the doc entry for a single declaration. */
  12926. extractDeclaration(node) {
  12927. // Ignore anonymous classes.
  12928. if (checker.isNamedClassDeclaration(node)) {
  12929. return extractClass(node, this.metadataReader, this.typeChecker);
  12930. }
  12931. if (isInitializerApiFunction(node, this.typeChecker)) {
  12932. return extractInitializerApiFunction(node, this.typeChecker);
  12933. }
  12934. if (ts.isInterfaceDeclaration(node) && !isIgnoredInterface(node)) {
  12935. return extractInterface(node, this.typeChecker);
  12936. }
  12937. if (ts.isFunctionDeclaration(node)) {
  12938. // Name is guaranteed to be set, because it's exported directly.
  12939. const functionExtractor = new FunctionExtractor(node.name.getText(), node, this.typeChecker);
  12940. return functionExtractor.extract();
  12941. }
  12942. if (ts.isVariableDeclaration(node) && !isSyntheticAngularConstant(node)) {
  12943. return isDecoratorDeclaration(node)
  12944. ? extractorDecorator(node, this.typeChecker)
  12945. : extractConstant(node, this.typeChecker);
  12946. }
  12947. if (ts.isTypeAliasDeclaration(node)) {
  12948. return extractTypeAlias(node);
  12949. }
  12950. if (ts.isEnumDeclaration(node)) {
  12951. return extractEnum(node, this.typeChecker);
  12952. }
  12953. return null;
  12954. }
  12955. /** Gets the list of exported declarations for doc extraction. */
  12956. getExportedDeclarations(sourceFile) {
  12957. // Use the reflection host to get all the exported declarations from this
  12958. // source file entry point.
  12959. const reflector = new checker.TypeScriptReflectionHost(this.typeChecker, false, true);
  12960. const exportedDeclarationMap = reflector.getExportsOfModule(sourceFile);
  12961. // Augment each declaration with the exported name in the public API.
  12962. let exportedDeclarations = Array.from(exportedDeclarationMap?.entries() ?? []).map(([exportName, declaration]) => [exportName, declaration.node]);
  12963. // Sort the declaration nodes into declaration position because their order is lost in
  12964. // reading from the export map. This is primarily useful for testing and debugging.
  12965. return exportedDeclarations.sort(([a, declarationA], [b, declarationB]) => declarationA.pos - declarationB.pos);
  12966. }
  12967. }
  12968. /** Gets whether an interface should be ignored for docs extraction. */
  12969. function isIgnoredInterface(node) {
  12970. // We filter out all interfaces that end with "Decorator" because we capture their
  12971. // types as part of the main decorator entry (which are declared as constants).
  12972. // This approach to dealing with decorators is admittedly fuzzy, but this aspect of
  12973. // the framework's source code is unlikely to change. We also filter out the interfaces
  12974. // that contain the decorator options.
  12975. return node.name.getText().endsWith('Decorator') || isDecoratorOptionsInterface(node);
  12976. }
  12977. /**
  12978. * Whether the doc entry should be ignored.
  12979. *
  12980. * Note: We cannot check whether a node is marked as docs private
  12981. * before extraction because the extractor may find the attached
  12982. * JSDoc tags on different AST nodes. For example, a variable declaration
  12983. * never has JSDoc tags attached, but rather the parent variable statement.
  12984. */
  12985. function isIgnoredDocEntry(entry) {
  12986. const isDocsPrivate = entry.jsdocTags.find((e) => e.name === 'docsPrivate');
  12987. if (isDocsPrivate !== undefined && isDocsPrivate.comment === '') {
  12988. throw new Error(`Docs extraction: Entry "${entry.name}" is marked as ` +
  12989. `"@docsPrivate" but without reasoning.`);
  12990. }
  12991. return isDocsPrivate !== undefined;
  12992. }
  12993. function getRelativeFilePath(sourceFile, rootDir) {
  12994. const fullPath = sourceFile.fileName;
  12995. const relativePath = fullPath.replace(rootDir, '');
  12996. return relativePath;
  12997. }
  12998. /// <reference types="node" />
  12999. class FlatIndexGenerator {
  13000. entryPoint;
  13001. moduleName;
  13002. flatIndexPath;
  13003. shouldEmit = true;
  13004. constructor(entryPoint, relativeFlatIndexPath, moduleName) {
  13005. this.entryPoint = entryPoint;
  13006. this.moduleName = moduleName;
  13007. this.flatIndexPath =
  13008. checker.join(checker.dirname(entryPoint), relativeFlatIndexPath).replace(/\.js$/, '') + '.ts';
  13009. }
  13010. makeTopLevelShim() {
  13011. const relativeEntryPoint = relativePathBetween(this.flatIndexPath, this.entryPoint);
  13012. const contents = `/**
  13013. * Generated bundle index. Do not edit.
  13014. */
  13015. export * from '${relativeEntryPoint}';
  13016. `;
  13017. const genFile = ts.createSourceFile(this.flatIndexPath, contents, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TS);
  13018. if (this.moduleName !== null) {
  13019. genFile.moduleName = this.moduleName;
  13020. }
  13021. return genFile;
  13022. }
  13023. }
  13024. function findFlatIndexEntryPoint(rootFiles) {
  13025. // There are two ways for a file to be recognized as the flat module index:
  13026. // 1) if it's the only file!!!!!!
  13027. // 2) (deprecated) if it's named 'index.ts' and has the shortest path of all such files.
  13028. const tsFiles = rootFiles.filter((file) => checker.isNonDeclarationTsPath(file));
  13029. let resolvedEntryPoint = null;
  13030. if (tsFiles.length === 1) {
  13031. // There's only one file - this is the flat module index.
  13032. resolvedEntryPoint = tsFiles[0];
  13033. }
  13034. else {
  13035. // In the event there's more than one TS file, one of them can still be selected as the
  13036. // flat module index if it's named 'index.ts'. If there's more than one 'index.ts', the one
  13037. // with the shortest path wins.
  13038. //
  13039. // This behavior is DEPRECATED and only exists to support existing usages.
  13040. for (const tsFile of tsFiles) {
  13041. if (checker.getFileSystem().basename(tsFile) === 'index.ts' &&
  13042. (resolvedEntryPoint === null || tsFile.length <= resolvedEntryPoint.length)) {
  13043. resolvedEntryPoint = tsFile;
  13044. }
  13045. }
  13046. }
  13047. return resolvedEntryPoint;
  13048. }
  13049. /**
  13050. * Produce `ts.Diagnostic`s for classes that are visible from exported types (e.g. directives
  13051. * exposed by exported `NgModule`s) that are not themselves exported.
  13052. *
  13053. * This function reconciles two concepts:
  13054. *
  13055. * A class is Exported if it's exported from the main library `entryPoint` file.
  13056. * A class is Visible if, via Angular semantics, a downstream consumer can import an Exported class
  13057. * and be affected by the class in question. For example, an Exported NgModule may expose a
  13058. * directive class to its consumers. Consumers that import the NgModule may have the directive
  13059. * applied to elements in their templates. In this case, the directive is considered Visible.
  13060. *
  13061. * `checkForPrivateExports` attempts to verify that all Visible classes are Exported, and report
  13062. * `ts.Diagnostic`s for those that aren't.
  13063. *
  13064. * @param entryPoint `ts.SourceFile` of the library's entrypoint, which should export the library's
  13065. * public API.
  13066. * @param checker `ts.TypeChecker` for the current program.
  13067. * @param refGraph `ReferenceGraph` tracking the visibility of Angular types.
  13068. * @returns an array of `ts.Diagnostic`s representing errors when visible classes are not exported
  13069. * properly.
  13070. */
  13071. function checkForPrivateExports(entryPoint, checker$1, refGraph) {
  13072. const diagnostics = [];
  13073. // Firstly, compute the exports of the entry point. These are all the Exported classes.
  13074. const topLevelExports = new Set();
  13075. // Do this via `ts.TypeChecker.getExportsOfModule`.
  13076. const moduleSymbol = checker$1.getSymbolAtLocation(entryPoint);
  13077. if (moduleSymbol === undefined) {
  13078. throw new Error(`Internal error: failed to get symbol for entrypoint`);
  13079. }
  13080. const exportedSymbols = checker$1.getExportsOfModule(moduleSymbol);
  13081. // Loop through the exported symbols, de-alias if needed, and add them to `topLevelExports`.
  13082. // TODO(alxhub): use proper iteration when build.sh is removed. (#27762)
  13083. exportedSymbols.forEach((symbol) => {
  13084. if (symbol.flags & ts.SymbolFlags.Alias) {
  13085. symbol = checker$1.getAliasedSymbol(symbol);
  13086. }
  13087. const decl = symbol.valueDeclaration;
  13088. if (decl !== undefined) {
  13089. topLevelExports.add(decl);
  13090. }
  13091. });
  13092. // Next, go through each exported class and expand it to the set of classes it makes Visible,
  13093. // using the `ReferenceGraph`. For each Visible class, verify that it's also Exported, and queue
  13094. // an error if it isn't. `checkedSet` ensures only one error is queued per class.
  13095. const checkedSet = new Set();
  13096. // Loop through each Exported class.
  13097. // TODO(alxhub): use proper iteration when the legacy build is removed. (#27762)
  13098. topLevelExports.forEach((mainExport) => {
  13099. // Loop through each class made Visible by the Exported class.
  13100. refGraph.transitiveReferencesOf(mainExport).forEach((transitiveReference) => {
  13101. // Skip classes which have already been checked.
  13102. if (checkedSet.has(transitiveReference)) {
  13103. return;
  13104. }
  13105. checkedSet.add(transitiveReference);
  13106. // Verify that the Visible class is also Exported.
  13107. if (!topLevelExports.has(transitiveReference)) {
  13108. // This is an error, `mainExport` makes `transitiveReference` Visible, but
  13109. // `transitiveReference` is not Exported from the entrypoint. Construct a diagnostic to
  13110. // give to the user explaining the situation.
  13111. const descriptor = getDescriptorOfDeclaration(transitiveReference);
  13112. const name = getNameOfDeclaration(transitiveReference);
  13113. // Construct the path of visibility, from `mainExport` to `transitiveReference`.
  13114. let visibleVia = 'NgModule exports';
  13115. const transitivePath = refGraph.pathFrom(mainExport, transitiveReference);
  13116. if (transitivePath !== null) {
  13117. visibleVia = transitivePath.map((seg) => getNameOfDeclaration(seg)).join(' -> ');
  13118. }
  13119. const diagnostic = {
  13120. category: ts.DiagnosticCategory.Error,
  13121. code: checker.ngErrorCode(checker.ErrorCode.SYMBOL_NOT_EXPORTED),
  13122. file: transitiveReference.getSourceFile(),
  13123. ...getPosOfDeclaration(transitiveReference),
  13124. messageText: `Unsupported private ${descriptor} ${name}. This ${descriptor} is visible to consumers via ${visibleVia}, but is not exported from the top-level library entrypoint.`,
  13125. };
  13126. diagnostics.push(diagnostic);
  13127. }
  13128. });
  13129. });
  13130. return diagnostics;
  13131. }
  13132. function getPosOfDeclaration(decl) {
  13133. const node = getIdentifierOfDeclaration(decl) || decl;
  13134. return {
  13135. start: node.getStart(),
  13136. length: node.getEnd() + 1 - node.getStart(),
  13137. };
  13138. }
  13139. function getIdentifierOfDeclaration(decl) {
  13140. if ((ts.isClassDeclaration(decl) ||
  13141. ts.isVariableDeclaration(decl) ||
  13142. ts.isFunctionDeclaration(decl)) &&
  13143. decl.name !== undefined &&
  13144. ts.isIdentifier(decl.name)) {
  13145. return decl.name;
  13146. }
  13147. else {
  13148. return null;
  13149. }
  13150. }
  13151. function getNameOfDeclaration(decl) {
  13152. const id = getIdentifierOfDeclaration(decl);
  13153. return id !== null ? id.text : '(unnamed)';
  13154. }
  13155. function getDescriptorOfDeclaration(decl) {
  13156. switch (decl.kind) {
  13157. case ts.SyntaxKind.ClassDeclaration:
  13158. return 'class';
  13159. case ts.SyntaxKind.FunctionDeclaration:
  13160. return 'function';
  13161. case ts.SyntaxKind.VariableDeclaration:
  13162. return 'variable';
  13163. case ts.SyntaxKind.EnumDeclaration:
  13164. return 'enum';
  13165. default:
  13166. return 'declaration';
  13167. }
  13168. }
  13169. class ReferenceGraph {
  13170. references = new Map();
  13171. add(from, to) {
  13172. if (!this.references.has(from)) {
  13173. this.references.set(from, new Set());
  13174. }
  13175. this.references.get(from).add(to);
  13176. }
  13177. transitiveReferencesOf(target) {
  13178. const set = new Set();
  13179. this.collectTransitiveReferences(set, target);
  13180. return set;
  13181. }
  13182. pathFrom(source, target) {
  13183. return this.collectPathFrom(source, target, new Set());
  13184. }
  13185. collectPathFrom(source, target, seen) {
  13186. if (source === target) {
  13187. // Looking for a path from the target to itself - that path is just the target. This is the
  13188. // "base case" of the search.
  13189. return [target];
  13190. }
  13191. else if (seen.has(source)) {
  13192. // The search has already looked through this source before.
  13193. return null;
  13194. }
  13195. // Consider outgoing edges from `source`.
  13196. seen.add(source);
  13197. if (!this.references.has(source)) {
  13198. // There are no outgoing edges from `source`.
  13199. return null;
  13200. }
  13201. else {
  13202. // Look through the outgoing edges of `source`.
  13203. // TODO(alxhub): use proper iteration when the legacy build is removed. (#27762)
  13204. let candidatePath = null;
  13205. this.references.get(source).forEach((edge) => {
  13206. // Early exit if a path has already been found.
  13207. if (candidatePath !== null) {
  13208. return;
  13209. }
  13210. // Look for a path from this outgoing edge to `target`.
  13211. const partialPath = this.collectPathFrom(edge, target, seen);
  13212. if (partialPath !== null) {
  13213. // A path exists from `edge` to `target`. Insert `source` at the beginning.
  13214. candidatePath = [source, ...partialPath];
  13215. }
  13216. });
  13217. return candidatePath;
  13218. }
  13219. }
  13220. collectTransitiveReferences(set, decl) {
  13221. if (this.references.has(decl)) {
  13222. // TODO(alxhub): use proper iteration when the legacy build is removed. (#27762)
  13223. this.references.get(decl).forEach((ref) => {
  13224. if (!set.has(ref)) {
  13225. set.add(ref);
  13226. this.collectTransitiveReferences(set, ref);
  13227. }
  13228. });
  13229. }
  13230. }
  13231. }
  13232. /**
  13233. * An implementation of the `DependencyTracker` dependency graph API.
  13234. *
  13235. * The `FileDependencyGraph`'s primary job is to determine whether a given file has "logically"
  13236. * changed, given the set of physical changes (direct changes to files on disk).
  13237. *
  13238. * A file is logically changed if at least one of three conditions is met:
  13239. *
  13240. * 1. The file itself has physically changed.
  13241. * 2. One of its dependencies has physically changed.
  13242. * 3. One of its resource dependencies has physically changed.
  13243. */
  13244. class FileDependencyGraph {
  13245. nodes = new Map();
  13246. addDependency(from, on) {
  13247. this.nodeFor(from).dependsOn.add(checker.absoluteFromSourceFile(on));
  13248. }
  13249. addResourceDependency(from, resource) {
  13250. this.nodeFor(from).usesResources.add(resource);
  13251. }
  13252. recordDependencyAnalysisFailure(file) {
  13253. this.nodeFor(file).failedAnalysis = true;
  13254. }
  13255. getResourceDependencies(from) {
  13256. const node = this.nodes.get(from);
  13257. return node ? [...node.usesResources] : [];
  13258. }
  13259. /**
  13260. * Update the current dependency graph from a previous one, incorporating a set of physical
  13261. * changes.
  13262. *
  13263. * This method performs two tasks:
  13264. *
  13265. * 1. For files which have not logically changed, their dependencies from `previous` are added to
  13266. * `this` graph.
  13267. * 2. For files which have logically changed, they're added to a set of logically changed files
  13268. * which is eventually returned.
  13269. *
  13270. * In essence, for build `n`, this method performs:
  13271. *
  13272. * G(n) + L(n) = G(n - 1) + P(n)
  13273. *
  13274. * where:
  13275. *
  13276. * G(n) = the dependency graph of build `n`
  13277. * L(n) = the logically changed files from build n - 1 to build n.
  13278. * P(n) = the physically changed files from build n - 1 to build n.
  13279. */
  13280. updateWithPhysicalChanges(previous, changedTsPaths, deletedTsPaths, changedResources) {
  13281. const logicallyChanged = new Set();
  13282. for (const sf of previous.nodes.keys()) {
  13283. const sfPath = checker.absoluteFromSourceFile(sf);
  13284. const node = previous.nodeFor(sf);
  13285. if (isLogicallyChanged(sf, node, changedTsPaths, deletedTsPaths, changedResources)) {
  13286. logicallyChanged.add(sfPath);
  13287. }
  13288. else if (!deletedTsPaths.has(sfPath)) {
  13289. this.nodes.set(sf, {
  13290. dependsOn: new Set(node.dependsOn),
  13291. usesResources: new Set(node.usesResources),
  13292. failedAnalysis: false,
  13293. });
  13294. }
  13295. }
  13296. return logicallyChanged;
  13297. }
  13298. nodeFor(sf) {
  13299. if (!this.nodes.has(sf)) {
  13300. this.nodes.set(sf, {
  13301. dependsOn: new Set(),
  13302. usesResources: new Set(),
  13303. failedAnalysis: false,
  13304. });
  13305. }
  13306. return this.nodes.get(sf);
  13307. }
  13308. }
  13309. /**
  13310. * Determine whether `sf` has logically changed, given its dependencies and the set of physically
  13311. * changed files and resources.
  13312. */
  13313. function isLogicallyChanged(sf, node, changedTsPaths, deletedTsPaths, changedResources) {
  13314. // A file is assumed to have logically changed if its dependencies could not be determined
  13315. // accurately.
  13316. if (node.failedAnalysis) {
  13317. return true;
  13318. }
  13319. const sfPath = checker.absoluteFromSourceFile(sf);
  13320. // A file is logically changed if it has physically changed itself (including being deleted).
  13321. if (changedTsPaths.has(sfPath) || deletedTsPaths.has(sfPath)) {
  13322. return true;
  13323. }
  13324. // A file is logically changed if one of its dependencies has physically changed.
  13325. for (const dep of node.dependsOn) {
  13326. if (changedTsPaths.has(dep) || deletedTsPaths.has(dep)) {
  13327. return true;
  13328. }
  13329. }
  13330. // A file is logically changed if one of its resources has physically changed.
  13331. for (const dep of node.usesResources) {
  13332. if (changedResources.has(dep)) {
  13333. return true;
  13334. }
  13335. }
  13336. return false;
  13337. }
  13338. /**
  13339. * Discriminant of the `IncrementalState` union.
  13340. */
  13341. var IncrementalStateKind;
  13342. (function (IncrementalStateKind) {
  13343. IncrementalStateKind[IncrementalStateKind["Fresh"] = 0] = "Fresh";
  13344. IncrementalStateKind[IncrementalStateKind["Delta"] = 1] = "Delta";
  13345. IncrementalStateKind[IncrementalStateKind["Analyzed"] = 2] = "Analyzed";
  13346. })(IncrementalStateKind || (IncrementalStateKind = {}));
  13347. /**
  13348. * Discriminant of the `Phase` type union.
  13349. */
  13350. var PhaseKind;
  13351. (function (PhaseKind) {
  13352. PhaseKind[PhaseKind["Analysis"] = 0] = "Analysis";
  13353. PhaseKind[PhaseKind["TypeCheckAndEmit"] = 1] = "TypeCheckAndEmit";
  13354. })(PhaseKind || (PhaseKind = {}));
  13355. /**
  13356. * Manages the incremental portion of an Angular compilation, allowing for reuse of a prior
  13357. * compilation if available, and producing an output state for reuse of the current compilation in a
  13358. * future one.
  13359. */
  13360. class IncrementalCompilation {
  13361. depGraph;
  13362. versions;
  13363. step;
  13364. phase;
  13365. /**
  13366. * `IncrementalState` of this compilation if it were to be reused in a subsequent incremental
  13367. * compilation at the current moment.
  13368. *
  13369. * Exposed via the `state` read-only getter.
  13370. */
  13371. _state;
  13372. constructor(state, depGraph, versions, step) {
  13373. this.depGraph = depGraph;
  13374. this.versions = versions;
  13375. this.step = step;
  13376. this._state = state;
  13377. // The compilation begins in analysis phase.
  13378. this.phase = {
  13379. kind: PhaseKind.Analysis,
  13380. semanticDepGraphUpdater: new SemanticDepGraphUpdater(step !== null ? step.priorState.semanticDepGraph : null),
  13381. };
  13382. }
  13383. /**
  13384. * Begin a fresh `IncrementalCompilation`.
  13385. */
  13386. static fresh(program, versions) {
  13387. const state = {
  13388. kind: IncrementalStateKind.Fresh,
  13389. };
  13390. return new IncrementalCompilation(state, new FileDependencyGraph(), versions, /* reuse */ null);
  13391. }
  13392. static incremental(program, newVersions, oldProgram, oldState, modifiedResourceFiles, perf) {
  13393. return perf.inPhase(checker.PerfPhase.Reconciliation, () => {
  13394. const physicallyChangedTsFiles = new Set();
  13395. const changedResourceFiles = new Set(modifiedResourceFiles ?? []);
  13396. let priorAnalysis;
  13397. switch (oldState.kind) {
  13398. case IncrementalStateKind.Fresh:
  13399. // Since this line of program has never been successfully analyzed to begin with, treat
  13400. // this as a fresh compilation.
  13401. return IncrementalCompilation.fresh(program, newVersions);
  13402. case IncrementalStateKind.Analyzed:
  13403. // The most recent program was analyzed successfully, so we can use that as our prior
  13404. // state and don't need to consider any other deltas except changes in the most recent
  13405. // program.
  13406. priorAnalysis = oldState;
  13407. break;
  13408. case IncrementalStateKind.Delta:
  13409. // There is an ancestor program which was analyzed successfully and can be used as a
  13410. // starting point, but we need to determine what's changed since that program.
  13411. priorAnalysis = oldState.lastAnalyzedState;
  13412. for (const sfPath of oldState.physicallyChangedTsFiles) {
  13413. physicallyChangedTsFiles.add(sfPath);
  13414. }
  13415. for (const resourcePath of oldState.changedResourceFiles) {
  13416. changedResourceFiles.add(resourcePath);
  13417. }
  13418. break;
  13419. }
  13420. const oldVersions = priorAnalysis.versions;
  13421. const oldFilesArray = oldProgram.getSourceFiles().map(toOriginalSourceFile);
  13422. const oldFiles = new Set(oldFilesArray);
  13423. const deletedTsFiles = new Set(oldFilesArray.map((sf) => checker.absoluteFromSourceFile(sf)));
  13424. for (const possiblyRedirectedNewFile of program.getSourceFiles()) {
  13425. const sf = toOriginalSourceFile(possiblyRedirectedNewFile);
  13426. const sfPath = checker.absoluteFromSourceFile(sf);
  13427. // Since we're seeing a file in the incoming program with this name, it can't have been
  13428. // deleted.
  13429. deletedTsFiles.delete(sfPath);
  13430. if (oldFiles.has(sf)) {
  13431. // This source file has the same object identity as in the previous program. We need to
  13432. // determine if it's really the same file, or if it might have changed versions since the
  13433. // last program without changing its identity.
  13434. // If there's no version information available, then this is the same file, and we can
  13435. // skip it.
  13436. if (oldVersions === null || newVersions === null) {
  13437. continue;
  13438. }
  13439. // If a version is available for the file from both the prior and the current program, and
  13440. // that version is the same, then this is the same file, and we can skip it.
  13441. if (oldVersions.has(sfPath) &&
  13442. newVersions.has(sfPath) &&
  13443. oldVersions.get(sfPath) === newVersions.get(sfPath)) {
  13444. continue;
  13445. }
  13446. // Otherwise, assume that the file has changed. Either its versions didn't match, or we
  13447. // were missing version information about it on one side for some reason.
  13448. }
  13449. // Bail out if a .d.ts file changes - the semantic dep graph is not able to process such
  13450. // changes correctly yet.
  13451. if (sf.isDeclarationFile) {
  13452. return IncrementalCompilation.fresh(program, newVersions);
  13453. }
  13454. // The file has changed physically, so record it.
  13455. physicallyChangedTsFiles.add(sfPath);
  13456. }
  13457. // Remove any files that have been deleted from the list of physical changes.
  13458. for (const deletedFileName of deletedTsFiles) {
  13459. physicallyChangedTsFiles.delete(checker.resolve(deletedFileName));
  13460. }
  13461. // Use the prior dependency graph to project physical changes into a set of logically changed
  13462. // files.
  13463. const depGraph = new FileDependencyGraph();
  13464. const logicallyChangedTsFiles = depGraph.updateWithPhysicalChanges(priorAnalysis.depGraph, physicallyChangedTsFiles, deletedTsFiles, changedResourceFiles);
  13465. // Physically changed files aren't necessarily counted as logically changed by the dependency
  13466. // graph (files do not have edges to themselves), so add them to the logical changes
  13467. // explicitly.
  13468. for (const sfPath of physicallyChangedTsFiles) {
  13469. logicallyChangedTsFiles.add(sfPath);
  13470. }
  13471. // Start off in a `DeltaIncrementalState` as a delta against the previous successful analysis,
  13472. // until this compilation completes its own analysis.
  13473. const state = {
  13474. kind: IncrementalStateKind.Delta,
  13475. physicallyChangedTsFiles,
  13476. changedResourceFiles,
  13477. lastAnalyzedState: priorAnalysis,
  13478. };
  13479. return new IncrementalCompilation(state, depGraph, newVersions, {
  13480. priorState: priorAnalysis,
  13481. logicallyChangedTsFiles,
  13482. });
  13483. });
  13484. }
  13485. get state() {
  13486. return this._state;
  13487. }
  13488. get semanticDepGraphUpdater() {
  13489. if (this.phase.kind !== PhaseKind.Analysis) {
  13490. throw new Error(`AssertionError: Cannot update the SemanticDepGraph after analysis completes`);
  13491. }
  13492. return this.phase.semanticDepGraphUpdater;
  13493. }
  13494. recordSuccessfulAnalysis(traitCompiler) {
  13495. if (this.phase.kind !== PhaseKind.Analysis) {
  13496. throw new Error(`AssertionError: Incremental compilation in phase ${PhaseKind[this.phase.kind]}, expected Analysis`);
  13497. }
  13498. const { needsEmit, needsTypeCheckEmit, newGraph } = this.phase.semanticDepGraphUpdater.finalize();
  13499. // Determine the set of files which have already been emitted.
  13500. let emitted;
  13501. if (this.step === null) {
  13502. // Since there is no prior compilation, no files have yet been emitted.
  13503. emitted = new Set();
  13504. }
  13505. else {
  13506. // Begin with the files emitted by the prior successful compilation, but remove those which we
  13507. // know need to bee re-emitted.
  13508. emitted = new Set(this.step.priorState.emitted);
  13509. // Files need re-emitted if they've logically changed.
  13510. for (const sfPath of this.step.logicallyChangedTsFiles) {
  13511. emitted.delete(sfPath);
  13512. }
  13513. // Files need re-emitted if they've semantically changed.
  13514. for (const sfPath of needsEmit) {
  13515. emitted.delete(sfPath);
  13516. }
  13517. }
  13518. // Transition to a successfully analyzed compilation. At this point, a subsequent compilation
  13519. // could use this state as a starting point.
  13520. this._state = {
  13521. kind: IncrementalStateKind.Analyzed,
  13522. versions: this.versions,
  13523. depGraph: this.depGraph,
  13524. semanticDepGraph: newGraph,
  13525. priorAnalysis: traitCompiler.getAnalyzedRecords(),
  13526. typeCheckResults: null,
  13527. emitted,
  13528. };
  13529. // We now enter the type-check and emit phase of compilation.
  13530. this.phase = {
  13531. kind: PhaseKind.TypeCheckAndEmit,
  13532. needsEmit,
  13533. needsTypeCheckEmit,
  13534. };
  13535. }
  13536. recordSuccessfulTypeCheck(results) {
  13537. if (this._state.kind !== IncrementalStateKind.Analyzed) {
  13538. throw new Error(`AssertionError: Expected successfully analyzed compilation.`);
  13539. }
  13540. else if (this.phase.kind !== PhaseKind.TypeCheckAndEmit) {
  13541. throw new Error(`AssertionError: Incremental compilation in phase ${PhaseKind[this.phase.kind]}, expected TypeCheck`);
  13542. }
  13543. this._state.typeCheckResults = results;
  13544. }
  13545. recordSuccessfulEmit(sf) {
  13546. if (this._state.kind !== IncrementalStateKind.Analyzed) {
  13547. throw new Error(`AssertionError: Expected successfully analyzed compilation.`);
  13548. }
  13549. this._state.emitted.add(checker.absoluteFromSourceFile(sf));
  13550. }
  13551. priorAnalysisFor(sf) {
  13552. if (this.step === null) {
  13553. return null;
  13554. }
  13555. const sfPath = checker.absoluteFromSourceFile(sf);
  13556. // If the file has logically changed, its previous analysis cannot be reused.
  13557. if (this.step.logicallyChangedTsFiles.has(sfPath)) {
  13558. return null;
  13559. }
  13560. const priorAnalysis = this.step.priorState.priorAnalysis;
  13561. if (!priorAnalysis.has(sf)) {
  13562. return null;
  13563. }
  13564. return priorAnalysis.get(sf);
  13565. }
  13566. priorTypeCheckingResultsFor(sf) {
  13567. if (this.phase.kind !== PhaseKind.TypeCheckAndEmit) {
  13568. throw new Error(`AssertionError: Expected successfully analyzed compilation.`);
  13569. }
  13570. if (this.step === null) {
  13571. return null;
  13572. }
  13573. const sfPath = checker.absoluteFromSourceFile(sf);
  13574. // If the file has logically changed, or its template type-checking results have semantically
  13575. // changed, then past type-checking results cannot be reused.
  13576. if (this.step.logicallyChangedTsFiles.has(sfPath) ||
  13577. this.phase.needsTypeCheckEmit.has(sfPath)) {
  13578. return null;
  13579. }
  13580. // Past results also cannot be reused if they're not available.
  13581. if (this.step.priorState.typeCheckResults === null ||
  13582. !this.step.priorState.typeCheckResults.has(sfPath)) {
  13583. return null;
  13584. }
  13585. const priorResults = this.step.priorState.typeCheckResults.get(sfPath);
  13586. // If the past results relied on inlining, they're not safe for reuse.
  13587. if (priorResults.hasInlines) {
  13588. return null;
  13589. }
  13590. return priorResults;
  13591. }
  13592. safeToSkipEmit(sf) {
  13593. // If this is a fresh compilation, it's never safe to skip an emit.
  13594. if (this.step === null) {
  13595. return false;
  13596. }
  13597. const sfPath = checker.absoluteFromSourceFile(sf);
  13598. // If the file has itself logically changed, it must be emitted.
  13599. if (this.step.logicallyChangedTsFiles.has(sfPath)) {
  13600. return false;
  13601. }
  13602. if (this.phase.kind !== PhaseKind.TypeCheckAndEmit) {
  13603. throw new Error(`AssertionError: Expected successful analysis before attempting to emit files`);
  13604. }
  13605. // If during analysis it was determined that this file has semantically changed, it must be
  13606. // emitted.
  13607. if (this.phase.needsEmit.has(sfPath)) {
  13608. return false;
  13609. }
  13610. // Generally it should be safe to assume here that the file was previously emitted by the last
  13611. // successful compilation. However, as a defense-in-depth against incorrectness, we explicitly
  13612. // check that the last emit included this file, and re-emit it otherwise.
  13613. return this.step.priorState.emitted.has(sfPath);
  13614. }
  13615. }
  13616. /**
  13617. * To accurately detect whether a source file was affected during an incremental rebuild, the
  13618. * "original" source file needs to be consistently used.
  13619. *
  13620. * First, TypeScript may have created source file redirects when declaration files of the same
  13621. * version of a library are included multiple times. The non-redirected source file should be used
  13622. * to detect changes, as otherwise the redirected source files cause a mismatch when compared to
  13623. * a prior program.
  13624. *
  13625. * Second, the program that is used for template type checking may contain mutated source files, if
  13626. * inline type constructors or inline template type-check blocks had to be used. Such source files
  13627. * store their original, non-mutated source file from the original program in a symbol. For
  13628. * computing the affected files in an incremental build this original source file should be used, as
  13629. * the mutated source file would always be considered affected.
  13630. */
  13631. function toOriginalSourceFile(sf) {
  13632. const unredirectedSf = checker.toUnredirectedSourceFile(sf);
  13633. const originalFile = unredirectedSf[checker.NgOriginalFile];
  13634. if (originalFile !== undefined) {
  13635. return originalFile;
  13636. }
  13637. else {
  13638. return unredirectedSf;
  13639. }
  13640. }
  13641. /**
  13642. * A noop implementation of `IncrementalBuildStrategy` which neither returns nor tracks any
  13643. * incremental data.
  13644. */
  13645. /**
  13646. * Tracks an `IncrementalState` within the strategy itself.
  13647. */
  13648. class TrackedIncrementalBuildStrategy {
  13649. state = null;
  13650. isSet = false;
  13651. getIncrementalState() {
  13652. return this.state;
  13653. }
  13654. setIncrementalState(state) {
  13655. this.state = state;
  13656. this.isSet = true;
  13657. }
  13658. toNextBuildStrategy() {
  13659. const strategy = new TrackedIncrementalBuildStrategy();
  13660. // Only reuse state that was explicitly set via `setIncrementalState`.
  13661. strategy.state = this.isSet ? this.state : null;
  13662. return strategy;
  13663. }
  13664. }
  13665. /**
  13666. * Describes the kind of identifier found in a template.
  13667. */
  13668. var IdentifierKind;
  13669. (function (IdentifierKind) {
  13670. IdentifierKind[IdentifierKind["Property"] = 0] = "Property";
  13671. IdentifierKind[IdentifierKind["Method"] = 1] = "Method";
  13672. IdentifierKind[IdentifierKind["Element"] = 2] = "Element";
  13673. IdentifierKind[IdentifierKind["Template"] = 3] = "Template";
  13674. IdentifierKind[IdentifierKind["Attribute"] = 4] = "Attribute";
  13675. IdentifierKind[IdentifierKind["Reference"] = 5] = "Reference";
  13676. IdentifierKind[IdentifierKind["Variable"] = 6] = "Variable";
  13677. IdentifierKind[IdentifierKind["LetDeclaration"] = 7] = "LetDeclaration";
  13678. })(IdentifierKind || (IdentifierKind = {}));
  13679. /**
  13680. * Describes the absolute byte offsets of a text anchor in a source code.
  13681. */
  13682. class AbsoluteSourceSpan {
  13683. start;
  13684. end;
  13685. constructor(start, end) {
  13686. this.start = start;
  13687. this.end = end;
  13688. }
  13689. }
  13690. /**
  13691. * A context for storing indexing information about components of a program.
  13692. *
  13693. * An `IndexingContext` collects component and template analysis information from
  13694. * `DecoratorHandler`s and exposes them to be indexed.
  13695. */
  13696. class IndexingContext {
  13697. components = new Set();
  13698. /**
  13699. * Adds a component to the context.
  13700. */
  13701. addComponent(info) {
  13702. this.components.add(info);
  13703. }
  13704. }
  13705. /**
  13706. * Visits the AST of an Angular template syntax expression, finding interesting
  13707. * entities (variable references, etc.). Creates an array of Entities found in
  13708. * the expression, with the location of the Entities being relative to the
  13709. * expression.
  13710. *
  13711. * Visiting `text {{prop}}` will return
  13712. * `[TopLevelIdentifier {name: 'prop', span: {start: 7, end: 11}}]`.
  13713. */
  13714. class ExpressionVisitor extends checker.RecursiveAstVisitor {
  13715. expressionStr;
  13716. absoluteOffset;
  13717. boundTemplate;
  13718. targetToIdentifier;
  13719. identifiers = [];
  13720. errors = [];
  13721. constructor(expressionStr, absoluteOffset, boundTemplate, targetToIdentifier) {
  13722. super();
  13723. this.expressionStr = expressionStr;
  13724. this.absoluteOffset = absoluteOffset;
  13725. this.boundTemplate = boundTemplate;
  13726. this.targetToIdentifier = targetToIdentifier;
  13727. }
  13728. /**
  13729. * Returns identifiers discovered in an expression.
  13730. *
  13731. * @param ast expression AST to visit
  13732. * @param source expression AST source code
  13733. * @param absoluteOffset absolute byte offset from start of the file to the start of the AST
  13734. * source code.
  13735. * @param boundTemplate bound target of the entire template, which can be used to query for the
  13736. * entities expressions target.
  13737. * @param targetToIdentifier closure converting a template target node to its identifier.
  13738. */
  13739. static getIdentifiers(ast, source, absoluteOffset, boundTemplate, targetToIdentifier) {
  13740. const visitor = new ExpressionVisitor(source, absoluteOffset, boundTemplate, targetToIdentifier);
  13741. visitor.visit(ast);
  13742. return { identifiers: visitor.identifiers, errors: visitor.errors };
  13743. }
  13744. visit(ast) {
  13745. ast.visit(this);
  13746. }
  13747. visitPropertyRead(ast, context) {
  13748. this.visitIdentifier(ast, IdentifierKind.Property);
  13749. super.visitPropertyRead(ast, context);
  13750. }
  13751. visitPropertyWrite(ast, context) {
  13752. this.visitIdentifier(ast, IdentifierKind.Property);
  13753. super.visitPropertyWrite(ast, context);
  13754. }
  13755. /**
  13756. * Visits an identifier, adding it to the identifier store if it is useful for indexing.
  13757. *
  13758. * @param ast expression AST the identifier is in
  13759. * @param kind identifier kind
  13760. */
  13761. visitIdentifier(ast, kind) {
  13762. // The definition of a non-top-level property such as `bar` in `{{foo.bar}}` is currently
  13763. // impossible to determine by an indexer and unsupported by the indexing module.
  13764. // The indexing module also does not currently support references to identifiers declared in the
  13765. // template itself, which have a non-null expression target.
  13766. if (!(ast.receiver instanceof checker.ImplicitReceiver)) {
  13767. return;
  13768. }
  13769. // The source span of the requested AST starts at a location that is offset from the expression.
  13770. let identifierStart = ast.sourceSpan.start - this.absoluteOffset;
  13771. if (ast instanceof checker.PropertyRead || ast instanceof checker.PropertyWrite) {
  13772. // For `PropertyRead` and `PropertyWrite`, the identifier starts at the `nameSpan`, not
  13773. // necessarily the `sourceSpan`.
  13774. identifierStart = ast.nameSpan.start - this.absoluteOffset;
  13775. }
  13776. if (!this.expressionStr.substring(identifierStart).startsWith(ast.name)) {
  13777. this.errors.push(new Error(`Impossible state: "${ast.name}" not found in "${this.expressionStr}" at location ${identifierStart}`));
  13778. return;
  13779. }
  13780. // Join the relative position of the expression within a node with the absolute position
  13781. // of the node to get the absolute position of the expression in the source code.
  13782. const absoluteStart = this.absoluteOffset + identifierStart;
  13783. const span = new AbsoluteSourceSpan(absoluteStart, absoluteStart + ast.name.length);
  13784. const targetAst = this.boundTemplate.getExpressionTarget(ast);
  13785. const target = targetAst ? this.targetToIdentifier(targetAst) : null;
  13786. const identifier = {
  13787. name: ast.name,
  13788. span,
  13789. kind,
  13790. target,
  13791. };
  13792. this.identifiers.push(identifier);
  13793. }
  13794. }
  13795. /**
  13796. * Visits the AST of a parsed Angular template. Discovers and stores
  13797. * identifiers of interest, deferring to an `ExpressionVisitor` as needed.
  13798. */
  13799. let TemplateVisitor$1 = class TemplateVisitor extends checker.RecursiveVisitor$1 {
  13800. boundTemplate;
  13801. // Identifiers of interest found in the template.
  13802. identifiers = new Set();
  13803. errors = [];
  13804. // Map of targets in a template to their identifiers.
  13805. targetIdentifierCache = new Map();
  13806. // Map of elements and templates to their identifiers.
  13807. elementAndTemplateIdentifierCache = new Map();
  13808. /**
  13809. * Creates a template visitor for a bound template target. The bound target can be used when
  13810. * deferred to the expression visitor to get information about the target of an expression.
  13811. *
  13812. * @param boundTemplate bound template target
  13813. */
  13814. constructor(boundTemplate) {
  13815. super();
  13816. this.boundTemplate = boundTemplate;
  13817. }
  13818. /**
  13819. * Visits a node in the template.
  13820. *
  13821. * @param node node to visit
  13822. */
  13823. visit(node) {
  13824. node.visit(this);
  13825. }
  13826. visitAll(nodes) {
  13827. nodes.forEach((node) => this.visit(node));
  13828. }
  13829. /**
  13830. * Add an identifier for an HTML element and visit its children recursively.
  13831. *
  13832. * @param element
  13833. */
  13834. visitElement(element) {
  13835. const elementIdentifier = this.elementOrTemplateToIdentifier(element);
  13836. if (elementIdentifier !== null) {
  13837. this.identifiers.add(elementIdentifier);
  13838. }
  13839. this.visitAll(element.references);
  13840. this.visitAll(element.inputs);
  13841. this.visitAll(element.attributes);
  13842. this.visitAll(element.children);
  13843. this.visitAll(element.outputs);
  13844. }
  13845. visitTemplate(template) {
  13846. const templateIdentifier = this.elementOrTemplateToIdentifier(template);
  13847. if (templateIdentifier !== null) {
  13848. this.identifiers.add(templateIdentifier);
  13849. }
  13850. this.visitAll(template.variables);
  13851. this.visitAll(template.attributes);
  13852. this.visitAll(template.templateAttrs);
  13853. this.visitAll(template.children);
  13854. this.visitAll(template.references);
  13855. }
  13856. visitBoundAttribute(attribute) {
  13857. // If the bound attribute has no value, it cannot have any identifiers in the value expression.
  13858. if (attribute.valueSpan === undefined) {
  13859. return;
  13860. }
  13861. const { identifiers, errors } = ExpressionVisitor.getIdentifiers(attribute.value, attribute.valueSpan.toString(), attribute.valueSpan.start.offset, this.boundTemplate, this.targetToIdentifier.bind(this));
  13862. identifiers.forEach((id) => this.identifiers.add(id));
  13863. this.errors.push(...errors);
  13864. }
  13865. visitBoundEvent(attribute) {
  13866. this.visitExpression(attribute.handler);
  13867. }
  13868. visitBoundText(text) {
  13869. this.visitExpression(text.value);
  13870. }
  13871. visitReference(reference) {
  13872. const referenceIdentifier = this.targetToIdentifier(reference);
  13873. if (referenceIdentifier === null) {
  13874. return;
  13875. }
  13876. this.identifiers.add(referenceIdentifier);
  13877. }
  13878. visitVariable(variable) {
  13879. const variableIdentifier = this.targetToIdentifier(variable);
  13880. if (variableIdentifier === null) {
  13881. return;
  13882. }
  13883. this.identifiers.add(variableIdentifier);
  13884. }
  13885. visitDeferredBlock(deferred) {
  13886. deferred.visitAll(this);
  13887. }
  13888. visitDeferredBlockPlaceholder(block) {
  13889. this.visitAll(block.children);
  13890. }
  13891. visitDeferredBlockError(block) {
  13892. this.visitAll(block.children);
  13893. }
  13894. visitDeferredBlockLoading(block) {
  13895. this.visitAll(block.children);
  13896. }
  13897. visitDeferredTrigger(trigger) {
  13898. if (trigger instanceof checker.BoundDeferredTrigger) {
  13899. this.visitExpression(trigger.value);
  13900. }
  13901. }
  13902. visitSwitchBlock(block) {
  13903. this.visitExpression(block.expression);
  13904. this.visitAll(block.cases);
  13905. }
  13906. visitSwitchBlockCase(block) {
  13907. block.expression && this.visitExpression(block.expression);
  13908. this.visitAll(block.children);
  13909. }
  13910. visitForLoopBlock(block) {
  13911. block.item.visit(this);
  13912. this.visitAll(block.contextVariables);
  13913. this.visitExpression(block.expression);
  13914. this.visitAll(block.children);
  13915. block.empty?.visit(this);
  13916. }
  13917. visitForLoopBlockEmpty(block) {
  13918. this.visitAll(block.children);
  13919. }
  13920. visitIfBlock(block) {
  13921. this.visitAll(block.branches);
  13922. }
  13923. visitIfBlockBranch(block) {
  13924. block.expression && this.visitExpression(block.expression);
  13925. block.expressionAlias?.visit(this);
  13926. this.visitAll(block.children);
  13927. }
  13928. visitLetDeclaration(decl) {
  13929. const identifier = this.targetToIdentifier(decl);
  13930. if (identifier !== null) {
  13931. this.identifiers.add(identifier);
  13932. }
  13933. this.visitExpression(decl.value);
  13934. }
  13935. /** Creates an identifier for a template element or template node. */
  13936. elementOrTemplateToIdentifier(node) {
  13937. // If this node has already been seen, return the cached result.
  13938. if (this.elementAndTemplateIdentifierCache.has(node)) {
  13939. return this.elementAndTemplateIdentifierCache.get(node);
  13940. }
  13941. let name;
  13942. let kind;
  13943. if (node instanceof checker.Template) {
  13944. name = node.tagName ?? 'ng-template';
  13945. kind = IdentifierKind.Template;
  13946. }
  13947. else {
  13948. name = node.name;
  13949. kind = IdentifierKind.Element;
  13950. }
  13951. // Namespaced elements have a particular format for `node.name` that needs to be handled.
  13952. // For example, an `<svg>` element has a `node.name` of `':svg:svg'`.
  13953. // TODO(alxhub): properly handle namespaced elements
  13954. if (name.startsWith(':')) {
  13955. name = name.split(':').pop();
  13956. }
  13957. const sourceSpan = node.startSourceSpan;
  13958. // An element's or template's source span can be of the form `<element>`, `<element />`, or
  13959. // `<element></element>`. Only the selector is interesting to the indexer, so the source is
  13960. // searched for the first occurrence of the element (selector) name.
  13961. const start = this.getStartLocation(name, sourceSpan);
  13962. if (start === null) {
  13963. return null;
  13964. }
  13965. const absoluteSpan = new AbsoluteSourceSpan(start, start + name.length);
  13966. // Record the nodes's attributes, which an indexer can later traverse to see if any of them
  13967. // specify a used directive on the node.
  13968. const attributes = node.attributes.map(({ name, sourceSpan }) => {
  13969. return {
  13970. name,
  13971. span: new AbsoluteSourceSpan(sourceSpan.start.offset, sourceSpan.end.offset),
  13972. kind: IdentifierKind.Attribute,
  13973. };
  13974. });
  13975. const usedDirectives = this.boundTemplate.getDirectivesOfNode(node) || [];
  13976. const identifier = {
  13977. name,
  13978. span: absoluteSpan,
  13979. kind,
  13980. attributes: new Set(attributes),
  13981. usedDirectives: new Set(usedDirectives.map((dir) => {
  13982. return {
  13983. node: dir.ref.node,
  13984. selector: dir.selector,
  13985. };
  13986. })),
  13987. // cast b/c pre-TypeScript 3.5 unions aren't well discriminated
  13988. };
  13989. this.elementAndTemplateIdentifierCache.set(node, identifier);
  13990. return identifier;
  13991. }
  13992. /** Creates an identifier for a template reference or template variable target. */
  13993. targetToIdentifier(node) {
  13994. // If this node has already been seen, return the cached result.
  13995. if (this.targetIdentifierCache.has(node)) {
  13996. return this.targetIdentifierCache.get(node);
  13997. }
  13998. const { name, sourceSpan } = node;
  13999. const start = this.getStartLocation(name, sourceSpan);
  14000. if (start === null) {
  14001. return null;
  14002. }
  14003. const span = new AbsoluteSourceSpan(start, start + name.length);
  14004. let identifier;
  14005. if (node instanceof checker.Reference$1) {
  14006. // If the node is a reference, we care about its target. The target can be an element, a
  14007. // template, a directive applied on a template or element (in which case the directive field
  14008. // is non-null), or nothing at all.
  14009. const refTarget = this.boundTemplate.getReferenceTarget(node);
  14010. let target = null;
  14011. if (refTarget) {
  14012. let node = null;
  14013. let directive = null;
  14014. if (refTarget instanceof checker.Element$1 || refTarget instanceof checker.Template) {
  14015. node = this.elementOrTemplateToIdentifier(refTarget);
  14016. }
  14017. else {
  14018. node = this.elementOrTemplateToIdentifier(refTarget.node);
  14019. directive = refTarget.directive.ref.node;
  14020. }
  14021. if (node === null) {
  14022. return null;
  14023. }
  14024. target = {
  14025. node,
  14026. directive,
  14027. };
  14028. }
  14029. identifier = {
  14030. name,
  14031. span,
  14032. kind: IdentifierKind.Reference,
  14033. target,
  14034. };
  14035. }
  14036. else if (node instanceof checker.Variable) {
  14037. identifier = {
  14038. name,
  14039. span,
  14040. kind: IdentifierKind.Variable,
  14041. };
  14042. }
  14043. else {
  14044. identifier = {
  14045. name,
  14046. span,
  14047. kind: IdentifierKind.LetDeclaration,
  14048. };
  14049. }
  14050. this.targetIdentifierCache.set(node, identifier);
  14051. return identifier;
  14052. }
  14053. /** Gets the start location of a string in a SourceSpan */
  14054. getStartLocation(name, context) {
  14055. const localStr = context.toString();
  14056. if (!localStr.includes(name)) {
  14057. this.errors.push(new Error(`Impossible state: "${name}" not found in "${localStr}"`));
  14058. return null;
  14059. }
  14060. return context.start.offset + localStr.indexOf(name);
  14061. }
  14062. /**
  14063. * Visits a node's expression and adds its identifiers, if any, to the visitor's state.
  14064. * Only ASTs with information about the expression source and its location are visited.
  14065. *
  14066. * @param node node whose expression to visit
  14067. */
  14068. visitExpression(ast) {
  14069. // Only include ASTs that have information about their source and absolute source spans.
  14070. if (ast instanceof checker.ASTWithSource && ast.source !== null) {
  14071. // Make target to identifier mapping closure stateful to this visitor instance.
  14072. const targetToIdentifier = this.targetToIdentifier.bind(this);
  14073. const absoluteOffset = ast.sourceSpan.start;
  14074. const { identifiers, errors } = ExpressionVisitor.getIdentifiers(ast, ast.source, absoluteOffset, this.boundTemplate, targetToIdentifier);
  14075. identifiers.forEach((id) => this.identifiers.add(id));
  14076. this.errors.push(...errors);
  14077. }
  14078. }
  14079. };
  14080. /**
  14081. * Traverses a template AST and builds identifiers discovered in it.
  14082. *
  14083. * @param boundTemplate bound template target, which can be used for querying expression targets.
  14084. * @return identifiers in template
  14085. */
  14086. function getTemplateIdentifiers(boundTemplate) {
  14087. const visitor = new TemplateVisitor$1(boundTemplate);
  14088. if (boundTemplate.target.template !== undefined) {
  14089. visitor.visitAll(boundTemplate.target.template);
  14090. }
  14091. return { identifiers: visitor.identifiers, errors: visitor.errors };
  14092. }
  14093. /**
  14094. * Generates `IndexedComponent` entries from a `IndexingContext`, which has information
  14095. * about components discovered in the program registered in it.
  14096. *
  14097. * The context must be populated before `generateAnalysis` is called.
  14098. */
  14099. function generateAnalysis(context) {
  14100. const analysis = new Map();
  14101. context.components.forEach(({ declaration, selector, boundTemplate, templateMeta }) => {
  14102. const name = declaration.name.getText();
  14103. const usedComponents = new Set();
  14104. const usedDirs = boundTemplate.getUsedDirectives();
  14105. usedDirs.forEach((dir) => {
  14106. if (dir.isComponent) {
  14107. usedComponents.add(dir.ref.node);
  14108. }
  14109. });
  14110. // Get source files for the component and the template. If the template is inline, its source
  14111. // file is the component's.
  14112. const componentFile = new checker.ParseSourceFile(declaration.getSourceFile().getFullText(), declaration.getSourceFile().fileName);
  14113. let templateFile;
  14114. if (templateMeta.isInline) {
  14115. templateFile = componentFile;
  14116. }
  14117. else {
  14118. templateFile = templateMeta.file;
  14119. }
  14120. const { identifiers, errors } = getTemplateIdentifiers(boundTemplate);
  14121. analysis.set(declaration, {
  14122. name,
  14123. selector,
  14124. file: componentFile,
  14125. template: {
  14126. identifiers,
  14127. usedComponents,
  14128. isInline: templateMeta.isInline,
  14129. file: templateFile,
  14130. },
  14131. errors,
  14132. });
  14133. });
  14134. return analysis;
  14135. }
  14136. /**
  14137. * An index of all NgModules that export or re-export a given trait.
  14138. */
  14139. class NgModuleIndexImpl {
  14140. metaReader;
  14141. localReader;
  14142. constructor(metaReader, localReader) {
  14143. this.metaReader = metaReader;
  14144. this.localReader = localReader;
  14145. }
  14146. // A map from an NgModule's Class Declaration to the "main" reference to that module, aka the one
  14147. // present in the reader metadata object
  14148. ngModuleAuthoritativeReference = new Map();
  14149. // A map from a Directive/Pipe's class declaration to the class declarations of all re-exporting
  14150. // NgModules
  14151. typeToExportingModules = new Map();
  14152. indexed = false;
  14153. updateWith(cache, key, elem) {
  14154. if (cache.has(key)) {
  14155. cache.get(key).add(elem);
  14156. }
  14157. else {
  14158. const set = new Set();
  14159. set.add(elem);
  14160. cache.set(key, set);
  14161. }
  14162. }
  14163. index() {
  14164. const seenTypesWithReexports = new Map();
  14165. const locallyDeclaredDirsAndNgModules = [
  14166. ...this.localReader.getKnown(checker.MetaKind.NgModule),
  14167. ...this.localReader.getKnown(checker.MetaKind.Directive),
  14168. ];
  14169. for (const decl of locallyDeclaredDirsAndNgModules) {
  14170. // Here it's safe to create a new Reference because these are known local types.
  14171. this.indexTrait(new checker.Reference(decl), seenTypesWithReexports);
  14172. }
  14173. this.indexed = true;
  14174. }
  14175. indexTrait(ref, seenTypesWithReexports) {
  14176. if (seenTypesWithReexports.has(ref.node)) {
  14177. // We've processed this type before.
  14178. return;
  14179. }
  14180. seenTypesWithReexports.set(ref.node, new Set());
  14181. const meta = this.metaReader.getDirectiveMetadata(ref) ?? this.metaReader.getNgModuleMetadata(ref);
  14182. if (meta === null) {
  14183. return;
  14184. }
  14185. // Component + NgModule: recurse into imports
  14186. if (meta.imports !== null) {
  14187. for (const childRef of meta.imports) {
  14188. this.indexTrait(childRef, seenTypesWithReexports);
  14189. }
  14190. }
  14191. if (meta.kind === checker.MetaKind.NgModule) {
  14192. if (!this.ngModuleAuthoritativeReference.has(ref.node)) {
  14193. this.ngModuleAuthoritativeReference.set(ref.node, ref);
  14194. }
  14195. for (const childRef of meta.exports) {
  14196. this.indexTrait(childRef, seenTypesWithReexports);
  14197. const childMeta = this.metaReader.getDirectiveMetadata(childRef) ??
  14198. this.metaReader.getPipeMetadata(childRef) ??
  14199. this.metaReader.getNgModuleMetadata(childRef);
  14200. if (childMeta === null) {
  14201. continue;
  14202. }
  14203. switch (childMeta.kind) {
  14204. case checker.MetaKind.Directive:
  14205. case checker.MetaKind.Pipe:
  14206. this.updateWith(this.typeToExportingModules, childRef.node, ref.node);
  14207. this.updateWith(seenTypesWithReexports, ref.node, childRef.node);
  14208. break;
  14209. case checker.MetaKind.NgModule:
  14210. if (seenTypesWithReexports.has(childRef.node)) {
  14211. for (const reexported of seenTypesWithReexports.get(childRef.node)) {
  14212. this.updateWith(this.typeToExportingModules, reexported, ref.node);
  14213. this.updateWith(seenTypesWithReexports, ref.node, reexported);
  14214. }
  14215. }
  14216. break;
  14217. }
  14218. }
  14219. }
  14220. }
  14221. getNgModulesExporting(directiveOrPipe) {
  14222. if (!this.indexed) {
  14223. this.index();
  14224. }
  14225. if (!this.typeToExportingModules.has(directiveOrPipe)) {
  14226. return [];
  14227. }
  14228. const refs = [];
  14229. for (const ngModule of this.typeToExportingModules.get(directiveOrPipe)) {
  14230. if (this.ngModuleAuthoritativeReference.has(ngModule)) {
  14231. refs.push(this.ngModuleAuthoritativeReference.get(ngModule));
  14232. }
  14233. }
  14234. return refs;
  14235. }
  14236. }
  14237. const CSS_PREPROCESSOR_EXT = /(\.scss|\.sass|\.less|\.styl)$/;
  14238. const RESOURCE_MARKER = '.$ngresource$';
  14239. const RESOURCE_MARKER_TS = RESOURCE_MARKER + '.ts';
  14240. /**
  14241. * `ResourceLoader` which delegates to an `NgCompilerAdapter`'s resource loading methods.
  14242. */
  14243. class AdapterResourceLoader {
  14244. adapter;
  14245. options;
  14246. cache = new Map();
  14247. fetching = new Map();
  14248. lookupResolutionHost;
  14249. canPreload;
  14250. canPreprocess;
  14251. constructor(adapter, options) {
  14252. this.adapter = adapter;
  14253. this.options = options;
  14254. this.lookupResolutionHost = createLookupResolutionHost(this.adapter);
  14255. this.canPreload = !!this.adapter.readResource;
  14256. this.canPreprocess = !!this.adapter.transformResource;
  14257. }
  14258. /**
  14259. * Resolve the url of a resource relative to the file that contains the reference to it.
  14260. * The return value of this method can be used in the `load()` and `preload()` methods.
  14261. *
  14262. * Uses the provided CompilerHost if it supports mapping resources to filenames.
  14263. * Otherwise, uses a fallback mechanism that searches the module resolution candidates.
  14264. *
  14265. * @param url The, possibly relative, url of the resource.
  14266. * @param fromFile The path to the file that contains the URL of the resource.
  14267. * @returns A resolved url of resource.
  14268. * @throws An error if the resource cannot be resolved.
  14269. */
  14270. resolve(url, fromFile) {
  14271. let resolvedUrl = null;
  14272. if (this.adapter.resourceNameToFileName) {
  14273. resolvedUrl = this.adapter.resourceNameToFileName(url, fromFile, (url, fromFile) => this.fallbackResolve(url, fromFile));
  14274. }
  14275. else {
  14276. resolvedUrl = this.fallbackResolve(url, fromFile);
  14277. }
  14278. if (resolvedUrl === null) {
  14279. throw new Error(`HostResourceResolver: could not resolve ${url} in context of ${fromFile})`);
  14280. }
  14281. return resolvedUrl;
  14282. }
  14283. /**
  14284. * Preload the specified resource, asynchronously.
  14285. *
  14286. * Once the resource is loaded, its value is cached so it can be accessed synchronously via the
  14287. * `load()` method.
  14288. *
  14289. * @param resolvedUrl The url (resolved by a call to `resolve()`) of the resource to preload.
  14290. * @param context Information about the resource such as the type and containing file.
  14291. * @returns A Promise that is resolved once the resource has been loaded or `undefined` if the
  14292. * file has already been loaded.
  14293. * @throws An Error if pre-loading is not available.
  14294. */
  14295. preload(resolvedUrl, context) {
  14296. if (!this.adapter.readResource) {
  14297. throw new Error('HostResourceLoader: the CompilerHost provided does not support pre-loading resources.');
  14298. }
  14299. if (this.cache.has(resolvedUrl)) {
  14300. return undefined;
  14301. }
  14302. else if (this.fetching.has(resolvedUrl)) {
  14303. return this.fetching.get(resolvedUrl);
  14304. }
  14305. let result = this.adapter.readResource(resolvedUrl);
  14306. if (this.adapter.transformResource && context.type === 'style') {
  14307. const resourceContext = {
  14308. type: 'style',
  14309. containingFile: context.containingFile,
  14310. resourceFile: resolvedUrl,
  14311. className: context.className,
  14312. };
  14313. result = Promise.resolve(result).then(async (str) => {
  14314. const transformResult = await this.adapter.transformResource(str, resourceContext);
  14315. return transformResult === null ? str : transformResult.content;
  14316. });
  14317. }
  14318. if (typeof result === 'string') {
  14319. this.cache.set(resolvedUrl, result);
  14320. return undefined;
  14321. }
  14322. else {
  14323. const fetchCompletion = result.then((str) => {
  14324. this.fetching.delete(resolvedUrl);
  14325. this.cache.set(resolvedUrl, str);
  14326. });
  14327. this.fetching.set(resolvedUrl, fetchCompletion);
  14328. return fetchCompletion;
  14329. }
  14330. }
  14331. /**
  14332. * Preprocess the content data of an inline resource, asynchronously.
  14333. *
  14334. * @param data The existing content data from the inline resource.
  14335. * @param context Information regarding the resource such as the type and containing file.
  14336. * @returns A Promise that resolves to the processed data. If no processing occurs, the
  14337. * same data string that was passed to the function will be resolved.
  14338. */
  14339. async preprocessInline(data, context) {
  14340. if (!this.adapter.transformResource || context.type !== 'style') {
  14341. return data;
  14342. }
  14343. const transformResult = await this.adapter.transformResource(data, {
  14344. type: 'style',
  14345. containingFile: context.containingFile,
  14346. resourceFile: null,
  14347. order: context.order,
  14348. className: context.className,
  14349. });
  14350. if (transformResult === null) {
  14351. return data;
  14352. }
  14353. return transformResult.content;
  14354. }
  14355. /**
  14356. * Load the resource at the given url, synchronously.
  14357. *
  14358. * The contents of the resource may have been cached by a previous call to `preload()`.
  14359. *
  14360. * @param resolvedUrl The url (resolved by a call to `resolve()`) of the resource to load.
  14361. * @returns The contents of the resource.
  14362. */
  14363. load(resolvedUrl) {
  14364. if (this.cache.has(resolvedUrl)) {
  14365. return this.cache.get(resolvedUrl);
  14366. }
  14367. const result = this.adapter.readResource
  14368. ? this.adapter.readResource(resolvedUrl)
  14369. : this.adapter.readFile(resolvedUrl);
  14370. if (typeof result !== 'string') {
  14371. throw new Error(`HostResourceLoader: loader(${resolvedUrl}) returned a Promise`);
  14372. }
  14373. this.cache.set(resolvedUrl, result);
  14374. return result;
  14375. }
  14376. /**
  14377. * Invalidate the entire resource cache.
  14378. */
  14379. invalidate() {
  14380. this.cache.clear();
  14381. }
  14382. /**
  14383. * Attempt to resolve `url` in the context of `fromFile`, while respecting the rootDirs
  14384. * option from the tsconfig. First, normalize the file name.
  14385. */
  14386. fallbackResolve(url, fromFile) {
  14387. let candidateLocations;
  14388. if (url.startsWith('/')) {
  14389. // This path is not really an absolute path, but instead the leading '/' means that it's
  14390. // rooted in the project rootDirs. So look for it according to the rootDirs.
  14391. candidateLocations = this.getRootedCandidateLocations(url);
  14392. }
  14393. else {
  14394. // This path is a "relative" path and can be resolved as such. To make this easier on the
  14395. // downstream resolver, the './' prefix is added if missing to distinguish these paths from
  14396. // absolute node_modules paths.
  14397. if (!url.startsWith('.')) {
  14398. url = `./${url}`;
  14399. }
  14400. candidateLocations = this.getResolvedCandidateLocations(url, fromFile);
  14401. }
  14402. for (const candidate of candidateLocations) {
  14403. if (this.adapter.fileExists(candidate)) {
  14404. return candidate;
  14405. }
  14406. else if (CSS_PREPROCESSOR_EXT.test(candidate)) {
  14407. /**
  14408. * If the user specified styleUrl points to *.scss, but the Sass compiler was run before
  14409. * Angular, then the resource may have been generated as *.css. Simply try the resolution
  14410. * again.
  14411. */
  14412. const cssFallbackUrl = candidate.replace(CSS_PREPROCESSOR_EXT, '.css');
  14413. if (this.adapter.fileExists(cssFallbackUrl)) {
  14414. return cssFallbackUrl;
  14415. }
  14416. }
  14417. }
  14418. return null;
  14419. }
  14420. getRootedCandidateLocations(url) {
  14421. // The path already starts with '/', so add a '.' to make it relative.
  14422. const segment = ('.' + url);
  14423. return this.adapter.rootDirs.map((rootDir) => checker.join(rootDir, segment));
  14424. }
  14425. /**
  14426. * TypeScript provides utilities to resolve module names, but not resource files (which aren't
  14427. * a part of the ts.Program). However, TypeScript's module resolution can be used creatively
  14428. * to locate where resource files should be expected to exist. Since module resolution returns
  14429. * a list of file names that were considered, the loader can enumerate the possible locations
  14430. * for the file by setting up a module resolution for it that will fail.
  14431. */
  14432. getResolvedCandidateLocations(url, fromFile) {
  14433. const failedLookup = ts.resolveModuleName(url + RESOURCE_MARKER, fromFile, this.options, this.lookupResolutionHost);
  14434. if (failedLookup.failedLookupLocations === undefined) {
  14435. throw new Error(`Internal error: expected to find failedLookupLocations during resolution of resource '${url}' in context of ${fromFile}`);
  14436. }
  14437. return failedLookup.failedLookupLocations
  14438. .filter((candidate) => candidate.endsWith(RESOURCE_MARKER_TS))
  14439. .map((candidate) => candidate.slice(0, -RESOURCE_MARKER_TS.length));
  14440. }
  14441. }
  14442. /**
  14443. * Derives a `ts.ModuleResolutionHost` from a compiler adapter that recognizes the special resource
  14444. * marker and does not go to the filesystem for these requests, as they are known not to exist.
  14445. */
  14446. function createLookupResolutionHost(adapter) {
  14447. return {
  14448. directoryExists(directoryName) {
  14449. if (directoryName.includes(RESOURCE_MARKER)) {
  14450. return false;
  14451. }
  14452. else if (adapter.directoryExists !== undefined) {
  14453. return adapter.directoryExists(directoryName);
  14454. }
  14455. else {
  14456. // TypeScript's module resolution logic assumes that the directory exists when no host
  14457. // implementation is available.
  14458. return true;
  14459. }
  14460. },
  14461. fileExists(fileName) {
  14462. if (fileName.includes(RESOURCE_MARKER)) {
  14463. return false;
  14464. }
  14465. else {
  14466. return adapter.fileExists(fileName);
  14467. }
  14468. },
  14469. readFile: adapter.readFile.bind(adapter),
  14470. getCurrentDirectory: adapter.getCurrentDirectory.bind(adapter),
  14471. getDirectories: adapter.getDirectories?.bind(adapter),
  14472. realpath: adapter.realpath?.bind(adapter),
  14473. trace: adapter.trace?.bind(adapter),
  14474. useCaseSensitiveFileNames: typeof adapter.useCaseSensitiveFileNames === 'function'
  14475. ? adapter.useCaseSensitiveFileNames.bind(adapter)
  14476. : adapter.useCaseSensitiveFileNames,
  14477. };
  14478. }
  14479. /**
  14480. * Computes scopes for standalone components based on their `imports`, expanding imported NgModule
  14481. * scopes where necessary.
  14482. */
  14483. class StandaloneComponentScopeReader {
  14484. metaReader;
  14485. localModuleReader;
  14486. dtsModuleReader;
  14487. cache = new Map();
  14488. constructor(metaReader, localModuleReader, dtsModuleReader) {
  14489. this.metaReader = metaReader;
  14490. this.localModuleReader = localModuleReader;
  14491. this.dtsModuleReader = dtsModuleReader;
  14492. }
  14493. getScopeForComponent(clazz) {
  14494. if (!this.cache.has(clazz)) {
  14495. const clazzRef = new checker.Reference(clazz);
  14496. const clazzMeta = this.metaReader.getDirectiveMetadata(clazzRef);
  14497. if (clazzMeta === null || !clazzMeta.isComponent || !clazzMeta.isStandalone) {
  14498. this.cache.set(clazz, null);
  14499. return null;
  14500. }
  14501. // A standalone component always has itself in scope, so add `clazzMeta` during
  14502. // initialization.
  14503. const dependencies = new Set([clazzMeta]);
  14504. const deferredDependencies = new Set();
  14505. const seen = new Set([clazz]);
  14506. let isPoisoned = clazzMeta.isPoisoned;
  14507. if (clazzMeta.imports !== null) {
  14508. for (const ref of clazzMeta.imports) {
  14509. if (seen.has(ref.node)) {
  14510. continue;
  14511. }
  14512. seen.add(ref.node);
  14513. const dirMeta = this.metaReader.getDirectiveMetadata(ref);
  14514. if (dirMeta !== null) {
  14515. dependencies.add({ ...dirMeta, ref });
  14516. isPoisoned = isPoisoned || dirMeta.isPoisoned || !dirMeta.isStandalone;
  14517. continue;
  14518. }
  14519. const pipeMeta = this.metaReader.getPipeMetadata(ref);
  14520. if (pipeMeta !== null) {
  14521. dependencies.add({ ...pipeMeta, ref });
  14522. isPoisoned = isPoisoned || !pipeMeta.isStandalone;
  14523. continue;
  14524. }
  14525. const ngModuleMeta = this.metaReader.getNgModuleMetadata(ref);
  14526. if (ngModuleMeta !== null) {
  14527. dependencies.add({ ...ngModuleMeta, ref });
  14528. let ngModuleScope;
  14529. if (ref.node.getSourceFile().isDeclarationFile) {
  14530. ngModuleScope = this.dtsModuleReader.resolve(ref);
  14531. }
  14532. else {
  14533. ngModuleScope = this.localModuleReader.getScopeOfModule(ref.node);
  14534. }
  14535. if (ngModuleScope === null) {
  14536. // This technically shouldn't happen, but mark the scope as poisoned just in case.
  14537. isPoisoned = true;
  14538. continue;
  14539. }
  14540. isPoisoned = isPoisoned || ngModuleScope.exported.isPoisoned;
  14541. for (const dep of ngModuleScope.exported.dependencies) {
  14542. if (!seen.has(dep.ref.node)) {
  14543. seen.add(dep.ref.node);
  14544. dependencies.add(dep);
  14545. }
  14546. }
  14547. continue;
  14548. }
  14549. // Import was not a component/directive/pipe/NgModule, which is an error and poisons the
  14550. // scope.
  14551. isPoisoned = true;
  14552. }
  14553. }
  14554. if (clazzMeta.deferredImports !== null) {
  14555. for (const ref of clazzMeta.deferredImports) {
  14556. const dirMeta = this.metaReader.getDirectiveMetadata(ref);
  14557. if (dirMeta !== null) {
  14558. deferredDependencies.add({ ...dirMeta, ref, isExplicitlyDeferred: true });
  14559. isPoisoned = isPoisoned || dirMeta.isPoisoned || !dirMeta.isStandalone;
  14560. continue;
  14561. }
  14562. const pipeMeta = this.metaReader.getPipeMetadata(ref);
  14563. if (pipeMeta !== null) {
  14564. deferredDependencies.add({ ...pipeMeta, ref, isExplicitlyDeferred: true });
  14565. isPoisoned = isPoisoned || !pipeMeta.isStandalone;
  14566. continue;
  14567. }
  14568. }
  14569. }
  14570. this.cache.set(clazz, {
  14571. kind: checker.ComponentScopeKind.Standalone,
  14572. component: clazz,
  14573. dependencies: Array.from(dependencies),
  14574. deferredDependencies: Array.from(deferredDependencies),
  14575. isPoisoned,
  14576. schemas: clazzMeta.schemas ?? [],
  14577. });
  14578. }
  14579. return this.cache.get(clazz);
  14580. }
  14581. getRemoteScope() {
  14582. return null;
  14583. }
  14584. }
  14585. /*!
  14586. * @license
  14587. * Copyright Google LLC All Rights Reserved.
  14588. *
  14589. * Use of this source code is governed by an MIT-style license that can be
  14590. * found in the LICENSE file at https://angular.dev/license
  14591. */
  14592. /** Names of known signal functions. */
  14593. const SIGNAL_FNS = new Set([
  14594. 'WritableSignal',
  14595. 'Signal',
  14596. 'InputSignal',
  14597. 'InputSignalWithTransform',
  14598. 'ModelSignal',
  14599. ]);
  14600. /** Returns whether a symbol is a reference to a signal. */
  14601. function isSignalReference(symbol) {
  14602. return ((symbol.kind === checker.SymbolKind.Expression ||
  14603. symbol.kind === checker.SymbolKind.Variable ||
  14604. symbol.kind === checker.SymbolKind.LetDeclaration) &&
  14605. // Note that `tsType.symbol` isn't optional in the typings,
  14606. // but it appears that it can be undefined at runtime.
  14607. ((symbol.tsType.symbol !== undefined && isSignalSymbol(symbol.tsType.symbol)) ||
  14608. (symbol.tsType.aliasSymbol !== undefined && isSignalSymbol(symbol.tsType.aliasSymbol))));
  14609. }
  14610. /** Checks whether a symbol points to a signal. */
  14611. function isSignalSymbol(symbol) {
  14612. const declarations = symbol.getDeclarations();
  14613. return (declarations !== undefined &&
  14614. declarations.some((decl) => {
  14615. const fileName = decl.getSourceFile().fileName;
  14616. return ((ts.isInterfaceDeclaration(decl) || ts.isTypeAliasDeclaration(decl)) &&
  14617. SIGNAL_FNS.has(decl.name.text) &&
  14618. (fileName.includes('@angular/core') || fileName.includes('angular2/rc/packages/core')));
  14619. }));
  14620. }
  14621. /**
  14622. * This abstract class provides a base implementation for the run method.
  14623. */
  14624. class TemplateCheckWithVisitor {
  14625. /**
  14626. * When extended diagnostics were first introduced, the visitor wasn't implemented correctly
  14627. * which meant that it wasn't visiting the `templateAttrs` of structural directives (e.g.
  14628. * the expression of `*ngIf`). Fixing the issue causes a lot of internal breakages and will likely
  14629. * need to be done in a major version to avoid external breakages. This flag is used to opt out
  14630. * pre-existing diagnostics from the correct behavior until the breakages have been fixed while
  14631. * ensuring that newly-written diagnostics are correct from the beginning.
  14632. * TODO(crisbeto): remove this flag and fix the internal brekages.
  14633. */
  14634. canVisitStructuralAttributes = true;
  14635. /**
  14636. * Base implementation for run function, visits all nodes in template and calls
  14637. * `visitNode()` for each one.
  14638. */
  14639. run(ctx, component, template) {
  14640. const visitor = new TemplateVisitor(ctx, component, this);
  14641. return visitor.getDiagnostics(template);
  14642. }
  14643. }
  14644. /**
  14645. * Visits all nodes in a template (TmplAstNode and AST) and calls `visitNode` for each one.
  14646. */
  14647. class TemplateVisitor extends checker.RecursiveAstVisitor {
  14648. ctx;
  14649. component;
  14650. check;
  14651. diagnostics = [];
  14652. constructor(ctx, component, check) {
  14653. super();
  14654. this.ctx = ctx;
  14655. this.component = component;
  14656. this.check = check;
  14657. }
  14658. visit(node, context) {
  14659. this.diagnostics.push(...this.check.visitNode(this.ctx, this.component, node));
  14660. node.visit(this);
  14661. }
  14662. visitAllNodes(nodes) {
  14663. for (const node of nodes) {
  14664. this.visit(node);
  14665. }
  14666. }
  14667. visitAst(ast) {
  14668. if (ast instanceof checker.ASTWithSource) {
  14669. ast = ast.ast;
  14670. }
  14671. this.visit(ast);
  14672. }
  14673. visitElement(element) {
  14674. this.visitAllNodes(element.attributes);
  14675. this.visitAllNodes(element.inputs);
  14676. this.visitAllNodes(element.outputs);
  14677. this.visitAllNodes(element.references);
  14678. this.visitAllNodes(element.children);
  14679. }
  14680. visitTemplate(template) {
  14681. const isInlineTemplate = template.tagName === 'ng-template';
  14682. this.visitAllNodes(template.attributes);
  14683. if (isInlineTemplate) {
  14684. // Only visit input/outputs if this isn't an inline template node generated for a structural
  14685. // directive (like `<div *ngIf></div>`). These nodes would be visited when the underlying
  14686. // element of an inline template node is processed.
  14687. this.visitAllNodes(template.inputs);
  14688. this.visitAllNodes(template.outputs);
  14689. }
  14690. // TODO(crisbeto): remove this condition when deleting `canVisitStructuralAttributes`.
  14691. if (this.check.canVisitStructuralAttributes || isInlineTemplate) {
  14692. // `templateAttrs` aren't transferred over to the inner element so we always have to visit them.
  14693. this.visitAllNodes(template.templateAttrs);
  14694. }
  14695. this.visitAllNodes(template.variables);
  14696. this.visitAllNodes(template.references);
  14697. this.visitAllNodes(template.children);
  14698. }
  14699. visitContent(content) {
  14700. this.visitAllNodes(content.children);
  14701. }
  14702. visitVariable(variable) { }
  14703. visitReference(reference) { }
  14704. visitTextAttribute(attribute) { }
  14705. visitUnknownBlock(block) { }
  14706. visitBoundAttribute(attribute) {
  14707. this.visitAst(attribute.value);
  14708. }
  14709. visitBoundEvent(attribute) {
  14710. this.visitAst(attribute.handler);
  14711. }
  14712. visitText(text) { }
  14713. visitBoundText(text) {
  14714. this.visitAst(text.value);
  14715. }
  14716. visitIcu(icu) {
  14717. Object.keys(icu.vars).forEach((key) => this.visit(icu.vars[key]));
  14718. Object.keys(icu.placeholders).forEach((key) => this.visit(icu.placeholders[key]));
  14719. }
  14720. visitDeferredBlock(deferred) {
  14721. deferred.visitAll(this);
  14722. }
  14723. visitDeferredTrigger(trigger) {
  14724. if (trigger instanceof checker.BoundDeferredTrigger) {
  14725. this.visitAst(trigger.value);
  14726. }
  14727. }
  14728. visitDeferredBlockPlaceholder(block) {
  14729. this.visitAllNodes(block.children);
  14730. }
  14731. visitDeferredBlockError(block) {
  14732. this.visitAllNodes(block.children);
  14733. }
  14734. visitDeferredBlockLoading(block) {
  14735. this.visitAllNodes(block.children);
  14736. }
  14737. visitSwitchBlock(block) {
  14738. this.visitAst(block.expression);
  14739. this.visitAllNodes(block.cases);
  14740. }
  14741. visitSwitchBlockCase(block) {
  14742. block.expression && this.visitAst(block.expression);
  14743. this.visitAllNodes(block.children);
  14744. }
  14745. visitForLoopBlock(block) {
  14746. block.item.visit(this);
  14747. this.visitAllNodes(block.contextVariables);
  14748. this.visitAst(block.expression);
  14749. this.visitAllNodes(block.children);
  14750. block.empty?.visit(this);
  14751. }
  14752. visitForLoopBlockEmpty(block) {
  14753. this.visitAllNodes(block.children);
  14754. }
  14755. visitIfBlock(block) {
  14756. this.visitAllNodes(block.branches);
  14757. }
  14758. visitIfBlockBranch(block) {
  14759. block.expression && this.visitAst(block.expression);
  14760. block.expressionAlias?.visit(this);
  14761. this.visitAllNodes(block.children);
  14762. }
  14763. visitLetDeclaration(decl) {
  14764. this.visitAst(decl.value);
  14765. }
  14766. getDiagnostics(template) {
  14767. this.diagnostics = [];
  14768. this.visitAllNodes(template);
  14769. return this.diagnostics;
  14770. }
  14771. }
  14772. /** Names of known signal instance properties. */
  14773. const SIGNAL_INSTANCE_PROPERTIES = new Set(['set', 'update', 'asReadonly']);
  14774. /**
  14775. * Names of known function instance properties.
  14776. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function#instance_properties
  14777. */
  14778. const FUNCTION_INSTANCE_PROPERTIES = new Set(['name', 'length', 'prototype']);
  14779. /**
  14780. * Ensures Signals are invoked when used in template interpolations.
  14781. */
  14782. class InterpolatedSignalCheck extends TemplateCheckWithVisitor {
  14783. code = checker.ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED;
  14784. visitNode(ctx, component, node) {
  14785. // interpolations like `{{ mySignal }}`
  14786. if (node instanceof checker.Interpolation) {
  14787. return node.expressions
  14788. .filter((item) => item instanceof checker.PropertyRead)
  14789. .flatMap((item) => buildDiagnosticForSignal(ctx, item, component));
  14790. }
  14791. // bound properties like `[prop]="mySignal"`
  14792. else if (node instanceof checker.BoundAttribute) {
  14793. // we skip the check if the node is an input binding
  14794. const usedDirectives = ctx.templateTypeChecker.getUsedDirectives(component);
  14795. if (usedDirectives !== null &&
  14796. usedDirectives.some((dir) => dir.inputs.getByBindingPropertyName(node.name) !== null)) {
  14797. return [];
  14798. }
  14799. // otherwise, we check if the node is
  14800. if (
  14801. // a bound property like `[prop]="mySignal"`
  14802. (node.type === checker.BindingType.Property ||
  14803. // or a class binding like `[class.myClass]="mySignal"`
  14804. node.type === checker.BindingType.Class ||
  14805. // or a style binding like `[style.width]="mySignal"`
  14806. node.type === checker.BindingType.Style ||
  14807. // or an attribute binding like `[attr.role]="mySignal"`
  14808. node.type === checker.BindingType.Attribute ||
  14809. // or an animation binding like `[@myAnimation]="mySignal"`
  14810. node.type === checker.BindingType.Animation) &&
  14811. node.value instanceof checker.ASTWithSource &&
  14812. node.value.ast instanceof checker.PropertyRead) {
  14813. return buildDiagnosticForSignal(ctx, node.value.ast, component);
  14814. }
  14815. }
  14816. return [];
  14817. }
  14818. }
  14819. function isFunctionInstanceProperty(name) {
  14820. return FUNCTION_INSTANCE_PROPERTIES.has(name);
  14821. }
  14822. function isSignalInstanceProperty(name) {
  14823. return SIGNAL_INSTANCE_PROPERTIES.has(name);
  14824. }
  14825. function buildDiagnosticForSignal(ctx, node, component) {
  14826. // check for `{{ mySignal }}`
  14827. const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
  14828. if (symbol !== null && symbol.kind === checker.SymbolKind.Expression && isSignalReference(symbol)) {
  14829. const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(symbol.tcbLocation);
  14830. const errorString = `${node.name} is a function and should be invoked: ${node.name}()`;
  14831. const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, errorString);
  14832. return [diagnostic];
  14833. }
  14834. // check for `{{ mySignal.name }}` or `{{ mySignal.length }}` or `{{ mySignal.prototype }}`
  14835. // as these are the names of instance properties of Function, the compiler does _not_ throw an
  14836. // error.
  14837. // We also check for `{{ mySignal.set }}` or `{{ mySignal.update }}` or
  14838. // `{{ mySignal.asReadonly }}` as these are the names of instance properties of Signal
  14839. const symbolOfReceiver = ctx.templateTypeChecker.getSymbolOfNode(node.receiver, component);
  14840. if ((isFunctionInstanceProperty(node.name) || isSignalInstanceProperty(node.name)) &&
  14841. symbolOfReceiver !== null &&
  14842. symbolOfReceiver.kind === checker.SymbolKind.Expression &&
  14843. isSignalReference(symbolOfReceiver)) {
  14844. const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(symbolOfReceiver.tcbLocation);
  14845. const errorString = `${node.receiver.name} is a function and should be invoked: ${node.receiver.name}()`;
  14846. const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, errorString);
  14847. return [diagnostic];
  14848. }
  14849. return [];
  14850. }
  14851. const factory$a = {
  14852. code: checker.ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED,
  14853. name: checker.ExtendedTemplateDiagnosticName.INTERPOLATED_SIGNAL_NOT_INVOKED,
  14854. create: () => new InterpolatedSignalCheck(),
  14855. };
  14856. /**
  14857. * Ensures the two-way binding syntax is correct.
  14858. * Parentheses should be inside the brackets "[()]".
  14859. * Will return diagnostic information when "([])" is found.
  14860. */
  14861. class InvalidBananaInBoxCheck extends TemplateCheckWithVisitor {
  14862. code = checker.ErrorCode.INVALID_BANANA_IN_BOX;
  14863. visitNode(ctx, component, node) {
  14864. if (!(node instanceof checker.BoundEvent))
  14865. return [];
  14866. const name = node.name;
  14867. if (!name.startsWith('[') || !name.endsWith(']'))
  14868. return [];
  14869. const boundSyntax = node.sourceSpan.toString();
  14870. const expectedBoundSyntax = boundSyntax.replace(`(${name})`, `[(${name.slice(1, -1)})]`);
  14871. const diagnostic = ctx.makeTemplateDiagnostic(node.sourceSpan, `In the two-way binding syntax the parentheses should be inside the brackets, ex. '${expectedBoundSyntax}'.
  14872. Find more at https://angular.dev/guide/templates/two-way-binding`);
  14873. return [diagnostic];
  14874. }
  14875. }
  14876. const factory$9 = {
  14877. code: checker.ErrorCode.INVALID_BANANA_IN_BOX,
  14878. name: checker.ExtendedTemplateDiagnosticName.INVALID_BANANA_IN_BOX,
  14879. create: () => new InvalidBananaInBoxCheck(),
  14880. };
  14881. /**
  14882. * The list of known control flow directives present in the `CommonModule`,
  14883. * and their corresponding imports.
  14884. *
  14885. * Note: there is no `ngSwitch` here since it's typically used as a regular
  14886. * binding (e.g. `[ngSwitch]`), however the `ngSwitchCase` and `ngSwitchDefault`
  14887. * are used as structural directives and a warning would be generated. Once the
  14888. * `CommonModule` is included, the `ngSwitch` would also be covered.
  14889. */
  14890. const KNOWN_CONTROL_FLOW_DIRECTIVES = new Map([
  14891. ['ngIf', { directive: 'NgIf', builtIn: '@if' }],
  14892. ['ngFor', { directive: 'NgFor', builtIn: '@for' }],
  14893. ['ngSwitchCase', { directive: 'NgSwitchCase', builtIn: '@switch with @case' }],
  14894. ['ngSwitchDefault', { directive: 'NgSwitchDefault', builtIn: '@switch with @default' }],
  14895. ]);
  14896. /**
  14897. * Ensures that there are no known control flow directives (such as *ngIf and *ngFor)
  14898. * used in a template of a *standalone* component without importing a `CommonModule`. Returns
  14899. * diagnostics in case such a directive is detected.
  14900. *
  14901. * Note: this check only handles the cases when structural directive syntax is used (e.g. `*ngIf`).
  14902. * Regular binding syntax (e.g. `[ngIf]`) is handled separately in type checker and treated as a
  14903. * hard error instead of a warning.
  14904. */
  14905. class MissingControlFlowDirectiveCheck extends TemplateCheckWithVisitor {
  14906. code = checker.ErrorCode.MISSING_CONTROL_FLOW_DIRECTIVE;
  14907. run(ctx, component, template) {
  14908. const componentMetadata = ctx.templateTypeChecker.getDirectiveMetadata(component);
  14909. // Avoid running this check for non-standalone components.
  14910. if (!componentMetadata || !componentMetadata.isStandalone) {
  14911. return [];
  14912. }
  14913. return super.run(ctx, component, template);
  14914. }
  14915. visitNode(ctx, component, node) {
  14916. if (!(node instanceof checker.Template))
  14917. return [];
  14918. const controlFlowAttr = node.templateAttrs.find((attr) => KNOWN_CONTROL_FLOW_DIRECTIVES.has(attr.name));
  14919. if (!controlFlowAttr)
  14920. return [];
  14921. const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
  14922. if (symbol === null || symbol.directives.length > 0) {
  14923. return [];
  14924. }
  14925. const sourceSpan = controlFlowAttr.keySpan || controlFlowAttr.sourceSpan;
  14926. const directiveAndBuiltIn = KNOWN_CONTROL_FLOW_DIRECTIVES.get(controlFlowAttr.name);
  14927. const errorMessage = `The \`*${controlFlowAttr.name}\` directive was used in the template, ` +
  14928. `but neither the \`${directiveAndBuiltIn?.directive}\` directive nor the \`CommonModule\` was imported. ` +
  14929. `Use Angular's built-in control flow ${directiveAndBuiltIn?.builtIn} or ` +
  14930. `make sure that either the \`${directiveAndBuiltIn?.directive}\` directive or the \`CommonModule\` ` +
  14931. `is included in the \`@Component.imports\` array of this component.`;
  14932. const diagnostic = ctx.makeTemplateDiagnostic(sourceSpan, errorMessage);
  14933. return [diagnostic];
  14934. }
  14935. }
  14936. const factory$8 = {
  14937. code: checker.ErrorCode.MISSING_CONTROL_FLOW_DIRECTIVE,
  14938. name: checker.ExtendedTemplateDiagnosticName.MISSING_CONTROL_FLOW_DIRECTIVE,
  14939. create: (options) => {
  14940. return new MissingControlFlowDirectiveCheck();
  14941. },
  14942. };
  14943. /**
  14944. * Ensures a user doesn't forget to omit `let` when using ngfor.
  14945. * Will return diagnostic information when `let` is missing.
  14946. */
  14947. class MissingNgForOfLetCheck extends TemplateCheckWithVisitor {
  14948. code = checker.ErrorCode.MISSING_NGFOROF_LET;
  14949. visitNode(ctx, component, node) {
  14950. if (!(node instanceof checker.Template)) {
  14951. return [];
  14952. }
  14953. if (node.templateAttrs.length === 0) {
  14954. return [];
  14955. }
  14956. const attr = node.templateAttrs.find((x) => x.name === 'ngFor');
  14957. if (attr === undefined) {
  14958. return [];
  14959. }
  14960. if (node.variables.length > 0) {
  14961. return [];
  14962. }
  14963. const errorString = 'Your ngFor is missing a value. Did you forget to add the `let` keyword?';
  14964. const diagnostic = ctx.makeTemplateDiagnostic(attr.sourceSpan, errorString);
  14965. return [diagnostic];
  14966. }
  14967. }
  14968. const factory$7 = {
  14969. code: checker.ErrorCode.MISSING_NGFOROF_LET,
  14970. name: checker.ExtendedTemplateDiagnosticName.MISSING_NGFOROF_LET,
  14971. create: () => new MissingNgForOfLetCheck(),
  14972. };
  14973. /**
  14974. * Ensures the left side of a nullish coalescing operation is nullable.
  14975. * Returns diagnostics for the cases where the operator is useless.
  14976. * This check should only be use if `strictNullChecks` is enabled,
  14977. * otherwise it would produce inaccurate results.
  14978. */
  14979. class NullishCoalescingNotNullableCheck extends TemplateCheckWithVisitor {
  14980. canVisitStructuralAttributes = false;
  14981. code = checker.ErrorCode.NULLISH_COALESCING_NOT_NULLABLE;
  14982. visitNode(ctx, component, node) {
  14983. if (!(node instanceof checker.Binary) || node.operation !== '??')
  14984. return [];
  14985. const symbolLeft = ctx.templateTypeChecker.getSymbolOfNode(node.left, component);
  14986. if (symbolLeft === null || symbolLeft.kind !== checker.SymbolKind.Expression) {
  14987. return [];
  14988. }
  14989. const typeLeft = symbolLeft.tsType;
  14990. if (typeLeft.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
  14991. // We should not make assumptions about the any and unknown types; using a nullish coalescing
  14992. // operator is acceptable for those.
  14993. return [];
  14994. }
  14995. // If the left operand's type is different from its non-nullable self, then it must
  14996. // contain a null or undefined so this nullish coalescing operator is useful. No diagnostic to
  14997. // report.
  14998. if (typeLeft.getNonNullableType() !== typeLeft)
  14999. return [];
  15000. const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
  15001. if (symbol.kind !== checker.SymbolKind.Expression) {
  15002. return [];
  15003. }
  15004. const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(symbol.tcbLocation);
  15005. if (templateMapping === null) {
  15006. return [];
  15007. }
  15008. const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, `The left side of this nullish coalescing operation does not include 'null' or 'undefined' in its type, therefore the '??' operator can be safely removed.`);
  15009. return [diagnostic];
  15010. }
  15011. }
  15012. const factory$6 = {
  15013. code: checker.ErrorCode.NULLISH_COALESCING_NOT_NULLABLE,
  15014. name: checker.ExtendedTemplateDiagnosticName.NULLISH_COALESCING_NOT_NULLABLE,
  15015. create: (options) => {
  15016. // Require `strictNullChecks` to be enabled.
  15017. const strictNullChecks = options.strictNullChecks === undefined ? !!options.strict : !!options.strictNullChecks;
  15018. if (!strictNullChecks) {
  15019. return null;
  15020. }
  15021. return new NullishCoalescingNotNullableCheck();
  15022. },
  15023. };
  15024. /**
  15025. * Ensures the left side of an optional chain operation is nullable.
  15026. * Returns diagnostics for the cases where the operator is useless.
  15027. * This check should only be use if `strictNullChecks` is enabled,
  15028. * otherwise it would produce inaccurate results.
  15029. */
  15030. class OptionalChainNotNullableCheck extends TemplateCheckWithVisitor {
  15031. canVisitStructuralAttributes = false;
  15032. code = checker.ErrorCode.OPTIONAL_CHAIN_NOT_NULLABLE;
  15033. visitNode(ctx, component, node) {
  15034. if (!(node instanceof checker.SafeCall) &&
  15035. !(node instanceof checker.SafePropertyRead) &&
  15036. !(node instanceof checker.SafeKeyedRead))
  15037. return [];
  15038. const symbolLeft = ctx.templateTypeChecker.getSymbolOfNode(node.receiver, component);
  15039. if (symbolLeft === null || symbolLeft.kind !== checker.SymbolKind.Expression) {
  15040. return [];
  15041. }
  15042. const typeLeft = symbolLeft.tsType;
  15043. if (typeLeft.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
  15044. // We should not make assumptions about the any and unknown types; using a nullish coalescing
  15045. // operator is acceptable for those.
  15046. return [];
  15047. }
  15048. // If the left operand's type is different from its non-nullable self, then it must
  15049. // contain a null or undefined so this nullish coalescing operator is useful. No diagnostic to
  15050. // report.
  15051. if (typeLeft.getNonNullableType() !== typeLeft)
  15052. return [];
  15053. const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
  15054. if (symbol.kind !== checker.SymbolKind.Expression) {
  15055. return [];
  15056. }
  15057. const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(symbol.tcbLocation);
  15058. if (templateMapping === null) {
  15059. return [];
  15060. }
  15061. const advice = node instanceof checker.SafePropertyRead
  15062. ? `the '?.' operator can be replaced with the '.' operator`
  15063. : `the '?.' operator can be safely removed`;
  15064. const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, `The left side of this optional chain operation does not include 'null' or 'undefined' in its type, therefore ${advice}.`);
  15065. return [diagnostic];
  15066. }
  15067. }
  15068. const factory$5 = {
  15069. code: checker.ErrorCode.OPTIONAL_CHAIN_NOT_NULLABLE,
  15070. name: checker.ExtendedTemplateDiagnosticName.OPTIONAL_CHAIN_NOT_NULLABLE,
  15071. create: (options) => {
  15072. // Require `strictNullChecks` to be enabled.
  15073. const strictNullChecks = options.strictNullChecks === undefined ? !!options.strict : !!options.strictNullChecks;
  15074. if (!strictNullChecks) {
  15075. return null;
  15076. }
  15077. return new OptionalChainNotNullableCheck();
  15078. },
  15079. };
  15080. const STYLE_SUFFIXES = ['px', '%', 'em'];
  15081. /**
  15082. * A check which detects when the `.px`, `.%`, and `.em` suffixes are used with an attribute
  15083. * binding. These suffixes are only available for style bindings.
  15084. */
  15085. class SuffixNotSupportedCheck extends TemplateCheckWithVisitor {
  15086. code = checker.ErrorCode.SUFFIX_NOT_SUPPORTED;
  15087. visitNode(ctx, component, node) {
  15088. if (!(node instanceof checker.BoundAttribute))
  15089. return [];
  15090. if (!node.keySpan.toString().startsWith('attr.') ||
  15091. !STYLE_SUFFIXES.some((suffix) => node.name.endsWith(`.${suffix}`))) {
  15092. return [];
  15093. }
  15094. const diagnostic = ctx.makeTemplateDiagnostic(node.keySpan, `The ${STYLE_SUFFIXES.map((suffix) => `'.${suffix}'`).join(', ')} suffixes are only supported on style bindings.`);
  15095. return [diagnostic];
  15096. }
  15097. }
  15098. const factory$4 = {
  15099. code: checker.ErrorCode.SUFFIX_NOT_SUPPORTED,
  15100. name: checker.ExtendedTemplateDiagnosticName.SUFFIX_NOT_SUPPORTED,
  15101. create: () => new SuffixNotSupportedCheck(),
  15102. };
  15103. /**
  15104. * Ensures that attributes that have the "special" angular binding prefix (attr., style., and
  15105. * class.) are interpreted as bindings. For example, `<div attr.id="my-id"></div>` will not
  15106. * interpret this as an `AttributeBinding` to `id` but rather just a `TmplAstTextAttribute`. This
  15107. * is likely not the intent of the developer. Instead, the intent is likely to have the `id` be set
  15108. * to 'my-id'.
  15109. */
  15110. class TextAttributeNotBindingSpec extends TemplateCheckWithVisitor {
  15111. code = checker.ErrorCode.TEXT_ATTRIBUTE_NOT_BINDING;
  15112. visitNode(ctx, component, node) {
  15113. if (!(node instanceof checker.TextAttribute))
  15114. return [];
  15115. const name = node.name;
  15116. if (!name.startsWith('attr.') && !name.startsWith('style.') && !name.startsWith('class.')) {
  15117. return [];
  15118. }
  15119. let errorString;
  15120. if (name.startsWith('attr.')) {
  15121. const staticAttr = name.replace('attr.', '');
  15122. errorString = `Static attributes should be written without the 'attr.' prefix.`;
  15123. if (node.value) {
  15124. errorString += ` For example, ${staticAttr}="${node.value}".`;
  15125. }
  15126. }
  15127. else {
  15128. const expectedKey = `[${name}]`;
  15129. const expectedValue =
  15130. // true/false are special cases because we don't want to convert them to strings but
  15131. // rather maintain the logical true/false when bound.
  15132. node.value === 'true' || node.value === 'false' ? node.value : `'${node.value}'`;
  15133. errorString = 'Attribute, style, and class bindings should be enclosed with square braces.';
  15134. if (node.value) {
  15135. errorString += ` For example, '${expectedKey}="${expectedValue}"'.`;
  15136. }
  15137. }
  15138. const diagnostic = ctx.makeTemplateDiagnostic(node.sourceSpan, errorString);
  15139. return [diagnostic];
  15140. }
  15141. }
  15142. const factory$3 = {
  15143. code: checker.ErrorCode.TEXT_ATTRIBUTE_NOT_BINDING,
  15144. name: checker.ExtendedTemplateDiagnosticName.TEXT_ATTRIBUTE_NOT_BINDING,
  15145. create: () => new TextAttributeNotBindingSpec(),
  15146. };
  15147. /**
  15148. * Ensures that function in event bindings are called. For example, `<button (click)="myFunc"></button>`
  15149. * will not call `myFunc` when the button is clicked. Instead, it should be `<button (click)="myFunc()"></button>`.
  15150. * This is likely not the intent of the developer. Instead, the intent is likely to call `myFunc`.
  15151. */
  15152. class UninvokedFunctionInEventBindingSpec extends TemplateCheckWithVisitor {
  15153. code = checker.ErrorCode.UNINVOKED_FUNCTION_IN_EVENT_BINDING;
  15154. visitNode(ctx, component, node) {
  15155. // If the node is not a bound event, skip it.
  15156. if (!(node instanceof checker.BoundEvent))
  15157. return [];
  15158. // If the node is not a regular or animation event, skip it.
  15159. if (node.type !== checker.ParsedEventType.Regular && node.type !== checker.ParsedEventType.Animation)
  15160. return [];
  15161. if (!(node.handler instanceof checker.ASTWithSource))
  15162. return [];
  15163. const sourceExpressionText = node.handler.source || '';
  15164. if (node.handler.ast instanceof checker.Chain) {
  15165. // (click)="increment; decrement"
  15166. return node.handler.ast.expressions.flatMap((expression) => assertExpressionInvoked(expression, component, node, sourceExpressionText, ctx));
  15167. }
  15168. if (node.handler.ast instanceof checker.Conditional) {
  15169. // (click)="true ? increment : decrement"
  15170. const { trueExp, falseExp } = node.handler.ast;
  15171. return [trueExp, falseExp].flatMap((expression) => assertExpressionInvoked(expression, component, node, sourceExpressionText, ctx));
  15172. }
  15173. // (click)="increment"
  15174. return assertExpressionInvoked(node.handler.ast, component, node, sourceExpressionText, ctx);
  15175. }
  15176. }
  15177. /**
  15178. * Asserts that the expression is invoked.
  15179. * If the expression is a property read, and it has a call signature, a diagnostic is generated.
  15180. */
  15181. function assertExpressionInvoked(expression, component, node, expressionText, ctx) {
  15182. if (expression instanceof checker.Call || expression instanceof checker.SafeCall) {
  15183. return []; // If the method is called, skip it.
  15184. }
  15185. if (!(expression instanceof checker.PropertyRead) && !(expression instanceof checker.SafePropertyRead)) {
  15186. return []; // If the expression is not a property read, skip it.
  15187. }
  15188. const symbol = ctx.templateTypeChecker.getSymbolOfNode(expression, component);
  15189. if (symbol !== null && symbol.kind === checker.SymbolKind.Expression) {
  15190. if (symbol.tsType.getCallSignatures()?.length > 0) {
  15191. const fullExpressionText = generateStringFromExpression(expression, expressionText);
  15192. const errorString = `Function in event binding should be invoked: ${fullExpressionText}()`;
  15193. return [ctx.makeTemplateDiagnostic(node.sourceSpan, errorString)];
  15194. }
  15195. }
  15196. return [];
  15197. }
  15198. function generateStringFromExpression(expression, source) {
  15199. return source.substring(expression.span.start, expression.span.end);
  15200. }
  15201. const factory$2 = {
  15202. code: checker.ErrorCode.UNINVOKED_FUNCTION_IN_EVENT_BINDING,
  15203. name: checker.ExtendedTemplateDiagnosticName.UNINVOKED_FUNCTION_IN_EVENT_BINDING,
  15204. create: () => new UninvokedFunctionInEventBindingSpec(),
  15205. };
  15206. /**
  15207. * Ensures that all `@let` declarations in a template are used.
  15208. */
  15209. class UnusedLetDeclarationCheck extends TemplateCheckWithVisitor {
  15210. code = checker.ErrorCode.UNUSED_LET_DECLARATION;
  15211. analysis = new Map();
  15212. run(ctx, component, template) {
  15213. super.run(ctx, component, template);
  15214. const diagnostics = [];
  15215. const { allLetDeclarations, usedLetDeclarations } = this.getAnalysis(component);
  15216. for (const decl of allLetDeclarations) {
  15217. if (!usedLetDeclarations.has(decl)) {
  15218. diagnostics.push(ctx.makeTemplateDiagnostic(decl.sourceSpan, `@let ${decl.name} is declared but its value is never read.`));
  15219. }
  15220. }
  15221. this.analysis.clear();
  15222. return diagnostics;
  15223. }
  15224. visitNode(ctx, component, node) {
  15225. if (node instanceof checker.LetDeclaration) {
  15226. this.getAnalysis(component).allLetDeclarations.add(node);
  15227. }
  15228. else if (node instanceof checker.AST) {
  15229. const unwrappedNode = node instanceof checker.ASTWithSource ? node.ast : node;
  15230. const target = ctx.templateTypeChecker.getExpressionTarget(unwrappedNode, component);
  15231. if (target !== null && target instanceof checker.LetDeclaration) {
  15232. this.getAnalysis(component).usedLetDeclarations.add(target);
  15233. }
  15234. }
  15235. return [];
  15236. }
  15237. getAnalysis(node) {
  15238. if (!this.analysis.has(node)) {
  15239. this.analysis.set(node, { allLetDeclarations: new Set(), usedLetDeclarations: new Set() });
  15240. }
  15241. return this.analysis.get(node);
  15242. }
  15243. }
  15244. const factory$1 = {
  15245. code: checker.ErrorCode.UNUSED_LET_DECLARATION,
  15246. name: checker.ExtendedTemplateDiagnosticName.UNUSED_LET_DECLARATION,
  15247. create: () => new UnusedLetDeclarationCheck(),
  15248. };
  15249. const NG_SKIP_HYDRATION_ATTR_NAME = 'ngSkipHydration';
  15250. /**
  15251. * Ensures that the special attribute `ngSkipHydration` is not a binding and has no other
  15252. * value than `"true"` or an empty value.
  15253. */
  15254. class NgSkipHydrationSpec extends TemplateCheckWithVisitor {
  15255. code = checker.ErrorCode.SKIP_HYDRATION_NOT_STATIC;
  15256. visitNode(ctx, component, node) {
  15257. /** Binding should always error */
  15258. if (node instanceof checker.BoundAttribute && node.name === NG_SKIP_HYDRATION_ATTR_NAME) {
  15259. const errorString = `ngSkipHydration should not be used as a binding.`;
  15260. const diagnostic = ctx.makeTemplateDiagnostic(node.sourceSpan, errorString);
  15261. return [diagnostic];
  15262. }
  15263. /** No value, empty string or `"true"` are the only valid values */
  15264. const acceptedValues = ['true', '' /* empty string */];
  15265. if (node instanceof checker.TextAttribute &&
  15266. node.name === NG_SKIP_HYDRATION_ATTR_NAME &&
  15267. !acceptedValues.includes(node.value) &&
  15268. node.value !== undefined) {
  15269. const errorString = `ngSkipHydration only accepts "true" or "" as value or no value at all. For example 'ngSkipHydration="true"' or 'ngSkipHydration'`;
  15270. const diagnostic = ctx.makeTemplateDiagnostic(node.sourceSpan, errorString);
  15271. return [diagnostic];
  15272. }
  15273. return [];
  15274. }
  15275. }
  15276. const factory = {
  15277. code: checker.ErrorCode.SKIP_HYDRATION_NOT_STATIC,
  15278. name: checker.ExtendedTemplateDiagnosticName.SKIP_HYDRATION_NOT_STATIC,
  15279. create: () => new NgSkipHydrationSpec(),
  15280. };
  15281. /**
  15282. * A label referring to a `ts.DiagnosticCategory` or `'suppress'`, meaning the associated diagnostic
  15283. * should not be displayed at all.
  15284. *
  15285. * @publicApi
  15286. */
  15287. exports.DiagnosticCategoryLabel = void 0;
  15288. (function (DiagnosticCategoryLabel) {
  15289. /** Treat the diagnostic as a warning, don't fail the compilation. */
  15290. DiagnosticCategoryLabel["Warning"] = "warning";
  15291. /** Treat the diagnostic as a hard error, fail the compilation. */
  15292. DiagnosticCategoryLabel["Error"] = "error";
  15293. /** Ignore the diagnostic altogether. */
  15294. DiagnosticCategoryLabel["Suppress"] = "suppress";
  15295. })(exports.DiagnosticCategoryLabel || (exports.DiagnosticCategoryLabel = {}));
  15296. class ExtendedTemplateCheckerImpl {
  15297. partialCtx;
  15298. templateChecks;
  15299. constructor(templateTypeChecker, typeChecker, templateCheckFactories, options) {
  15300. this.partialCtx = { templateTypeChecker, typeChecker };
  15301. this.templateChecks = new Map();
  15302. for (const factory of templateCheckFactories) {
  15303. // Read the diagnostic category from compiler options.
  15304. const category = diagnosticLabelToCategory(options?.extendedDiagnostics?.checks?.[factory.name] ??
  15305. options?.extendedDiagnostics?.defaultCategory ??
  15306. exports.DiagnosticCategoryLabel.Warning);
  15307. // Skip the diagnostic if suppressed via compiler options.
  15308. if (category === null) {
  15309. continue;
  15310. }
  15311. // Try to create the check.
  15312. const check = factory.create(options);
  15313. // Skip the diagnostic if it was disabled due to unsupported options. For example, this can
  15314. // happen if the check requires `strictNullChecks: true` but that flag is disabled in compiler
  15315. // options.
  15316. if (check === null) {
  15317. continue;
  15318. }
  15319. // Use the check.
  15320. this.templateChecks.set(check, category);
  15321. }
  15322. }
  15323. getDiagnosticsForComponent(component) {
  15324. const template = this.partialCtx.templateTypeChecker.getTemplate(component);
  15325. // Skip checks if component has no template. This can happen if the user writes a
  15326. // `@Component()` but doesn't add the template, could happen in the language service
  15327. // when users are in the middle of typing code.
  15328. if (template === null) {
  15329. return [];
  15330. }
  15331. const diagnostics = [];
  15332. for (const [check, category] of this.templateChecks.entries()) {
  15333. const ctx = {
  15334. ...this.partialCtx,
  15335. // Wrap `templateTypeChecker.makeTemplateDiagnostic()` to implicitly provide all the known
  15336. // options.
  15337. makeTemplateDiagnostic: (span, message, relatedInformation) => {
  15338. return this.partialCtx.templateTypeChecker.makeTemplateDiagnostic(component, span, category, check.code, message, relatedInformation);
  15339. },
  15340. };
  15341. diagnostics.push(...check.run(ctx, component, template));
  15342. }
  15343. return diagnostics;
  15344. }
  15345. }
  15346. /**
  15347. * Converts a `DiagnosticCategoryLabel` to its equivalent `ts.DiagnosticCategory` or `null` if
  15348. * the label is `DiagnosticCategoryLabel.Suppress`.
  15349. */
  15350. function diagnosticLabelToCategory(label) {
  15351. switch (label) {
  15352. case exports.DiagnosticCategoryLabel.Warning:
  15353. return ts.DiagnosticCategory.Warning;
  15354. case exports.DiagnosticCategoryLabel.Error:
  15355. return ts.DiagnosticCategory.Error;
  15356. case exports.DiagnosticCategoryLabel.Suppress:
  15357. return null;
  15358. default:
  15359. return assertNever(label);
  15360. }
  15361. }
  15362. function assertNever(value) {
  15363. throw new Error(`Unexpected call to 'assertNever()' with value:\n${value}`);
  15364. }
  15365. const ALL_DIAGNOSTIC_FACTORIES = [
  15366. factory$9,
  15367. factory$6,
  15368. factory$5,
  15369. factory$8,
  15370. factory$3,
  15371. factory$7,
  15372. factory$4,
  15373. factory$a,
  15374. factory$2,
  15375. factory$1,
  15376. factory,
  15377. ];
  15378. const SUPPORTED_DIAGNOSTIC_NAMES = new Set([
  15379. checker.ExtendedTemplateDiagnosticName.CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION,
  15380. checker.ExtendedTemplateDiagnosticName.UNUSED_STANDALONE_IMPORTS,
  15381. ...ALL_DIAGNOSTIC_FACTORIES.map((factory) => factory.name),
  15382. ]);
  15383. class TemplateSemanticsCheckerImpl {
  15384. templateTypeChecker;
  15385. constructor(templateTypeChecker) {
  15386. this.templateTypeChecker = templateTypeChecker;
  15387. }
  15388. getDiagnosticsForComponent(component) {
  15389. const template = this.templateTypeChecker.getTemplate(component);
  15390. return template !== null
  15391. ? TemplateSemanticsVisitor.visit(template, component, this.templateTypeChecker)
  15392. : [];
  15393. }
  15394. }
  15395. /** Visitor that verifies the semantics of a template. */
  15396. class TemplateSemanticsVisitor extends checker.RecursiveVisitor$1 {
  15397. expressionVisitor;
  15398. constructor(expressionVisitor) {
  15399. super();
  15400. this.expressionVisitor = expressionVisitor;
  15401. }
  15402. static visit(nodes, component, templateTypeChecker) {
  15403. const diagnostics = [];
  15404. const expressionVisitor = new ExpressionsSemanticsVisitor(templateTypeChecker, component, diagnostics);
  15405. const templateVisitor = new TemplateSemanticsVisitor(expressionVisitor);
  15406. nodes.forEach((node) => node.visit(templateVisitor));
  15407. return diagnostics;
  15408. }
  15409. visitBoundEvent(event) {
  15410. super.visitBoundEvent(event);
  15411. event.handler.visit(this.expressionVisitor, event);
  15412. }
  15413. }
  15414. /** Visitor that verifies the semantics of the expressions within a template. */
  15415. class ExpressionsSemanticsVisitor extends checker.RecursiveAstVisitor {
  15416. templateTypeChecker;
  15417. component;
  15418. diagnostics;
  15419. constructor(templateTypeChecker, component, diagnostics) {
  15420. super();
  15421. this.templateTypeChecker = templateTypeChecker;
  15422. this.component = component;
  15423. this.diagnostics = diagnostics;
  15424. }
  15425. visitPropertyWrite(ast, context) {
  15426. super.visitPropertyWrite(ast, context);
  15427. this.checkForIllegalWriteInEventBinding(ast, context);
  15428. }
  15429. visitPropertyRead(ast, context) {
  15430. super.visitPropertyRead(ast, context);
  15431. this.checkForIllegalWriteInTwoWayBinding(ast, context);
  15432. }
  15433. checkForIllegalWriteInEventBinding(ast, context) {
  15434. if (!(context instanceof checker.BoundEvent) || !(ast.receiver instanceof checker.ImplicitReceiver)) {
  15435. return;
  15436. }
  15437. const target = this.templateTypeChecker.getExpressionTarget(ast, this.component);
  15438. if (target instanceof checker.Variable) {
  15439. const errorMessage = `Cannot use variable '${target.name}' as the left-hand side of an assignment expression. Template variables are read-only.`;
  15440. this.diagnostics.push(this.makeIllegalTemplateVarDiagnostic(target, context, errorMessage));
  15441. }
  15442. }
  15443. checkForIllegalWriteInTwoWayBinding(ast, context) {
  15444. // Only check top-level property reads inside two-way bindings for illegal assignments.
  15445. if (!(context instanceof checker.BoundEvent) ||
  15446. context.type !== checker.ParsedEventType.TwoWay ||
  15447. !(ast.receiver instanceof checker.ImplicitReceiver) ||
  15448. ast !== unwrapAstWithSource(context.handler)) {
  15449. return;
  15450. }
  15451. const target = this.templateTypeChecker.getExpressionTarget(ast, this.component);
  15452. const isVariable = target instanceof checker.Variable;
  15453. const isLet = target instanceof checker.LetDeclaration;
  15454. if (!isVariable && !isLet) {
  15455. return;
  15456. }
  15457. // Two-way bindings to template variables are only allowed if the variables are signals.
  15458. const symbol = this.templateTypeChecker.getSymbolOfNode(target, this.component);
  15459. if (symbol !== null && !isSignalReference(symbol)) {
  15460. let errorMessage;
  15461. if (isVariable) {
  15462. errorMessage = `Cannot use a non-signal variable '${target.name}' in a two-way binding expression. Template variables are read-only.`;
  15463. }
  15464. else {
  15465. errorMessage = `Cannot use non-signal @let declaration '${target.name}' in a two-way binding expression. @let declarations are read-only.`;
  15466. }
  15467. this.diagnostics.push(this.makeIllegalTemplateVarDiagnostic(target, context, errorMessage));
  15468. }
  15469. }
  15470. makeIllegalTemplateVarDiagnostic(target, expressionNode, errorMessage) {
  15471. const span = target instanceof checker.Variable ? target.valueSpan || target.sourceSpan : target.sourceSpan;
  15472. return this.templateTypeChecker.makeTemplateDiagnostic(this.component, expressionNode.handlerSpan, ts.DiagnosticCategory.Error, checker.ngErrorCode(checker.ErrorCode.WRITE_TO_READ_ONLY_VARIABLE), errorMessage, [
  15473. {
  15474. text: `'${target.name}' is declared here.`,
  15475. start: span.start.offset,
  15476. end: span.end.offset,
  15477. sourceFile: this.component.getSourceFile(),
  15478. },
  15479. ]);
  15480. }
  15481. }
  15482. function unwrapAstWithSource(ast) {
  15483. return ast instanceof checker.ASTWithSource ? ast.ast : ast;
  15484. }
  15485. /*!
  15486. * @license
  15487. * Copyright Google LLC All Rights Reserved.
  15488. *
  15489. * Use of this source code is governed by an MIT-style license that can be
  15490. * found in the LICENSE file at https://angular.dev/license
  15491. */
  15492. /** APIs whose usages should be checked by the rule. */
  15493. const APIS_TO_CHECK = [
  15494. checker.INPUT_INITIALIZER_FN,
  15495. checker.MODEL_INITIALIZER_FN,
  15496. ...checker.OUTPUT_INITIALIZER_FNS,
  15497. ...checker.QUERY_INITIALIZER_FNS,
  15498. ];
  15499. /**
  15500. * Rule that flags any initializer APIs that are used outside of an initializer.
  15501. */
  15502. class InitializerApiUsageRule {
  15503. reflector;
  15504. importedSymbolsTracker;
  15505. constructor(reflector, importedSymbolsTracker) {
  15506. this.reflector = reflector;
  15507. this.importedSymbolsTracker = importedSymbolsTracker;
  15508. }
  15509. shouldCheck(sourceFile) {
  15510. // Skip the traversal if there are no imports of the initializer APIs.
  15511. return APIS_TO_CHECK.some(({ functionName, owningModule }) => {
  15512. return (this.importedSymbolsTracker.hasNamedImport(sourceFile, functionName, owningModule) ||
  15513. this.importedSymbolsTracker.hasNamespaceImport(sourceFile, owningModule));
  15514. });
  15515. }
  15516. checkNode(node) {
  15517. // We only care about call expressions.
  15518. if (!ts.isCallExpression(node)) {
  15519. return null;
  15520. }
  15521. // Unwrap any parenthesized and `as` expressions since they don't affect the runtime behavior.
  15522. while (node.parent &&
  15523. (ts.isParenthesizedExpression(node.parent) || ts.isAsExpression(node.parent))) {
  15524. node = node.parent;
  15525. }
  15526. if (!node.parent || !ts.isCallExpression(node)) {
  15527. return null;
  15528. }
  15529. const identifiedInitializer = checker.tryParseInitializerApi(APIS_TO_CHECK, node, this.reflector, this.importedSymbolsTracker);
  15530. if (identifiedInitializer === null) {
  15531. return null;
  15532. }
  15533. const functionName = identifiedInitializer.api.functionName +
  15534. (identifiedInitializer.isRequired ? '.required' : '');
  15535. if (ts.isPropertyDeclaration(node.parent) && node.parent.initializer === node) {
  15536. let closestClass = node.parent;
  15537. while (closestClass && !ts.isClassDeclaration(closestClass)) {
  15538. closestClass = closestClass.parent;
  15539. }
  15540. if (closestClass && ts.isClassDeclaration(closestClass)) {
  15541. const decorators = this.reflector.getDecoratorsOfDeclaration(closestClass);
  15542. const isComponentOrDirective = decorators !== null &&
  15543. decorators.some((decorator) => {
  15544. return (decorator.import?.from === '@angular/core' &&
  15545. (decorator.name === 'Component' || decorator.name === 'Directive'));
  15546. });
  15547. return isComponentOrDirective
  15548. ? null
  15549. : checker.makeDiagnostic(checker.ErrorCode.UNSUPPORTED_INITIALIZER_API_USAGE, node, `Unsupported call to the ${functionName} function. This function can only be used as the initializer ` +
  15550. `of a property on a @Component or @Directive class.`);
  15551. }
  15552. }
  15553. return checker.makeDiagnostic(checker.ErrorCode.UNSUPPORTED_INITIALIZER_API_USAGE, node, `Unsupported call to the ${functionName} function. This function can only be called in the initializer of a class member.`);
  15554. }
  15555. }
  15556. /*!
  15557. * @license
  15558. * Copyright Google LLC All Rights Reserved.
  15559. *
  15560. * Use of this source code is governed by an MIT-style license that can be
  15561. * found in the LICENSE file at https://angular.dev/license
  15562. */
  15563. /**
  15564. * Rule that flags unused symbols inside of the `imports` array of a component.
  15565. */
  15566. class UnusedStandaloneImportsRule {
  15567. templateTypeChecker;
  15568. typeCheckingConfig;
  15569. importedSymbolsTracker;
  15570. constructor(templateTypeChecker, typeCheckingConfig, importedSymbolsTracker) {
  15571. this.templateTypeChecker = templateTypeChecker;
  15572. this.typeCheckingConfig = typeCheckingConfig;
  15573. this.importedSymbolsTracker = importedSymbolsTracker;
  15574. }
  15575. shouldCheck(sourceFile) {
  15576. return (this.typeCheckingConfig.unusedStandaloneImports !== 'suppress' &&
  15577. (this.importedSymbolsTracker.hasNamedImport(sourceFile, 'Component', '@angular/core') ||
  15578. this.importedSymbolsTracker.hasNamespaceImport(sourceFile, '@angular/core')));
  15579. }
  15580. checkNode(node) {
  15581. if (!ts.isClassDeclaration(node)) {
  15582. return null;
  15583. }
  15584. const metadata = this.templateTypeChecker.getDirectiveMetadata(node);
  15585. if (!metadata ||
  15586. !metadata.isStandalone ||
  15587. metadata.rawImports === null ||
  15588. metadata.imports === null ||
  15589. metadata.imports.length === 0) {
  15590. return null;
  15591. }
  15592. const usedDirectives = this.templateTypeChecker.getUsedDirectives(node);
  15593. const usedPipes = this.templateTypeChecker.getUsedPipes(node);
  15594. // These will be null if the component is invalid for some reason.
  15595. if (!usedDirectives || !usedPipes) {
  15596. return null;
  15597. }
  15598. const unused = this.getUnusedSymbols(metadata, new Set(usedDirectives.map((dir) => dir.ref.node)), new Set(usedPipes));
  15599. if (unused === null) {
  15600. return null;
  15601. }
  15602. const propertyAssignment = closestNode(metadata.rawImports, ts.isPropertyAssignment);
  15603. const category = this.typeCheckingConfig.unusedStandaloneImports === 'error'
  15604. ? ts.DiagnosticCategory.Error
  15605. : ts.DiagnosticCategory.Warning;
  15606. if (unused.length === metadata.imports.length && propertyAssignment !== null) {
  15607. return checker.makeDiagnostic(checker.ErrorCode.UNUSED_STANDALONE_IMPORTS, propertyAssignment.name, 'All imports are unused', undefined, category);
  15608. }
  15609. return unused.map((ref) => {
  15610. const diagnosticNode = ref.getIdentityInExpression(metadata.rawImports) ||
  15611. ref.getIdentityIn(node.getSourceFile()) ||
  15612. metadata.rawImports;
  15613. return checker.makeDiagnostic(checker.ErrorCode.UNUSED_STANDALONE_IMPORTS, diagnosticNode, `${ref.node.name.text} is not used within the template of ${metadata.name}`, undefined, category);
  15614. });
  15615. }
  15616. getUnusedSymbols(metadata, usedDirectives, usedPipes) {
  15617. const { imports, rawImports } = metadata;
  15618. if (imports === null || rawImports === null) {
  15619. return null;
  15620. }
  15621. let unused = null;
  15622. for (const current of imports) {
  15623. const currentNode = current.node;
  15624. const dirMeta = this.templateTypeChecker.getDirectiveMetadata(currentNode);
  15625. if (dirMeta !== null) {
  15626. if (dirMeta.isStandalone &&
  15627. !usedDirectives.has(currentNode) &&
  15628. !this.isPotentialSharedReference(current, rawImports)) {
  15629. unused ??= [];
  15630. unused.push(current);
  15631. }
  15632. continue;
  15633. }
  15634. const pipeMeta = this.templateTypeChecker.getPipeMetadata(currentNode);
  15635. if (pipeMeta !== null &&
  15636. pipeMeta.isStandalone &&
  15637. !usedPipes.has(pipeMeta.name) &&
  15638. !this.isPotentialSharedReference(current, rawImports)) {
  15639. unused ??= [];
  15640. unused.push(current);
  15641. }
  15642. }
  15643. return unused;
  15644. }
  15645. /**
  15646. * Determines if an import reference *might* be coming from a shared imports array.
  15647. * @param reference Reference to be checked.
  15648. * @param rawImports AST node that defines the `imports` array.
  15649. */
  15650. isPotentialSharedReference(reference, rawImports) {
  15651. // If the reference is defined directly in the `imports` array, it cannot be shared.
  15652. if (reference.getIdentityInExpression(rawImports) !== null) {
  15653. return false;
  15654. }
  15655. // The reference might be shared if it comes from an exported array. If the variable is local
  15656. /// to the file, then it likely isn't shared. Note that this has the potential for false
  15657. // positives if a non-exported array of imports is shared between components in the same
  15658. // file. This scenario is unlikely and even if we report the diagnostic for it, it would be
  15659. // okay since the user only has to refactor components within the same file, rather than the
  15660. // entire application.
  15661. let current = reference.getIdentityIn(rawImports.getSourceFile());
  15662. while (current !== null) {
  15663. if (ts.isVariableStatement(current)) {
  15664. return !!current.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
  15665. }
  15666. // `Node.parent` can be undefined, but the TS types don't reflect it.
  15667. // Coerce to null so the value is consitent with the type.
  15668. current = current.parent ?? null;
  15669. }
  15670. // Otherwise the reference likely comes from an imported
  15671. // symbol like an array of shared common components.
  15672. return true;
  15673. }
  15674. }
  15675. /** Gets the closest parent node of a certain type. */
  15676. function closestNode(start, predicate) {
  15677. let current = start.parent;
  15678. while (current) {
  15679. if (predicate(current)) {
  15680. return current;
  15681. }
  15682. else {
  15683. current = current.parent;
  15684. }
  15685. }
  15686. return null;
  15687. }
  15688. /*!
  15689. * @license
  15690. * Copyright Google LLC All Rights Reserved.
  15691. *
  15692. * Use of this source code is governed by an MIT-style license that can be
  15693. * found in the LICENSE file at https://angular.dev/license
  15694. */
  15695. /**
  15696. * Validates that TypeScript files match a specific set of rules set by the Angular compiler.
  15697. */
  15698. class SourceFileValidator {
  15699. rules;
  15700. constructor(reflector, importedSymbolsTracker, templateTypeChecker, typeCheckingConfig) {
  15701. this.rules = [new InitializerApiUsageRule(reflector, importedSymbolsTracker)];
  15702. {
  15703. this.rules.push(new UnusedStandaloneImportsRule(templateTypeChecker, typeCheckingConfig, importedSymbolsTracker));
  15704. }
  15705. }
  15706. /**
  15707. * Gets the diagnostics for a specific file, or null if the file is valid.
  15708. * @param sourceFile File to be checked.
  15709. */
  15710. getDiagnosticsForFile(sourceFile) {
  15711. if (sourceFile.isDeclarationFile || sourceFile.fileName.endsWith('.ngtypecheck.ts')) {
  15712. return null;
  15713. }
  15714. let rulesToRun = null;
  15715. for (const rule of this.rules) {
  15716. if (rule.shouldCheck(sourceFile)) {
  15717. rulesToRun ??= [];
  15718. rulesToRun.push(rule);
  15719. }
  15720. }
  15721. if (rulesToRun === null) {
  15722. return null;
  15723. }
  15724. let fileDiagnostics = null;
  15725. sourceFile.forEachChild(function walk(node) {
  15726. // Note: non-null assertion is here because of g3.
  15727. for (const rule of rulesToRun) {
  15728. const nodeDiagnostics = rule.checkNode(node);
  15729. if (nodeDiagnostics !== null) {
  15730. fileDiagnostics ??= [];
  15731. if (Array.isArray(nodeDiagnostics)) {
  15732. fileDiagnostics.push(...nodeDiagnostics);
  15733. }
  15734. else {
  15735. fileDiagnostics.push(nodeDiagnostics);
  15736. }
  15737. }
  15738. }
  15739. node.forEachChild(walk);
  15740. });
  15741. return fileDiagnostics;
  15742. }
  15743. }
  15744. function coreHasSymbol(program, symbol) {
  15745. const checker = program.getTypeChecker();
  15746. for (const sf of program.getSourceFiles().filter(isMaybeCore)) {
  15747. const sym = checker.getSymbolAtLocation(sf);
  15748. if (sym === undefined || sym.exports === undefined) {
  15749. continue;
  15750. }
  15751. if (!sym.exports.has('ɵɵtemplate')) {
  15752. // This is not @angular/core.
  15753. continue;
  15754. }
  15755. return sym.exports.has(symbol.name);
  15756. }
  15757. // No @angular/core file found, so we have no information.
  15758. return null;
  15759. }
  15760. function isMaybeCore(sf) {
  15761. return (sf.isDeclarationFile &&
  15762. sf.fileName.includes('@angular/core') &&
  15763. sf.fileName.endsWith('index.d.ts'));
  15764. }
  15765. function getDefaultExportFromCjs (x) {
  15766. return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
  15767. }
  15768. var re = {exports: {}};
  15769. var constants;
  15770. var hasRequiredConstants;
  15771. function requireConstants () {
  15772. if (hasRequiredConstants) return constants;
  15773. hasRequiredConstants = 1;
  15774. // Note: this is the semver.org version of the spec that it implements
  15775. // Not necessarily the package version of this code.
  15776. const SEMVER_SPEC_VERSION = '2.0.0';
  15777. const MAX_LENGTH = 256;
  15778. const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER ||
  15779. /* istanbul ignore next */ 9007199254740991;
  15780. // Max safe segment length for coercion.
  15781. const MAX_SAFE_COMPONENT_LENGTH = 16;
  15782. // Max safe length for a build identifier. The max length minus 6 characters for
  15783. // the shortest version with a build 0.0.0+BUILD.
  15784. const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6;
  15785. const RELEASE_TYPES = [
  15786. 'major',
  15787. 'premajor',
  15788. 'minor',
  15789. 'preminor',
  15790. 'patch',
  15791. 'prepatch',
  15792. 'prerelease',
  15793. ];
  15794. constants = {
  15795. MAX_LENGTH,
  15796. MAX_SAFE_COMPONENT_LENGTH,
  15797. MAX_SAFE_BUILD_LENGTH,
  15798. MAX_SAFE_INTEGER,
  15799. RELEASE_TYPES,
  15800. SEMVER_SPEC_VERSION,
  15801. FLAG_INCLUDE_PRERELEASE: 0b001,
  15802. FLAG_LOOSE: 0b010,
  15803. };
  15804. return constants;
  15805. }
  15806. var debug_1;
  15807. var hasRequiredDebug;
  15808. function requireDebug () {
  15809. if (hasRequiredDebug) return debug_1;
  15810. hasRequiredDebug = 1;
  15811. const debug = (
  15812. typeof process === 'object' &&
  15813. process.env &&
  15814. process.env.NODE_DEBUG &&
  15815. /\bsemver\b/i.test(process.env.NODE_DEBUG)
  15816. ) ? (...args) => console.error('SEMVER', ...args)
  15817. : () => {};
  15818. debug_1 = debug;
  15819. return debug_1;
  15820. }
  15821. var hasRequiredRe;
  15822. function requireRe () {
  15823. if (hasRequiredRe) return re.exports;
  15824. hasRequiredRe = 1;
  15825. (function (module, exports) {
  15826. const {
  15827. MAX_SAFE_COMPONENT_LENGTH,
  15828. MAX_SAFE_BUILD_LENGTH,
  15829. MAX_LENGTH,
  15830. } = requireConstants();
  15831. const debug = requireDebug();
  15832. exports = module.exports = {};
  15833. // The actual regexps go on exports.re
  15834. const re = exports.re = [];
  15835. const safeRe = exports.safeRe = [];
  15836. const src = exports.src = [];
  15837. const safeSrc = exports.safeSrc = [];
  15838. const t = exports.t = {};
  15839. let R = 0;
  15840. const LETTERDASHNUMBER = '[a-zA-Z0-9-]';
  15841. // Replace some greedy regex tokens to prevent regex dos issues. These regex are
  15842. // used internally via the safeRe object since all inputs in this library get
  15843. // normalized first to trim and collapse all extra whitespace. The original
  15844. // regexes are exported for userland consumption and lower level usage. A
  15845. // future breaking change could export the safer regex only with a note that
  15846. // all input should have extra whitespace removed.
  15847. const safeRegexReplacements = [
  15848. ['\\s', 1],
  15849. ['\\d', MAX_LENGTH],
  15850. [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH],
  15851. ];
  15852. const makeSafeRegex = (value) => {
  15853. for (const [token, max] of safeRegexReplacements) {
  15854. value = value
  15855. .split(`${token}*`).join(`${token}{0,${max}}`)
  15856. .split(`${token}+`).join(`${token}{1,${max}}`);
  15857. }
  15858. return value
  15859. };
  15860. const createToken = (name, value, isGlobal) => {
  15861. const safe = makeSafeRegex(value);
  15862. const index = R++;
  15863. debug(name, index, value);
  15864. t[name] = index;
  15865. src[index] = value;
  15866. safeSrc[index] = safe;
  15867. re[index] = new RegExp(value, isGlobal ? 'g' : undefined);
  15868. safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined);
  15869. };
  15870. // The following Regular Expressions can be used for tokenizing,
  15871. // validating, and parsing SemVer version strings.
  15872. // ## Numeric Identifier
  15873. // A single `0`, or a non-zero digit followed by zero or more digits.
  15874. createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*');
  15875. createToken('NUMERICIDENTIFIERLOOSE', '\\d+');
  15876. // ## Non-numeric Identifier
  15877. // Zero or more digits, followed by a letter or hyphen, and then zero or
  15878. // more letters, digits, or hyphens.
  15879. createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`);
  15880. // ## Main Version
  15881. // Three dot-separated numeric identifiers.
  15882. createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` +
  15883. `(${src[t.NUMERICIDENTIFIER]})\\.` +
  15884. `(${src[t.NUMERICIDENTIFIER]})`);
  15885. createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` +
  15886. `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` +
  15887. `(${src[t.NUMERICIDENTIFIERLOOSE]})`);
  15888. // ## Pre-release Version Identifier
  15889. // A numeric identifier, or a non-numeric identifier.
  15890. createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER]
  15891. }|${src[t.NONNUMERICIDENTIFIER]})`);
  15892. createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE]
  15893. }|${src[t.NONNUMERICIDENTIFIER]})`);
  15894. // ## Pre-release Version
  15895. // Hyphen, followed by one or more dot-separated pre-release version
  15896. // identifiers.
  15897. createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER]
  15898. }(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`);
  15899. createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE]
  15900. }(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`);
  15901. // ## Build Metadata Identifier
  15902. // Any combination of digits, letters, or hyphens.
  15903. createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`);
  15904. // ## Build Metadata
  15905. // Plus sign, followed by one or more period-separated build metadata
  15906. // identifiers.
  15907. createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER]
  15908. }(?:\\.${src[t.BUILDIDENTIFIER]})*))`);
  15909. // ## Full Version String
  15910. // A main version, followed optionally by a pre-release version and
  15911. // build metadata.
  15912. // Note that the only major, minor, patch, and pre-release sections of
  15913. // the version string are capturing groups. The build metadata is not a
  15914. // capturing group, because it should not ever be used in version
  15915. // comparison.
  15916. createToken('FULLPLAIN', `v?${src[t.MAINVERSION]
  15917. }${src[t.PRERELEASE]}?${
  15918. src[t.BUILD]}?`);
  15919. createToken('FULL', `^${src[t.FULLPLAIN]}$`);
  15920. // like full, but allows v1.2.3 and =1.2.3, which people do sometimes.
  15921. // also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty
  15922. // common in the npm registry.
  15923. createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE]
  15924. }${src[t.PRERELEASELOOSE]}?${
  15925. src[t.BUILD]}?`);
  15926. createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`);
  15927. createToken('GTLT', '((?:<|>)?=?)');
  15928. // Something like "2.*" or "1.2.x".
  15929. // Note that "x.x" is a valid xRange identifer, meaning "any version"
  15930. // Only the first item is strictly required.
  15931. createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`);
  15932. createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`);
  15933. createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` +
  15934. `(?:\\.(${src[t.XRANGEIDENTIFIER]})` +
  15935. `(?:\\.(${src[t.XRANGEIDENTIFIER]})` +
  15936. `(?:${src[t.PRERELEASE]})?${
  15937. src[t.BUILD]}?` +
  15938. `)?)?`);
  15939. createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` +
  15940. `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` +
  15941. `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` +
  15942. `(?:${src[t.PRERELEASELOOSE]})?${
  15943. src[t.BUILD]}?` +
  15944. `)?)?`);
  15945. createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`);
  15946. createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`);
  15947. // Coercion.
  15948. // Extract anything that could conceivably be a part of a valid semver
  15949. createToken('COERCEPLAIN', `${'(^|[^\\d])' +
  15950. '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` +
  15951. `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` +
  15952. `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`);
  15953. createToken('COERCE', `${src[t.COERCEPLAIN]}(?:$|[^\\d])`);
  15954. createToken('COERCEFULL', src[t.COERCEPLAIN] +
  15955. `(?:${src[t.PRERELEASE]})?` +
  15956. `(?:${src[t.BUILD]})?` +
  15957. `(?:$|[^\\d])`);
  15958. createToken('COERCERTL', src[t.COERCE], true);
  15959. createToken('COERCERTLFULL', src[t.COERCEFULL], true);
  15960. // Tilde ranges.
  15961. // Meaning is "reasonably at or greater than"
  15962. createToken('LONETILDE', '(?:~>?)');
  15963. createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true);
  15964. exports.tildeTrimReplace = '$1~';
  15965. createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`);
  15966. createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`);
  15967. // Caret ranges.
  15968. // Meaning is "at least and backwards compatible with"
  15969. createToken('LONECARET', '(?:\\^)');
  15970. createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true);
  15971. exports.caretTrimReplace = '$1^';
  15972. createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`);
  15973. createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`);
  15974. // A simple gt/lt/eq thing, or just "" to indicate "any version"
  15975. createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`);
  15976. createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`);
  15977. // An expression to strip any whitespace between the gtlt and the thing
  15978. // it modifies, so that `> 1.2.3` ==> `>1.2.3`
  15979. createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT]
  15980. }\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true);
  15981. exports.comparatorTrimReplace = '$1$2$3';
  15982. // Something like `1.2.3 - 1.2.4`
  15983. // Note that these all use the loose form, because they'll be
  15984. // checked against either the strict or loose comparator form
  15985. // later.
  15986. createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` +
  15987. `\\s+-\\s+` +
  15988. `(${src[t.XRANGEPLAIN]})` +
  15989. `\\s*$`);
  15990. createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` +
  15991. `\\s+-\\s+` +
  15992. `(${src[t.XRANGEPLAINLOOSE]})` +
  15993. `\\s*$`);
  15994. // Star ranges basically just allow anything at all.
  15995. createToken('STAR', '(<|>)?=?\\s*\\*');
  15996. // >=0.0.0 is like a star
  15997. createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$');
  15998. createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$');
  15999. } (re, re.exports));
  16000. return re.exports;
  16001. }
  16002. var parseOptions_1;
  16003. var hasRequiredParseOptions;
  16004. function requireParseOptions () {
  16005. if (hasRequiredParseOptions) return parseOptions_1;
  16006. hasRequiredParseOptions = 1;
  16007. // parse out just the options we care about
  16008. const looseOption = Object.freeze({ loose: true });
  16009. const emptyOpts = Object.freeze({ });
  16010. const parseOptions = options => {
  16011. if (!options) {
  16012. return emptyOpts
  16013. }
  16014. if (typeof options !== 'object') {
  16015. return looseOption
  16016. }
  16017. return options
  16018. };
  16019. parseOptions_1 = parseOptions;
  16020. return parseOptions_1;
  16021. }
  16022. var identifiers;
  16023. var hasRequiredIdentifiers;
  16024. function requireIdentifiers () {
  16025. if (hasRequiredIdentifiers) return identifiers;
  16026. hasRequiredIdentifiers = 1;
  16027. const numeric = /^[0-9]+$/;
  16028. const compareIdentifiers = (a, b) => {
  16029. const anum = numeric.test(a);
  16030. const bnum = numeric.test(b);
  16031. if (anum && bnum) {
  16032. a = +a;
  16033. b = +b;
  16034. }
  16035. return a === b ? 0
  16036. : (anum && !bnum) ? -1
  16037. : (bnum && !anum) ? 1
  16038. : a < b ? -1
  16039. : 1
  16040. };
  16041. const rcompareIdentifiers = (a, b) => compareIdentifiers(b, a);
  16042. identifiers = {
  16043. compareIdentifiers,
  16044. rcompareIdentifiers,
  16045. };
  16046. return identifiers;
  16047. }
  16048. var semver$2;
  16049. var hasRequiredSemver$1;
  16050. function requireSemver$1 () {
  16051. if (hasRequiredSemver$1) return semver$2;
  16052. hasRequiredSemver$1 = 1;
  16053. const debug = requireDebug();
  16054. const { MAX_LENGTH, MAX_SAFE_INTEGER } = requireConstants();
  16055. const { safeRe: re, safeSrc: src, t } = requireRe();
  16056. const parseOptions = requireParseOptions();
  16057. const { compareIdentifiers } = requireIdentifiers();
  16058. class SemVer {
  16059. constructor (version, options) {
  16060. options = parseOptions(options);
  16061. if (version instanceof SemVer) {
  16062. if (version.loose === !!options.loose &&
  16063. version.includePrerelease === !!options.includePrerelease) {
  16064. return version
  16065. } else {
  16066. version = version.version;
  16067. }
  16068. } else if (typeof version !== 'string') {
  16069. throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`)
  16070. }
  16071. if (version.length > MAX_LENGTH) {
  16072. throw new TypeError(
  16073. `version is longer than ${MAX_LENGTH} characters`
  16074. )
  16075. }
  16076. debug('SemVer', version, options);
  16077. this.options = options;
  16078. this.loose = !!options.loose;
  16079. // this isn't actually relevant for versions, but keep it so that we
  16080. // don't run into trouble passing this.options around.
  16081. this.includePrerelease = !!options.includePrerelease;
  16082. const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL]);
  16083. if (!m) {
  16084. throw new TypeError(`Invalid Version: ${version}`)
  16085. }
  16086. this.raw = version;
  16087. // these are actually numbers
  16088. this.major = +m[1];
  16089. this.minor = +m[2];
  16090. this.patch = +m[3];
  16091. if (this.major > MAX_SAFE_INTEGER || this.major < 0) {
  16092. throw new TypeError('Invalid major version')
  16093. }
  16094. if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) {
  16095. throw new TypeError('Invalid minor version')
  16096. }
  16097. if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) {
  16098. throw new TypeError('Invalid patch version')
  16099. }
  16100. // numberify any prerelease numeric ids
  16101. if (!m[4]) {
  16102. this.prerelease = [];
  16103. } else {
  16104. this.prerelease = m[4].split('.').map((id) => {
  16105. if (/^[0-9]+$/.test(id)) {
  16106. const num = +id;
  16107. if (num >= 0 && num < MAX_SAFE_INTEGER) {
  16108. return num
  16109. }
  16110. }
  16111. return id
  16112. });
  16113. }
  16114. this.build = m[5] ? m[5].split('.') : [];
  16115. this.format();
  16116. }
  16117. format () {
  16118. this.version = `${this.major}.${this.minor}.${this.patch}`;
  16119. if (this.prerelease.length) {
  16120. this.version += `-${this.prerelease.join('.')}`;
  16121. }
  16122. return this.version
  16123. }
  16124. toString () {
  16125. return this.version
  16126. }
  16127. compare (other) {
  16128. debug('SemVer.compare', this.version, this.options, other);
  16129. if (!(other instanceof SemVer)) {
  16130. if (typeof other === 'string' && other === this.version) {
  16131. return 0
  16132. }
  16133. other = new SemVer(other, this.options);
  16134. }
  16135. if (other.version === this.version) {
  16136. return 0
  16137. }
  16138. return this.compareMain(other) || this.comparePre(other)
  16139. }
  16140. compareMain (other) {
  16141. if (!(other instanceof SemVer)) {
  16142. other = new SemVer(other, this.options);
  16143. }
  16144. return (
  16145. compareIdentifiers(this.major, other.major) ||
  16146. compareIdentifiers(this.minor, other.minor) ||
  16147. compareIdentifiers(this.patch, other.patch)
  16148. )
  16149. }
  16150. comparePre (other) {
  16151. if (!(other instanceof SemVer)) {
  16152. other = new SemVer(other, this.options);
  16153. }
  16154. // NOT having a prerelease is > having one
  16155. if (this.prerelease.length && !other.prerelease.length) {
  16156. return -1
  16157. } else if (!this.prerelease.length && other.prerelease.length) {
  16158. return 1
  16159. } else if (!this.prerelease.length && !other.prerelease.length) {
  16160. return 0
  16161. }
  16162. let i = 0;
  16163. do {
  16164. const a = this.prerelease[i];
  16165. const b = other.prerelease[i];
  16166. debug('prerelease compare', i, a, b);
  16167. if (a === undefined && b === undefined) {
  16168. return 0
  16169. } else if (b === undefined) {
  16170. return 1
  16171. } else if (a === undefined) {
  16172. return -1
  16173. } else if (a === b) {
  16174. continue
  16175. } else {
  16176. return compareIdentifiers(a, b)
  16177. }
  16178. } while (++i)
  16179. }
  16180. compareBuild (other) {
  16181. if (!(other instanceof SemVer)) {
  16182. other = new SemVer(other, this.options);
  16183. }
  16184. let i = 0;
  16185. do {
  16186. const a = this.build[i];
  16187. const b = other.build[i];
  16188. debug('build compare', i, a, b);
  16189. if (a === undefined && b === undefined) {
  16190. return 0
  16191. } else if (b === undefined) {
  16192. return 1
  16193. } else if (a === undefined) {
  16194. return -1
  16195. } else if (a === b) {
  16196. continue
  16197. } else {
  16198. return compareIdentifiers(a, b)
  16199. }
  16200. } while (++i)
  16201. }
  16202. // preminor will bump the version up to the next minor release, and immediately
  16203. // down to pre-release. premajor and prepatch work the same way.
  16204. inc (release, identifier, identifierBase) {
  16205. if (release.startsWith('pre')) {
  16206. if (!identifier && identifierBase === false) {
  16207. throw new Error('invalid increment argument: identifier is empty')
  16208. }
  16209. // Avoid an invalid semver results
  16210. if (identifier) {
  16211. const r = new RegExp(`^${this.options.loose ? src[t.PRERELEASELOOSE] : src[t.PRERELEASE]}$`);
  16212. const match = `-${identifier}`.match(r);
  16213. if (!match || match[1] !== identifier) {
  16214. throw new Error(`invalid identifier: ${identifier}`)
  16215. }
  16216. }
  16217. }
  16218. switch (release) {
  16219. case 'premajor':
  16220. this.prerelease.length = 0;
  16221. this.patch = 0;
  16222. this.minor = 0;
  16223. this.major++;
  16224. this.inc('pre', identifier, identifierBase);
  16225. break
  16226. case 'preminor':
  16227. this.prerelease.length = 0;
  16228. this.patch = 0;
  16229. this.minor++;
  16230. this.inc('pre', identifier, identifierBase);
  16231. break
  16232. case 'prepatch':
  16233. // If this is already a prerelease, it will bump to the next version
  16234. // drop any prereleases that might already exist, since they are not
  16235. // relevant at this point.
  16236. this.prerelease.length = 0;
  16237. this.inc('patch', identifier, identifierBase);
  16238. this.inc('pre', identifier, identifierBase);
  16239. break
  16240. // If the input is a non-prerelease version, this acts the same as
  16241. // prepatch.
  16242. case 'prerelease':
  16243. if (this.prerelease.length === 0) {
  16244. this.inc('patch', identifier, identifierBase);
  16245. }
  16246. this.inc('pre', identifier, identifierBase);
  16247. break
  16248. case 'release':
  16249. if (this.prerelease.length === 0) {
  16250. throw new Error(`version ${this.raw} is not a prerelease`)
  16251. }
  16252. this.prerelease.length = 0;
  16253. break
  16254. case 'major':
  16255. // If this is a pre-major version, bump up to the same major version.
  16256. // Otherwise increment major.
  16257. // 1.0.0-5 bumps to 1.0.0
  16258. // 1.1.0 bumps to 2.0.0
  16259. if (
  16260. this.minor !== 0 ||
  16261. this.patch !== 0 ||
  16262. this.prerelease.length === 0
  16263. ) {
  16264. this.major++;
  16265. }
  16266. this.minor = 0;
  16267. this.patch = 0;
  16268. this.prerelease = [];
  16269. break
  16270. case 'minor':
  16271. // If this is a pre-minor version, bump up to the same minor version.
  16272. // Otherwise increment minor.
  16273. // 1.2.0-5 bumps to 1.2.0
  16274. // 1.2.1 bumps to 1.3.0
  16275. if (this.patch !== 0 || this.prerelease.length === 0) {
  16276. this.minor++;
  16277. }
  16278. this.patch = 0;
  16279. this.prerelease = [];
  16280. break
  16281. case 'patch':
  16282. // If this is not a pre-release version, it will increment the patch.
  16283. // If it is a pre-release it will bump up to the same patch version.
  16284. // 1.2.0-5 patches to 1.2.0
  16285. // 1.2.0 patches to 1.2.1
  16286. if (this.prerelease.length === 0) {
  16287. this.patch++;
  16288. }
  16289. this.prerelease = [];
  16290. break
  16291. // This probably shouldn't be used publicly.
  16292. // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction.
  16293. case 'pre': {
  16294. const base = Number(identifierBase) ? 1 : 0;
  16295. if (this.prerelease.length === 0) {
  16296. this.prerelease = [base];
  16297. } else {
  16298. let i = this.prerelease.length;
  16299. while (--i >= 0) {
  16300. if (typeof this.prerelease[i] === 'number') {
  16301. this.prerelease[i]++;
  16302. i = -2;
  16303. }
  16304. }
  16305. if (i === -1) {
  16306. // didn't increment anything
  16307. if (identifier === this.prerelease.join('.') && identifierBase === false) {
  16308. throw new Error('invalid increment argument: identifier already exists')
  16309. }
  16310. this.prerelease.push(base);
  16311. }
  16312. }
  16313. if (identifier) {
  16314. // 1.2.0-beta.1 bumps to 1.2.0-beta.2,
  16315. // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
  16316. let prerelease = [identifier, base];
  16317. if (identifierBase === false) {
  16318. prerelease = [identifier];
  16319. }
  16320. if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
  16321. if (isNaN(this.prerelease[1])) {
  16322. this.prerelease = prerelease;
  16323. }
  16324. } else {
  16325. this.prerelease = prerelease;
  16326. }
  16327. }
  16328. break
  16329. }
  16330. default:
  16331. throw new Error(`invalid increment argument: ${release}`)
  16332. }
  16333. this.raw = this.format();
  16334. if (this.build.length) {
  16335. this.raw += `+${this.build.join('.')}`;
  16336. }
  16337. return this
  16338. }
  16339. }
  16340. semver$2 = SemVer;
  16341. return semver$2;
  16342. }
  16343. var parse_1;
  16344. var hasRequiredParse;
  16345. function requireParse () {
  16346. if (hasRequiredParse) return parse_1;
  16347. hasRequiredParse = 1;
  16348. const SemVer = requireSemver$1();
  16349. const parse = (version, options, throwErrors = false) => {
  16350. if (version instanceof SemVer) {
  16351. return version
  16352. }
  16353. try {
  16354. return new SemVer(version, options)
  16355. } catch (er) {
  16356. if (!throwErrors) {
  16357. return null
  16358. }
  16359. throw er
  16360. }
  16361. };
  16362. parse_1 = parse;
  16363. return parse_1;
  16364. }
  16365. var valid_1;
  16366. var hasRequiredValid$1;
  16367. function requireValid$1 () {
  16368. if (hasRequiredValid$1) return valid_1;
  16369. hasRequiredValid$1 = 1;
  16370. const parse = requireParse();
  16371. const valid = (version, options) => {
  16372. const v = parse(version, options);
  16373. return v ? v.version : null
  16374. };
  16375. valid_1 = valid;
  16376. return valid_1;
  16377. }
  16378. var clean_1;
  16379. var hasRequiredClean;
  16380. function requireClean () {
  16381. if (hasRequiredClean) return clean_1;
  16382. hasRequiredClean = 1;
  16383. const parse = requireParse();
  16384. const clean = (version, options) => {
  16385. const s = parse(version.trim().replace(/^[=v]+/, ''), options);
  16386. return s ? s.version : null
  16387. };
  16388. clean_1 = clean;
  16389. return clean_1;
  16390. }
  16391. var inc_1;
  16392. var hasRequiredInc;
  16393. function requireInc () {
  16394. if (hasRequiredInc) return inc_1;
  16395. hasRequiredInc = 1;
  16396. const SemVer = requireSemver$1();
  16397. const inc = (version, release, options, identifier, identifierBase) => {
  16398. if (typeof (options) === 'string') {
  16399. identifierBase = identifier;
  16400. identifier = options;
  16401. options = undefined;
  16402. }
  16403. try {
  16404. return new SemVer(
  16405. version instanceof SemVer ? version.version : version,
  16406. options
  16407. ).inc(release, identifier, identifierBase).version
  16408. } catch (er) {
  16409. return null
  16410. }
  16411. };
  16412. inc_1 = inc;
  16413. return inc_1;
  16414. }
  16415. var diff_1;
  16416. var hasRequiredDiff;
  16417. function requireDiff () {
  16418. if (hasRequiredDiff) return diff_1;
  16419. hasRequiredDiff = 1;
  16420. const parse = requireParse();
  16421. const diff = (version1, version2) => {
  16422. const v1 = parse(version1, null, true);
  16423. const v2 = parse(version2, null, true);
  16424. const comparison = v1.compare(v2);
  16425. if (comparison === 0) {
  16426. return null
  16427. }
  16428. const v1Higher = comparison > 0;
  16429. const highVersion = v1Higher ? v1 : v2;
  16430. const lowVersion = v1Higher ? v2 : v1;
  16431. const highHasPre = !!highVersion.prerelease.length;
  16432. const lowHasPre = !!lowVersion.prerelease.length;
  16433. if (lowHasPre && !highHasPre) {
  16434. // Going from prerelease -> no prerelease requires some special casing
  16435. // If the low version has only a major, then it will always be a major
  16436. // Some examples:
  16437. // 1.0.0-1 -> 1.0.0
  16438. // 1.0.0-1 -> 1.1.1
  16439. // 1.0.0-1 -> 2.0.0
  16440. if (!lowVersion.patch && !lowVersion.minor) {
  16441. return 'major'
  16442. }
  16443. // If the main part has no difference
  16444. if (lowVersion.compareMain(highVersion) === 0) {
  16445. if (lowVersion.minor && !lowVersion.patch) {
  16446. return 'minor'
  16447. }
  16448. return 'patch'
  16449. }
  16450. }
  16451. // add the `pre` prefix if we are going to a prerelease version
  16452. const prefix = highHasPre ? 'pre' : '';
  16453. if (v1.major !== v2.major) {
  16454. return prefix + 'major'
  16455. }
  16456. if (v1.minor !== v2.minor) {
  16457. return prefix + 'minor'
  16458. }
  16459. if (v1.patch !== v2.patch) {
  16460. return prefix + 'patch'
  16461. }
  16462. // high and low are preleases
  16463. return 'prerelease'
  16464. };
  16465. diff_1 = diff;
  16466. return diff_1;
  16467. }
  16468. var major_1;
  16469. var hasRequiredMajor;
  16470. function requireMajor () {
  16471. if (hasRequiredMajor) return major_1;
  16472. hasRequiredMajor = 1;
  16473. const SemVer = requireSemver$1();
  16474. const major = (a, loose) => new SemVer(a, loose).major;
  16475. major_1 = major;
  16476. return major_1;
  16477. }
  16478. var minor_1;
  16479. var hasRequiredMinor;
  16480. function requireMinor () {
  16481. if (hasRequiredMinor) return minor_1;
  16482. hasRequiredMinor = 1;
  16483. const SemVer = requireSemver$1();
  16484. const minor = (a, loose) => new SemVer(a, loose).minor;
  16485. minor_1 = minor;
  16486. return minor_1;
  16487. }
  16488. var patch_1;
  16489. var hasRequiredPatch;
  16490. function requirePatch () {
  16491. if (hasRequiredPatch) return patch_1;
  16492. hasRequiredPatch = 1;
  16493. const SemVer = requireSemver$1();
  16494. const patch = (a, loose) => new SemVer(a, loose).patch;
  16495. patch_1 = patch;
  16496. return patch_1;
  16497. }
  16498. var prerelease_1;
  16499. var hasRequiredPrerelease;
  16500. function requirePrerelease () {
  16501. if (hasRequiredPrerelease) return prerelease_1;
  16502. hasRequiredPrerelease = 1;
  16503. const parse = requireParse();
  16504. const prerelease = (version, options) => {
  16505. const parsed = parse(version, options);
  16506. return (parsed && parsed.prerelease.length) ? parsed.prerelease : null
  16507. };
  16508. prerelease_1 = prerelease;
  16509. return prerelease_1;
  16510. }
  16511. var compare_1;
  16512. var hasRequiredCompare;
  16513. function requireCompare () {
  16514. if (hasRequiredCompare) return compare_1;
  16515. hasRequiredCompare = 1;
  16516. const SemVer = requireSemver$1();
  16517. const compare = (a, b, loose) =>
  16518. new SemVer(a, loose).compare(new SemVer(b, loose));
  16519. compare_1 = compare;
  16520. return compare_1;
  16521. }
  16522. var rcompare_1;
  16523. var hasRequiredRcompare;
  16524. function requireRcompare () {
  16525. if (hasRequiredRcompare) return rcompare_1;
  16526. hasRequiredRcompare = 1;
  16527. const compare = requireCompare();
  16528. const rcompare = (a, b, loose) => compare(b, a, loose);
  16529. rcompare_1 = rcompare;
  16530. return rcompare_1;
  16531. }
  16532. var compareLoose_1;
  16533. var hasRequiredCompareLoose;
  16534. function requireCompareLoose () {
  16535. if (hasRequiredCompareLoose) return compareLoose_1;
  16536. hasRequiredCompareLoose = 1;
  16537. const compare = requireCompare();
  16538. const compareLoose = (a, b) => compare(a, b, true);
  16539. compareLoose_1 = compareLoose;
  16540. return compareLoose_1;
  16541. }
  16542. var compareBuild_1;
  16543. var hasRequiredCompareBuild;
  16544. function requireCompareBuild () {
  16545. if (hasRequiredCompareBuild) return compareBuild_1;
  16546. hasRequiredCompareBuild = 1;
  16547. const SemVer = requireSemver$1();
  16548. const compareBuild = (a, b, loose) => {
  16549. const versionA = new SemVer(a, loose);
  16550. const versionB = new SemVer(b, loose);
  16551. return versionA.compare(versionB) || versionA.compareBuild(versionB)
  16552. };
  16553. compareBuild_1 = compareBuild;
  16554. return compareBuild_1;
  16555. }
  16556. var sort_1;
  16557. var hasRequiredSort;
  16558. function requireSort () {
  16559. if (hasRequiredSort) return sort_1;
  16560. hasRequiredSort = 1;
  16561. const compareBuild = requireCompareBuild();
  16562. const sort = (list, loose) => list.sort((a, b) => compareBuild(a, b, loose));
  16563. sort_1 = sort;
  16564. return sort_1;
  16565. }
  16566. var rsort_1;
  16567. var hasRequiredRsort;
  16568. function requireRsort () {
  16569. if (hasRequiredRsort) return rsort_1;
  16570. hasRequiredRsort = 1;
  16571. const compareBuild = requireCompareBuild();
  16572. const rsort = (list, loose) => list.sort((a, b) => compareBuild(b, a, loose));
  16573. rsort_1 = rsort;
  16574. return rsort_1;
  16575. }
  16576. var gt_1;
  16577. var hasRequiredGt;
  16578. function requireGt () {
  16579. if (hasRequiredGt) return gt_1;
  16580. hasRequiredGt = 1;
  16581. const compare = requireCompare();
  16582. const gt = (a, b, loose) => compare(a, b, loose) > 0;
  16583. gt_1 = gt;
  16584. return gt_1;
  16585. }
  16586. var lt_1;
  16587. var hasRequiredLt;
  16588. function requireLt () {
  16589. if (hasRequiredLt) return lt_1;
  16590. hasRequiredLt = 1;
  16591. const compare = requireCompare();
  16592. const lt = (a, b, loose) => compare(a, b, loose) < 0;
  16593. lt_1 = lt;
  16594. return lt_1;
  16595. }
  16596. var eq_1;
  16597. var hasRequiredEq;
  16598. function requireEq () {
  16599. if (hasRequiredEq) return eq_1;
  16600. hasRequiredEq = 1;
  16601. const compare = requireCompare();
  16602. const eq = (a, b, loose) => compare(a, b, loose) === 0;
  16603. eq_1 = eq;
  16604. return eq_1;
  16605. }
  16606. var neq_1;
  16607. var hasRequiredNeq;
  16608. function requireNeq () {
  16609. if (hasRequiredNeq) return neq_1;
  16610. hasRequiredNeq = 1;
  16611. const compare = requireCompare();
  16612. const neq = (a, b, loose) => compare(a, b, loose) !== 0;
  16613. neq_1 = neq;
  16614. return neq_1;
  16615. }
  16616. var gte_1;
  16617. var hasRequiredGte;
  16618. function requireGte () {
  16619. if (hasRequiredGte) return gte_1;
  16620. hasRequiredGte = 1;
  16621. const compare = requireCompare();
  16622. const gte = (a, b, loose) => compare(a, b, loose) >= 0;
  16623. gte_1 = gte;
  16624. return gte_1;
  16625. }
  16626. var lte_1;
  16627. var hasRequiredLte;
  16628. function requireLte () {
  16629. if (hasRequiredLte) return lte_1;
  16630. hasRequiredLte = 1;
  16631. const compare = requireCompare();
  16632. const lte = (a, b, loose) => compare(a, b, loose) <= 0;
  16633. lte_1 = lte;
  16634. return lte_1;
  16635. }
  16636. var cmp_1;
  16637. var hasRequiredCmp;
  16638. function requireCmp () {
  16639. if (hasRequiredCmp) return cmp_1;
  16640. hasRequiredCmp = 1;
  16641. const eq = requireEq();
  16642. const neq = requireNeq();
  16643. const gt = requireGt();
  16644. const gte = requireGte();
  16645. const lt = requireLt();
  16646. const lte = requireLte();
  16647. const cmp = (a, op, b, loose) => {
  16648. switch (op) {
  16649. case '===':
  16650. if (typeof a === 'object') {
  16651. a = a.version;
  16652. }
  16653. if (typeof b === 'object') {
  16654. b = b.version;
  16655. }
  16656. return a === b
  16657. case '!==':
  16658. if (typeof a === 'object') {
  16659. a = a.version;
  16660. }
  16661. if (typeof b === 'object') {
  16662. b = b.version;
  16663. }
  16664. return a !== b
  16665. case '':
  16666. case '=':
  16667. case '==':
  16668. return eq(a, b, loose)
  16669. case '!=':
  16670. return neq(a, b, loose)
  16671. case '>':
  16672. return gt(a, b, loose)
  16673. case '>=':
  16674. return gte(a, b, loose)
  16675. case '<':
  16676. return lt(a, b, loose)
  16677. case '<=':
  16678. return lte(a, b, loose)
  16679. default:
  16680. throw new TypeError(`Invalid operator: ${op}`)
  16681. }
  16682. };
  16683. cmp_1 = cmp;
  16684. return cmp_1;
  16685. }
  16686. var coerce_1;
  16687. var hasRequiredCoerce;
  16688. function requireCoerce () {
  16689. if (hasRequiredCoerce) return coerce_1;
  16690. hasRequiredCoerce = 1;
  16691. const SemVer = requireSemver$1();
  16692. const parse = requireParse();
  16693. const { safeRe: re, t } = requireRe();
  16694. const coerce = (version, options) => {
  16695. if (version instanceof SemVer) {
  16696. return version
  16697. }
  16698. if (typeof version === 'number') {
  16699. version = String(version);
  16700. }
  16701. if (typeof version !== 'string') {
  16702. return null
  16703. }
  16704. options = options || {};
  16705. let match = null;
  16706. if (!options.rtl) {
  16707. match = version.match(options.includePrerelease ? re[t.COERCEFULL] : re[t.COERCE]);
  16708. } else {
  16709. // Find the right-most coercible string that does not share
  16710. // a terminus with a more left-ward coercible string.
  16711. // Eg, '1.2.3.4' wants to coerce '2.3.4', not '3.4' or '4'
  16712. // With includePrerelease option set, '1.2.3.4-rc' wants to coerce '2.3.4-rc', not '2.3.4'
  16713. //
  16714. // Walk through the string checking with a /g regexp
  16715. // Manually set the index so as to pick up overlapping matches.
  16716. // Stop when we get a match that ends at the string end, since no
  16717. // coercible string can be more right-ward without the same terminus.
  16718. const coerceRtlRegex = options.includePrerelease ? re[t.COERCERTLFULL] : re[t.COERCERTL];
  16719. let next;
  16720. while ((next = coerceRtlRegex.exec(version)) &&
  16721. (!match || match.index + match[0].length !== version.length)
  16722. ) {
  16723. if (!match ||
  16724. next.index + next[0].length !== match.index + match[0].length) {
  16725. match = next;
  16726. }
  16727. coerceRtlRegex.lastIndex = next.index + next[1].length + next[2].length;
  16728. }
  16729. // leave it in a clean state
  16730. coerceRtlRegex.lastIndex = -1;
  16731. }
  16732. if (match === null) {
  16733. return null
  16734. }
  16735. const major = match[2];
  16736. const minor = match[3] || '0';
  16737. const patch = match[4] || '0';
  16738. const prerelease = options.includePrerelease && match[5] ? `-${match[5]}` : '';
  16739. const build = options.includePrerelease && match[6] ? `+${match[6]}` : '';
  16740. return parse(`${major}.${minor}.${patch}${prerelease}${build}`, options)
  16741. };
  16742. coerce_1 = coerce;
  16743. return coerce_1;
  16744. }
  16745. var lrucache;
  16746. var hasRequiredLrucache;
  16747. function requireLrucache () {
  16748. if (hasRequiredLrucache) return lrucache;
  16749. hasRequiredLrucache = 1;
  16750. class LRUCache {
  16751. constructor () {
  16752. this.max = 1000;
  16753. this.map = new Map();
  16754. }
  16755. get (key) {
  16756. const value = this.map.get(key);
  16757. if (value === undefined) {
  16758. return undefined
  16759. } else {
  16760. // Remove the key from the map and add it to the end
  16761. this.map.delete(key);
  16762. this.map.set(key, value);
  16763. return value
  16764. }
  16765. }
  16766. delete (key) {
  16767. return this.map.delete(key)
  16768. }
  16769. set (key, value) {
  16770. const deleted = this.delete(key);
  16771. if (!deleted && value !== undefined) {
  16772. // If cache is full, delete the least recently used item
  16773. if (this.map.size >= this.max) {
  16774. const firstKey = this.map.keys().next().value;
  16775. this.delete(firstKey);
  16776. }
  16777. this.map.set(key, value);
  16778. }
  16779. return this
  16780. }
  16781. }
  16782. lrucache = LRUCache;
  16783. return lrucache;
  16784. }
  16785. var range;
  16786. var hasRequiredRange;
  16787. function requireRange () {
  16788. if (hasRequiredRange) return range;
  16789. hasRequiredRange = 1;
  16790. const SPACE_CHARACTERS = /\s+/g;
  16791. // hoisted class for cyclic dependency
  16792. class Range {
  16793. constructor (range, options) {
  16794. options = parseOptions(options);
  16795. if (range instanceof Range) {
  16796. if (
  16797. range.loose === !!options.loose &&
  16798. range.includePrerelease === !!options.includePrerelease
  16799. ) {
  16800. return range
  16801. } else {
  16802. return new Range(range.raw, options)
  16803. }
  16804. }
  16805. if (range instanceof Comparator) {
  16806. // just put it in the set and return
  16807. this.raw = range.value;
  16808. this.set = [[range]];
  16809. this.formatted = undefined;
  16810. return this
  16811. }
  16812. this.options = options;
  16813. this.loose = !!options.loose;
  16814. this.includePrerelease = !!options.includePrerelease;
  16815. // First reduce all whitespace as much as possible so we do not have to rely
  16816. // on potentially slow regexes like \s*. This is then stored and used for
  16817. // future error messages as well.
  16818. this.raw = range.trim().replace(SPACE_CHARACTERS, ' ');
  16819. // First, split on ||
  16820. this.set = this.raw
  16821. .split('||')
  16822. // map the range to a 2d array of comparators
  16823. .map(r => this.parseRange(r.trim()))
  16824. // throw out any comparator lists that are empty
  16825. // this generally means that it was not a valid range, which is allowed
  16826. // in loose mode, but will still throw if the WHOLE range is invalid.
  16827. .filter(c => c.length);
  16828. if (!this.set.length) {
  16829. throw new TypeError(`Invalid SemVer Range: ${this.raw}`)
  16830. }
  16831. // if we have any that are not the null set, throw out null sets.
  16832. if (this.set.length > 1) {
  16833. // keep the first one, in case they're all null sets
  16834. const first = this.set[0];
  16835. this.set = this.set.filter(c => !isNullSet(c[0]));
  16836. if (this.set.length === 0) {
  16837. this.set = [first];
  16838. } else if (this.set.length > 1) {
  16839. // if we have any that are *, then the range is just *
  16840. for (const c of this.set) {
  16841. if (c.length === 1 && isAny(c[0])) {
  16842. this.set = [c];
  16843. break
  16844. }
  16845. }
  16846. }
  16847. }
  16848. this.formatted = undefined;
  16849. }
  16850. get range () {
  16851. if (this.formatted === undefined) {
  16852. this.formatted = '';
  16853. for (let i = 0; i < this.set.length; i++) {
  16854. if (i > 0) {
  16855. this.formatted += '||';
  16856. }
  16857. const comps = this.set[i];
  16858. for (let k = 0; k < comps.length; k++) {
  16859. if (k > 0) {
  16860. this.formatted += ' ';
  16861. }
  16862. this.formatted += comps[k].toString().trim();
  16863. }
  16864. }
  16865. }
  16866. return this.formatted
  16867. }
  16868. format () {
  16869. return this.range
  16870. }
  16871. toString () {
  16872. return this.range
  16873. }
  16874. parseRange (range) {
  16875. // memoize range parsing for performance.
  16876. // this is a very hot path, and fully deterministic.
  16877. const memoOpts =
  16878. (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) |
  16879. (this.options.loose && FLAG_LOOSE);
  16880. const memoKey = memoOpts + ':' + range;
  16881. const cached = cache.get(memoKey);
  16882. if (cached) {
  16883. return cached
  16884. }
  16885. const loose = this.options.loose;
  16886. // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
  16887. const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE];
  16888. range = range.replace(hr, hyphenReplace(this.options.includePrerelease));
  16889. debug('hyphen replace', range);
  16890. // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
  16891. range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace);
  16892. debug('comparator trim', range);
  16893. // `~ 1.2.3` => `~1.2.3`
  16894. range = range.replace(re[t.TILDETRIM], tildeTrimReplace);
  16895. debug('tilde trim', range);
  16896. // `^ 1.2.3` => `^1.2.3`
  16897. range = range.replace(re[t.CARETTRIM], caretTrimReplace);
  16898. debug('caret trim', range);
  16899. // At this point, the range is completely trimmed and
  16900. // ready to be split into comparators.
  16901. let rangeList = range
  16902. .split(' ')
  16903. .map(comp => parseComparator(comp, this.options))
  16904. .join(' ')
  16905. .split(/\s+/)
  16906. // >=0.0.0 is equivalent to *
  16907. .map(comp => replaceGTE0(comp, this.options));
  16908. if (loose) {
  16909. // in loose mode, throw out any that are not valid comparators
  16910. rangeList = rangeList.filter(comp => {
  16911. debug('loose invalid filter', comp, this.options);
  16912. return !!comp.match(re[t.COMPARATORLOOSE])
  16913. });
  16914. }
  16915. debug('range list', rangeList);
  16916. // if any comparators are the null set, then replace with JUST null set
  16917. // if more than one comparator, remove any * comparators
  16918. // also, don't include the same comparator more than once
  16919. const rangeMap = new Map();
  16920. const comparators = rangeList.map(comp => new Comparator(comp, this.options));
  16921. for (const comp of comparators) {
  16922. if (isNullSet(comp)) {
  16923. return [comp]
  16924. }
  16925. rangeMap.set(comp.value, comp);
  16926. }
  16927. if (rangeMap.size > 1 && rangeMap.has('')) {
  16928. rangeMap.delete('');
  16929. }
  16930. const result = [...rangeMap.values()];
  16931. cache.set(memoKey, result);
  16932. return result
  16933. }
  16934. intersects (range, options) {
  16935. if (!(range instanceof Range)) {
  16936. throw new TypeError('a Range is required')
  16937. }
  16938. return this.set.some((thisComparators) => {
  16939. return (
  16940. isSatisfiable(thisComparators, options) &&
  16941. range.set.some((rangeComparators) => {
  16942. return (
  16943. isSatisfiable(rangeComparators, options) &&
  16944. thisComparators.every((thisComparator) => {
  16945. return rangeComparators.every((rangeComparator) => {
  16946. return thisComparator.intersects(rangeComparator, options)
  16947. })
  16948. })
  16949. )
  16950. })
  16951. )
  16952. })
  16953. }
  16954. // if ANY of the sets match ALL of its comparators, then pass
  16955. test (version) {
  16956. if (!version) {
  16957. return false
  16958. }
  16959. if (typeof version === 'string') {
  16960. try {
  16961. version = new SemVer(version, this.options);
  16962. } catch (er) {
  16963. return false
  16964. }
  16965. }
  16966. for (let i = 0; i < this.set.length; i++) {
  16967. if (testSet(this.set[i], version, this.options)) {
  16968. return true
  16969. }
  16970. }
  16971. return false
  16972. }
  16973. }
  16974. range = Range;
  16975. const LRU = requireLrucache();
  16976. const cache = new LRU();
  16977. const parseOptions = requireParseOptions();
  16978. const Comparator = requireComparator();
  16979. const debug = requireDebug();
  16980. const SemVer = requireSemver$1();
  16981. const {
  16982. safeRe: re,
  16983. t,
  16984. comparatorTrimReplace,
  16985. tildeTrimReplace,
  16986. caretTrimReplace,
  16987. } = requireRe();
  16988. const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = requireConstants();
  16989. const isNullSet = c => c.value === '<0.0.0-0';
  16990. const isAny = c => c.value === '';
  16991. // take a set of comparators and determine whether there
  16992. // exists a version which can satisfy it
  16993. const isSatisfiable = (comparators, options) => {
  16994. let result = true;
  16995. const remainingComparators = comparators.slice();
  16996. let testComparator = remainingComparators.pop();
  16997. while (result && remainingComparators.length) {
  16998. result = remainingComparators.every((otherComparator) => {
  16999. return testComparator.intersects(otherComparator, options)
  17000. });
  17001. testComparator = remainingComparators.pop();
  17002. }
  17003. return result
  17004. };
  17005. // comprised of xranges, tildes, stars, and gtlt's at this point.
  17006. // already replaced the hyphen ranges
  17007. // turn into a set of JUST comparators.
  17008. const parseComparator = (comp, options) => {
  17009. debug('comp', comp, options);
  17010. comp = replaceCarets(comp, options);
  17011. debug('caret', comp);
  17012. comp = replaceTildes(comp, options);
  17013. debug('tildes', comp);
  17014. comp = replaceXRanges(comp, options);
  17015. debug('xrange', comp);
  17016. comp = replaceStars(comp, options);
  17017. debug('stars', comp);
  17018. return comp
  17019. };
  17020. const isX = id => !id || id.toLowerCase() === 'x' || id === '*';
  17021. // ~, ~> --> * (any, kinda silly)
  17022. // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0
  17023. // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0
  17024. // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0
  17025. // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0
  17026. // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0
  17027. // ~0.0.1 --> >=0.0.1 <0.1.0-0
  17028. const replaceTildes = (comp, options) => {
  17029. return comp
  17030. .trim()
  17031. .split(/\s+/)
  17032. .map((c) => replaceTilde(c, options))
  17033. .join(' ')
  17034. };
  17035. const replaceTilde = (comp, options) => {
  17036. const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE];
  17037. return comp.replace(r, (_, M, m, p, pr) => {
  17038. debug('tilde', comp, _, M, m, p, pr);
  17039. let ret;
  17040. if (isX(M)) {
  17041. ret = '';
  17042. } else if (isX(m)) {
  17043. ret = `>=${M}.0.0 <${+M + 1}.0.0-0`;
  17044. } else if (isX(p)) {
  17045. // ~1.2 == >=1.2.0 <1.3.0-0
  17046. ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0`;
  17047. } else if (pr) {
  17048. debug('replaceTilde pr', pr);
  17049. ret = `>=${M}.${m}.${p}-${pr
  17050. } <${M}.${+m + 1}.0-0`;
  17051. } else {
  17052. // ~1.2.3 == >=1.2.3 <1.3.0-0
  17053. ret = `>=${M}.${m}.${p
  17054. } <${M}.${+m + 1}.0-0`;
  17055. }
  17056. debug('tilde return', ret);
  17057. return ret
  17058. })
  17059. };
  17060. // ^ --> * (any, kinda silly)
  17061. // ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0
  17062. // ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0
  17063. // ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0
  17064. // ^1.2.3 --> >=1.2.3 <2.0.0-0
  17065. // ^1.2.0 --> >=1.2.0 <2.0.0-0
  17066. // ^0.0.1 --> >=0.0.1 <0.0.2-0
  17067. // ^0.1.0 --> >=0.1.0 <0.2.0-0
  17068. const replaceCarets = (comp, options) => {
  17069. return comp
  17070. .trim()
  17071. .split(/\s+/)
  17072. .map((c) => replaceCaret(c, options))
  17073. .join(' ')
  17074. };
  17075. const replaceCaret = (comp, options) => {
  17076. debug('caret', comp, options);
  17077. const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET];
  17078. const z = options.includePrerelease ? '-0' : '';
  17079. return comp.replace(r, (_, M, m, p, pr) => {
  17080. debug('caret', comp, _, M, m, p, pr);
  17081. let ret;
  17082. if (isX(M)) {
  17083. ret = '';
  17084. } else if (isX(m)) {
  17085. ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`;
  17086. } else if (isX(p)) {
  17087. if (M === '0') {
  17088. ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`;
  17089. } else {
  17090. ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0`;
  17091. }
  17092. } else if (pr) {
  17093. debug('replaceCaret pr', pr);
  17094. if (M === '0') {
  17095. if (m === '0') {
  17096. ret = `>=${M}.${m}.${p}-${pr
  17097. } <${M}.${m}.${+p + 1}-0`;
  17098. } else {
  17099. ret = `>=${M}.${m}.${p}-${pr
  17100. } <${M}.${+m + 1}.0-0`;
  17101. }
  17102. } else {
  17103. ret = `>=${M}.${m}.${p}-${pr
  17104. } <${+M + 1}.0.0-0`;
  17105. }
  17106. } else {
  17107. debug('no pr');
  17108. if (M === '0') {
  17109. if (m === '0') {
  17110. ret = `>=${M}.${m}.${p
  17111. }${z} <${M}.${m}.${+p + 1}-0`;
  17112. } else {
  17113. ret = `>=${M}.${m}.${p
  17114. }${z} <${M}.${+m + 1}.0-0`;
  17115. }
  17116. } else {
  17117. ret = `>=${M}.${m}.${p
  17118. } <${+M + 1}.0.0-0`;
  17119. }
  17120. }
  17121. debug('caret return', ret);
  17122. return ret
  17123. })
  17124. };
  17125. const replaceXRanges = (comp, options) => {
  17126. debug('replaceXRanges', comp, options);
  17127. return comp
  17128. .split(/\s+/)
  17129. .map((c) => replaceXRange(c, options))
  17130. .join(' ')
  17131. };
  17132. const replaceXRange = (comp, options) => {
  17133. comp = comp.trim();
  17134. const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE];
  17135. return comp.replace(r, (ret, gtlt, M, m, p, pr) => {
  17136. debug('xRange', comp, ret, gtlt, M, m, p, pr);
  17137. const xM = isX(M);
  17138. const xm = xM || isX(m);
  17139. const xp = xm || isX(p);
  17140. const anyX = xp;
  17141. if (gtlt === '=' && anyX) {
  17142. gtlt = '';
  17143. }
  17144. // if we're including prereleases in the match, then we need
  17145. // to fix this to -0, the lowest possible prerelease value
  17146. pr = options.includePrerelease ? '-0' : '';
  17147. if (xM) {
  17148. if (gtlt === '>' || gtlt === '<') {
  17149. // nothing is allowed
  17150. ret = '<0.0.0-0';
  17151. } else {
  17152. // nothing is forbidden
  17153. ret = '*';
  17154. }
  17155. } else if (gtlt && anyX) {
  17156. // we know patch is an x, because we have any x at all.
  17157. // replace X with 0
  17158. if (xm) {
  17159. m = 0;
  17160. }
  17161. p = 0;
  17162. if (gtlt === '>') {
  17163. // >1 => >=2.0.0
  17164. // >1.2 => >=1.3.0
  17165. gtlt = '>=';
  17166. if (xm) {
  17167. M = +M + 1;
  17168. m = 0;
  17169. p = 0;
  17170. } else {
  17171. m = +m + 1;
  17172. p = 0;
  17173. }
  17174. } else if (gtlt === '<=') {
  17175. // <=0.7.x is actually <0.8.0, since any 0.7.x should
  17176. // pass. Similarly, <=7.x is actually <8.0.0, etc.
  17177. gtlt = '<';
  17178. if (xm) {
  17179. M = +M + 1;
  17180. } else {
  17181. m = +m + 1;
  17182. }
  17183. }
  17184. if (gtlt === '<') {
  17185. pr = '-0';
  17186. }
  17187. ret = `${gtlt + M}.${m}.${p}${pr}`;
  17188. } else if (xm) {
  17189. ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0`;
  17190. } else if (xp) {
  17191. ret = `>=${M}.${m}.0${pr
  17192. } <${M}.${+m + 1}.0-0`;
  17193. }
  17194. debug('xRange return', ret);
  17195. return ret
  17196. })
  17197. };
  17198. // Because * is AND-ed with everything else in the comparator,
  17199. // and '' means "any version", just remove the *s entirely.
  17200. const replaceStars = (comp, options) => {
  17201. debug('replaceStars', comp, options);
  17202. // Looseness is ignored here. star is always as loose as it gets!
  17203. return comp
  17204. .trim()
  17205. .replace(re[t.STAR], '')
  17206. };
  17207. const replaceGTE0 = (comp, options) => {
  17208. debug('replaceGTE0', comp, options);
  17209. return comp
  17210. .trim()
  17211. .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '')
  17212. };
  17213. // This function is passed to string.replace(re[t.HYPHENRANGE])
  17214. // M, m, patch, prerelease, build
  17215. // 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
  17216. // 1.2.3 - 3.4 => >=1.2.0 <3.5.0-0 Any 3.4.x will do
  17217. // 1.2 - 3.4 => >=1.2.0 <3.5.0-0
  17218. // TODO build?
  17219. const hyphenReplace = incPr => ($0,
  17220. from, fM, fm, fp, fpr, fb,
  17221. to, tM, tm, tp, tpr) => {
  17222. if (isX(fM)) {
  17223. from = '';
  17224. } else if (isX(fm)) {
  17225. from = `>=${fM}.0.0${incPr ? '-0' : ''}`;
  17226. } else if (isX(fp)) {
  17227. from = `>=${fM}.${fm}.0${incPr ? '-0' : ''}`;
  17228. } else if (fpr) {
  17229. from = `>=${from}`;
  17230. } else {
  17231. from = `>=${from}${incPr ? '-0' : ''}`;
  17232. }
  17233. if (isX(tM)) {
  17234. to = '';
  17235. } else if (isX(tm)) {
  17236. to = `<${+tM + 1}.0.0-0`;
  17237. } else if (isX(tp)) {
  17238. to = `<${tM}.${+tm + 1}.0-0`;
  17239. } else if (tpr) {
  17240. to = `<=${tM}.${tm}.${tp}-${tpr}`;
  17241. } else if (incPr) {
  17242. to = `<${tM}.${tm}.${+tp + 1}-0`;
  17243. } else {
  17244. to = `<=${to}`;
  17245. }
  17246. return `${from} ${to}`.trim()
  17247. };
  17248. const testSet = (set, version, options) => {
  17249. for (let i = 0; i < set.length; i++) {
  17250. if (!set[i].test(version)) {
  17251. return false
  17252. }
  17253. }
  17254. if (version.prerelease.length && !options.includePrerelease) {
  17255. // Find the set of versions that are allowed to have prereleases
  17256. // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
  17257. // That should allow `1.2.3-pr.2` to pass.
  17258. // However, `1.2.4-alpha.notready` should NOT be allowed,
  17259. // even though it's within the range set by the comparators.
  17260. for (let i = 0; i < set.length; i++) {
  17261. debug(set[i].semver);
  17262. if (set[i].semver === Comparator.ANY) {
  17263. continue
  17264. }
  17265. if (set[i].semver.prerelease.length > 0) {
  17266. const allowed = set[i].semver;
  17267. if (allowed.major === version.major &&
  17268. allowed.minor === version.minor &&
  17269. allowed.patch === version.patch) {
  17270. return true
  17271. }
  17272. }
  17273. }
  17274. // Version has a -pre, but it's not one of the ones we like.
  17275. return false
  17276. }
  17277. return true
  17278. };
  17279. return range;
  17280. }
  17281. var comparator;
  17282. var hasRequiredComparator;
  17283. function requireComparator () {
  17284. if (hasRequiredComparator) return comparator;
  17285. hasRequiredComparator = 1;
  17286. const ANY = Symbol('SemVer ANY');
  17287. // hoisted class for cyclic dependency
  17288. class Comparator {
  17289. static get ANY () {
  17290. return ANY
  17291. }
  17292. constructor (comp, options) {
  17293. options = parseOptions(options);
  17294. if (comp instanceof Comparator) {
  17295. if (comp.loose === !!options.loose) {
  17296. return comp
  17297. } else {
  17298. comp = comp.value;
  17299. }
  17300. }
  17301. comp = comp.trim().split(/\s+/).join(' ');
  17302. debug('comparator', comp, options);
  17303. this.options = options;
  17304. this.loose = !!options.loose;
  17305. this.parse(comp);
  17306. if (this.semver === ANY) {
  17307. this.value = '';
  17308. } else {
  17309. this.value = this.operator + this.semver.version;
  17310. }
  17311. debug('comp', this);
  17312. }
  17313. parse (comp) {
  17314. const r = this.options.loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR];
  17315. const m = comp.match(r);
  17316. if (!m) {
  17317. throw new TypeError(`Invalid comparator: ${comp}`)
  17318. }
  17319. this.operator = m[1] !== undefined ? m[1] : '';
  17320. if (this.operator === '=') {
  17321. this.operator = '';
  17322. }
  17323. // if it literally is just '>' or '' then allow anything.
  17324. if (!m[2]) {
  17325. this.semver = ANY;
  17326. } else {
  17327. this.semver = new SemVer(m[2], this.options.loose);
  17328. }
  17329. }
  17330. toString () {
  17331. return this.value
  17332. }
  17333. test (version) {
  17334. debug('Comparator.test', version, this.options.loose);
  17335. if (this.semver === ANY || version === ANY) {
  17336. return true
  17337. }
  17338. if (typeof version === 'string') {
  17339. try {
  17340. version = new SemVer(version, this.options);
  17341. } catch (er) {
  17342. return false
  17343. }
  17344. }
  17345. return cmp(version, this.operator, this.semver, this.options)
  17346. }
  17347. intersects (comp, options) {
  17348. if (!(comp instanceof Comparator)) {
  17349. throw new TypeError('a Comparator is required')
  17350. }
  17351. if (this.operator === '') {
  17352. if (this.value === '') {
  17353. return true
  17354. }
  17355. return new Range(comp.value, options).test(this.value)
  17356. } else if (comp.operator === '') {
  17357. if (comp.value === '') {
  17358. return true
  17359. }
  17360. return new Range(this.value, options).test(comp.semver)
  17361. }
  17362. options = parseOptions(options);
  17363. // Special cases where nothing can possibly be lower
  17364. if (options.includePrerelease &&
  17365. (this.value === '<0.0.0-0' || comp.value === '<0.0.0-0')) {
  17366. return false
  17367. }
  17368. if (!options.includePrerelease &&
  17369. (this.value.startsWith('<0.0.0') || comp.value.startsWith('<0.0.0'))) {
  17370. return false
  17371. }
  17372. // Same direction increasing (> or >=)
  17373. if (this.operator.startsWith('>') && comp.operator.startsWith('>')) {
  17374. return true
  17375. }
  17376. // Same direction decreasing (< or <=)
  17377. if (this.operator.startsWith('<') && comp.operator.startsWith('<')) {
  17378. return true
  17379. }
  17380. // same SemVer and both sides are inclusive (<= or >=)
  17381. if (
  17382. (this.semver.version === comp.semver.version) &&
  17383. this.operator.includes('=') && comp.operator.includes('=')) {
  17384. return true
  17385. }
  17386. // opposite directions less than
  17387. if (cmp(this.semver, '<', comp.semver, options) &&
  17388. this.operator.startsWith('>') && comp.operator.startsWith('<')) {
  17389. return true
  17390. }
  17391. // opposite directions greater than
  17392. if (cmp(this.semver, '>', comp.semver, options) &&
  17393. this.operator.startsWith('<') && comp.operator.startsWith('>')) {
  17394. return true
  17395. }
  17396. return false
  17397. }
  17398. }
  17399. comparator = Comparator;
  17400. const parseOptions = requireParseOptions();
  17401. const { safeRe: re, t } = requireRe();
  17402. const cmp = requireCmp();
  17403. const debug = requireDebug();
  17404. const SemVer = requireSemver$1();
  17405. const Range = requireRange();
  17406. return comparator;
  17407. }
  17408. var satisfies_1;
  17409. var hasRequiredSatisfies;
  17410. function requireSatisfies () {
  17411. if (hasRequiredSatisfies) return satisfies_1;
  17412. hasRequiredSatisfies = 1;
  17413. const Range = requireRange();
  17414. const satisfies = (version, range, options) => {
  17415. try {
  17416. range = new Range(range, options);
  17417. } catch (er) {
  17418. return false
  17419. }
  17420. return range.test(version)
  17421. };
  17422. satisfies_1 = satisfies;
  17423. return satisfies_1;
  17424. }
  17425. var toComparators_1;
  17426. var hasRequiredToComparators;
  17427. function requireToComparators () {
  17428. if (hasRequiredToComparators) return toComparators_1;
  17429. hasRequiredToComparators = 1;
  17430. const Range = requireRange();
  17431. // Mostly just for testing and legacy API reasons
  17432. const toComparators = (range, options) =>
  17433. new Range(range, options).set
  17434. .map(comp => comp.map(c => c.value).join(' ').trim().split(' '));
  17435. toComparators_1 = toComparators;
  17436. return toComparators_1;
  17437. }
  17438. var maxSatisfying_1;
  17439. var hasRequiredMaxSatisfying;
  17440. function requireMaxSatisfying () {
  17441. if (hasRequiredMaxSatisfying) return maxSatisfying_1;
  17442. hasRequiredMaxSatisfying = 1;
  17443. const SemVer = requireSemver$1();
  17444. const Range = requireRange();
  17445. const maxSatisfying = (versions, range, options) => {
  17446. let max = null;
  17447. let maxSV = null;
  17448. let rangeObj = null;
  17449. try {
  17450. rangeObj = new Range(range, options);
  17451. } catch (er) {
  17452. return null
  17453. }
  17454. versions.forEach((v) => {
  17455. if (rangeObj.test(v)) {
  17456. // satisfies(v, range, options)
  17457. if (!max || maxSV.compare(v) === -1) {
  17458. // compare(max, v, true)
  17459. max = v;
  17460. maxSV = new SemVer(max, options);
  17461. }
  17462. }
  17463. });
  17464. return max
  17465. };
  17466. maxSatisfying_1 = maxSatisfying;
  17467. return maxSatisfying_1;
  17468. }
  17469. var minSatisfying_1;
  17470. var hasRequiredMinSatisfying;
  17471. function requireMinSatisfying () {
  17472. if (hasRequiredMinSatisfying) return minSatisfying_1;
  17473. hasRequiredMinSatisfying = 1;
  17474. const SemVer = requireSemver$1();
  17475. const Range = requireRange();
  17476. const minSatisfying = (versions, range, options) => {
  17477. let min = null;
  17478. let minSV = null;
  17479. let rangeObj = null;
  17480. try {
  17481. rangeObj = new Range(range, options);
  17482. } catch (er) {
  17483. return null
  17484. }
  17485. versions.forEach((v) => {
  17486. if (rangeObj.test(v)) {
  17487. // satisfies(v, range, options)
  17488. if (!min || minSV.compare(v) === 1) {
  17489. // compare(min, v, true)
  17490. min = v;
  17491. minSV = new SemVer(min, options);
  17492. }
  17493. }
  17494. });
  17495. return min
  17496. };
  17497. minSatisfying_1 = minSatisfying;
  17498. return minSatisfying_1;
  17499. }
  17500. var minVersion_1;
  17501. var hasRequiredMinVersion;
  17502. function requireMinVersion () {
  17503. if (hasRequiredMinVersion) return minVersion_1;
  17504. hasRequiredMinVersion = 1;
  17505. const SemVer = requireSemver$1();
  17506. const Range = requireRange();
  17507. const gt = requireGt();
  17508. const minVersion = (range, loose) => {
  17509. range = new Range(range, loose);
  17510. let minver = new SemVer('0.0.0');
  17511. if (range.test(minver)) {
  17512. return minver
  17513. }
  17514. minver = new SemVer('0.0.0-0');
  17515. if (range.test(minver)) {
  17516. return minver
  17517. }
  17518. minver = null;
  17519. for (let i = 0; i < range.set.length; ++i) {
  17520. const comparators = range.set[i];
  17521. let setMin = null;
  17522. comparators.forEach((comparator) => {
  17523. // Clone to avoid manipulating the comparator's semver object.
  17524. const compver = new SemVer(comparator.semver.version);
  17525. switch (comparator.operator) {
  17526. case '>':
  17527. if (compver.prerelease.length === 0) {
  17528. compver.patch++;
  17529. } else {
  17530. compver.prerelease.push(0);
  17531. }
  17532. compver.raw = compver.format();
  17533. /* fallthrough */
  17534. case '':
  17535. case '>=':
  17536. if (!setMin || gt(compver, setMin)) {
  17537. setMin = compver;
  17538. }
  17539. break
  17540. case '<':
  17541. case '<=':
  17542. /* Ignore maximum versions */
  17543. break
  17544. /* istanbul ignore next */
  17545. default:
  17546. throw new Error(`Unexpected operation: ${comparator.operator}`)
  17547. }
  17548. });
  17549. if (setMin && (!minver || gt(minver, setMin))) {
  17550. minver = setMin;
  17551. }
  17552. }
  17553. if (minver && range.test(minver)) {
  17554. return minver
  17555. }
  17556. return null
  17557. };
  17558. minVersion_1 = minVersion;
  17559. return minVersion_1;
  17560. }
  17561. var valid;
  17562. var hasRequiredValid;
  17563. function requireValid () {
  17564. if (hasRequiredValid) return valid;
  17565. hasRequiredValid = 1;
  17566. const Range = requireRange();
  17567. const validRange = (range, options) => {
  17568. try {
  17569. // Return '*' instead of '' so that truthiness works.
  17570. // This will throw if it's invalid anyway
  17571. return new Range(range, options).range || '*'
  17572. } catch (er) {
  17573. return null
  17574. }
  17575. };
  17576. valid = validRange;
  17577. return valid;
  17578. }
  17579. var outside_1;
  17580. var hasRequiredOutside;
  17581. function requireOutside () {
  17582. if (hasRequiredOutside) return outside_1;
  17583. hasRequiredOutside = 1;
  17584. const SemVer = requireSemver$1();
  17585. const Comparator = requireComparator();
  17586. const { ANY } = Comparator;
  17587. const Range = requireRange();
  17588. const satisfies = requireSatisfies();
  17589. const gt = requireGt();
  17590. const lt = requireLt();
  17591. const lte = requireLte();
  17592. const gte = requireGte();
  17593. const outside = (version, range, hilo, options) => {
  17594. version = new SemVer(version, options);
  17595. range = new Range(range, options);
  17596. let gtfn, ltefn, ltfn, comp, ecomp;
  17597. switch (hilo) {
  17598. case '>':
  17599. gtfn = gt;
  17600. ltefn = lte;
  17601. ltfn = lt;
  17602. comp = '>';
  17603. ecomp = '>=';
  17604. break
  17605. case '<':
  17606. gtfn = lt;
  17607. ltefn = gte;
  17608. ltfn = gt;
  17609. comp = '<';
  17610. ecomp = '<=';
  17611. break
  17612. default:
  17613. throw new TypeError('Must provide a hilo val of "<" or ">"')
  17614. }
  17615. // If it satisfies the range it is not outside
  17616. if (satisfies(version, range, options)) {
  17617. return false
  17618. }
  17619. // From now on, variable terms are as if we're in "gtr" mode.
  17620. // but note that everything is flipped for the "ltr" function.
  17621. for (let i = 0; i < range.set.length; ++i) {
  17622. const comparators = range.set[i];
  17623. let high = null;
  17624. let low = null;
  17625. comparators.forEach((comparator) => {
  17626. if (comparator.semver === ANY) {
  17627. comparator = new Comparator('>=0.0.0');
  17628. }
  17629. high = high || comparator;
  17630. low = low || comparator;
  17631. if (gtfn(comparator.semver, high.semver, options)) {
  17632. high = comparator;
  17633. } else if (ltfn(comparator.semver, low.semver, options)) {
  17634. low = comparator;
  17635. }
  17636. });
  17637. // If the edge version comparator has a operator then our version
  17638. // isn't outside it
  17639. if (high.operator === comp || high.operator === ecomp) {
  17640. return false
  17641. }
  17642. // If the lowest version comparator has an operator and our version
  17643. // is less than it then it isn't higher than the range
  17644. if ((!low.operator || low.operator === comp) &&
  17645. ltefn(version, low.semver)) {
  17646. return false
  17647. } else if (low.operator === ecomp && ltfn(version, low.semver)) {
  17648. return false
  17649. }
  17650. }
  17651. return true
  17652. };
  17653. outside_1 = outside;
  17654. return outside_1;
  17655. }
  17656. var gtr_1;
  17657. var hasRequiredGtr;
  17658. function requireGtr () {
  17659. if (hasRequiredGtr) return gtr_1;
  17660. hasRequiredGtr = 1;
  17661. // Determine if version is greater than all the versions possible in the range.
  17662. const outside = requireOutside();
  17663. const gtr = (version, range, options) => outside(version, range, '>', options);
  17664. gtr_1 = gtr;
  17665. return gtr_1;
  17666. }
  17667. var ltr_1;
  17668. var hasRequiredLtr;
  17669. function requireLtr () {
  17670. if (hasRequiredLtr) return ltr_1;
  17671. hasRequiredLtr = 1;
  17672. const outside = requireOutside();
  17673. // Determine if version is less than all the versions possible in the range
  17674. const ltr = (version, range, options) => outside(version, range, '<', options);
  17675. ltr_1 = ltr;
  17676. return ltr_1;
  17677. }
  17678. var intersects_1;
  17679. var hasRequiredIntersects;
  17680. function requireIntersects () {
  17681. if (hasRequiredIntersects) return intersects_1;
  17682. hasRequiredIntersects = 1;
  17683. const Range = requireRange();
  17684. const intersects = (r1, r2, options) => {
  17685. r1 = new Range(r1, options);
  17686. r2 = new Range(r2, options);
  17687. return r1.intersects(r2, options)
  17688. };
  17689. intersects_1 = intersects;
  17690. return intersects_1;
  17691. }
  17692. var simplify;
  17693. var hasRequiredSimplify;
  17694. function requireSimplify () {
  17695. if (hasRequiredSimplify) return simplify;
  17696. hasRequiredSimplify = 1;
  17697. // given a set of versions and a range, create a "simplified" range
  17698. // that includes the same versions that the original range does
  17699. // If the original range is shorter than the simplified one, return that.
  17700. const satisfies = requireSatisfies();
  17701. const compare = requireCompare();
  17702. simplify = (versions, range, options) => {
  17703. const set = [];
  17704. let first = null;
  17705. let prev = null;
  17706. const v = versions.sort((a, b) => compare(a, b, options));
  17707. for (const version of v) {
  17708. const included = satisfies(version, range, options);
  17709. if (included) {
  17710. prev = version;
  17711. if (!first) {
  17712. first = version;
  17713. }
  17714. } else {
  17715. if (prev) {
  17716. set.push([first, prev]);
  17717. }
  17718. prev = null;
  17719. first = null;
  17720. }
  17721. }
  17722. if (first) {
  17723. set.push([first, null]);
  17724. }
  17725. const ranges = [];
  17726. for (const [min, max] of set) {
  17727. if (min === max) {
  17728. ranges.push(min);
  17729. } else if (!max && min === v[0]) {
  17730. ranges.push('*');
  17731. } else if (!max) {
  17732. ranges.push(`>=${min}`);
  17733. } else if (min === v[0]) {
  17734. ranges.push(`<=${max}`);
  17735. } else {
  17736. ranges.push(`${min} - ${max}`);
  17737. }
  17738. }
  17739. const simplified = ranges.join(' || ');
  17740. const original = typeof range.raw === 'string' ? range.raw : String(range);
  17741. return simplified.length < original.length ? simplified : range
  17742. };
  17743. return simplify;
  17744. }
  17745. var subset_1;
  17746. var hasRequiredSubset;
  17747. function requireSubset () {
  17748. if (hasRequiredSubset) return subset_1;
  17749. hasRequiredSubset = 1;
  17750. const Range = requireRange();
  17751. const Comparator = requireComparator();
  17752. const { ANY } = Comparator;
  17753. const satisfies = requireSatisfies();
  17754. const compare = requireCompare();
  17755. // Complex range `r1 || r2 || ...` is a subset of `R1 || R2 || ...` iff:
  17756. // - Every simple range `r1, r2, ...` is a null set, OR
  17757. // - Every simple range `r1, r2, ...` which is not a null set is a subset of
  17758. // some `R1, R2, ...`
  17759. //
  17760. // Simple range `c1 c2 ...` is a subset of simple range `C1 C2 ...` iff:
  17761. // - If c is only the ANY comparator
  17762. // - If C is only the ANY comparator, return true
  17763. // - Else if in prerelease mode, return false
  17764. // - else replace c with `[>=0.0.0]`
  17765. // - If C is only the ANY comparator
  17766. // - if in prerelease mode, return true
  17767. // - else replace C with `[>=0.0.0]`
  17768. // - Let EQ be the set of = comparators in c
  17769. // - If EQ is more than one, return true (null set)
  17770. // - Let GT be the highest > or >= comparator in c
  17771. // - Let LT be the lowest < or <= comparator in c
  17772. // - If GT and LT, and GT.semver > LT.semver, return true (null set)
  17773. // - If any C is a = range, and GT or LT are set, return false
  17774. // - If EQ
  17775. // - If GT, and EQ does not satisfy GT, return true (null set)
  17776. // - If LT, and EQ does not satisfy LT, return true (null set)
  17777. // - If EQ satisfies every C, return true
  17778. // - Else return false
  17779. // - If GT
  17780. // - If GT.semver is lower than any > or >= comp in C, return false
  17781. // - If GT is >=, and GT.semver does not satisfy every C, return false
  17782. // - If GT.semver has a prerelease, and not in prerelease mode
  17783. // - If no C has a prerelease and the GT.semver tuple, return false
  17784. // - If LT
  17785. // - If LT.semver is greater than any < or <= comp in C, return false
  17786. // - If LT is <=, and LT.semver does not satisfy every C, return false
  17787. // - If GT.semver has a prerelease, and not in prerelease mode
  17788. // - If no C has a prerelease and the LT.semver tuple, return false
  17789. // - Else return true
  17790. const subset = (sub, dom, options = {}) => {
  17791. if (sub === dom) {
  17792. return true
  17793. }
  17794. sub = new Range(sub, options);
  17795. dom = new Range(dom, options);
  17796. let sawNonNull = false;
  17797. OUTER: for (const simpleSub of sub.set) {
  17798. for (const simpleDom of dom.set) {
  17799. const isSub = simpleSubset(simpleSub, simpleDom, options);
  17800. sawNonNull = sawNonNull || isSub !== null;
  17801. if (isSub) {
  17802. continue OUTER
  17803. }
  17804. }
  17805. // the null set is a subset of everything, but null simple ranges in
  17806. // a complex range should be ignored. so if we saw a non-null range,
  17807. // then we know this isn't a subset, but if EVERY simple range was null,
  17808. // then it is a subset.
  17809. if (sawNonNull) {
  17810. return false
  17811. }
  17812. }
  17813. return true
  17814. };
  17815. const minimumVersionWithPreRelease = [new Comparator('>=0.0.0-0')];
  17816. const minimumVersion = [new Comparator('>=0.0.0')];
  17817. const simpleSubset = (sub, dom, options) => {
  17818. if (sub === dom) {
  17819. return true
  17820. }
  17821. if (sub.length === 1 && sub[0].semver === ANY) {
  17822. if (dom.length === 1 && dom[0].semver === ANY) {
  17823. return true
  17824. } else if (options.includePrerelease) {
  17825. sub = minimumVersionWithPreRelease;
  17826. } else {
  17827. sub = minimumVersion;
  17828. }
  17829. }
  17830. if (dom.length === 1 && dom[0].semver === ANY) {
  17831. if (options.includePrerelease) {
  17832. return true
  17833. } else {
  17834. dom = minimumVersion;
  17835. }
  17836. }
  17837. const eqSet = new Set();
  17838. let gt, lt;
  17839. for (const c of sub) {
  17840. if (c.operator === '>' || c.operator === '>=') {
  17841. gt = higherGT(gt, c, options);
  17842. } else if (c.operator === '<' || c.operator === '<=') {
  17843. lt = lowerLT(lt, c, options);
  17844. } else {
  17845. eqSet.add(c.semver);
  17846. }
  17847. }
  17848. if (eqSet.size > 1) {
  17849. return null
  17850. }
  17851. let gtltComp;
  17852. if (gt && lt) {
  17853. gtltComp = compare(gt.semver, lt.semver, options);
  17854. if (gtltComp > 0) {
  17855. return null
  17856. } else if (gtltComp === 0 && (gt.operator !== '>=' || lt.operator !== '<=')) {
  17857. return null
  17858. }
  17859. }
  17860. // will iterate one or zero times
  17861. for (const eq of eqSet) {
  17862. if (gt && !satisfies(eq, String(gt), options)) {
  17863. return null
  17864. }
  17865. if (lt && !satisfies(eq, String(lt), options)) {
  17866. return null
  17867. }
  17868. for (const c of dom) {
  17869. if (!satisfies(eq, String(c), options)) {
  17870. return false
  17871. }
  17872. }
  17873. return true
  17874. }
  17875. let higher, lower;
  17876. let hasDomLT, hasDomGT;
  17877. // if the subset has a prerelease, we need a comparator in the superset
  17878. // with the same tuple and a prerelease, or it's not a subset
  17879. let needDomLTPre = lt &&
  17880. !options.includePrerelease &&
  17881. lt.semver.prerelease.length ? lt.semver : false;
  17882. let needDomGTPre = gt &&
  17883. !options.includePrerelease &&
  17884. gt.semver.prerelease.length ? gt.semver : false;
  17885. // exception: <1.2.3-0 is the same as <1.2.3
  17886. if (needDomLTPre && needDomLTPre.prerelease.length === 1 &&
  17887. lt.operator === '<' && needDomLTPre.prerelease[0] === 0) {
  17888. needDomLTPre = false;
  17889. }
  17890. for (const c of dom) {
  17891. hasDomGT = hasDomGT || c.operator === '>' || c.operator === '>=';
  17892. hasDomLT = hasDomLT || c.operator === '<' || c.operator === '<=';
  17893. if (gt) {
  17894. if (needDomGTPre) {
  17895. if (c.semver.prerelease && c.semver.prerelease.length &&
  17896. c.semver.major === needDomGTPre.major &&
  17897. c.semver.minor === needDomGTPre.minor &&
  17898. c.semver.patch === needDomGTPre.patch) {
  17899. needDomGTPre = false;
  17900. }
  17901. }
  17902. if (c.operator === '>' || c.operator === '>=') {
  17903. higher = higherGT(gt, c, options);
  17904. if (higher === c && higher !== gt) {
  17905. return false
  17906. }
  17907. } else if (gt.operator === '>=' && !satisfies(gt.semver, String(c), options)) {
  17908. return false
  17909. }
  17910. }
  17911. if (lt) {
  17912. if (needDomLTPre) {
  17913. if (c.semver.prerelease && c.semver.prerelease.length &&
  17914. c.semver.major === needDomLTPre.major &&
  17915. c.semver.minor === needDomLTPre.minor &&
  17916. c.semver.patch === needDomLTPre.patch) {
  17917. needDomLTPre = false;
  17918. }
  17919. }
  17920. if (c.operator === '<' || c.operator === '<=') {
  17921. lower = lowerLT(lt, c, options);
  17922. if (lower === c && lower !== lt) {
  17923. return false
  17924. }
  17925. } else if (lt.operator === '<=' && !satisfies(lt.semver, String(c), options)) {
  17926. return false
  17927. }
  17928. }
  17929. if (!c.operator && (lt || gt) && gtltComp !== 0) {
  17930. return false
  17931. }
  17932. }
  17933. // if there was a < or >, and nothing in the dom, then must be false
  17934. // UNLESS it was limited by another range in the other direction.
  17935. // Eg, >1.0.0 <1.0.1 is still a subset of <2.0.0
  17936. if (gt && hasDomLT && !lt && gtltComp !== 0) {
  17937. return false
  17938. }
  17939. if (lt && hasDomGT && !gt && gtltComp !== 0) {
  17940. return false
  17941. }
  17942. // we needed a prerelease range in a specific tuple, but didn't get one
  17943. // then this isn't a subset. eg >=1.2.3-pre is not a subset of >=1.0.0,
  17944. // because it includes prereleases in the 1.2.3 tuple
  17945. if (needDomGTPre || needDomLTPre) {
  17946. return false
  17947. }
  17948. return true
  17949. };
  17950. // >=1.2.3 is lower than >1.2.3
  17951. const higherGT = (a, b, options) => {
  17952. if (!a) {
  17953. return b
  17954. }
  17955. const comp = compare(a.semver, b.semver, options);
  17956. return comp > 0 ? a
  17957. : comp < 0 ? b
  17958. : b.operator === '>' && a.operator === '>=' ? b
  17959. : a
  17960. };
  17961. // <=1.2.3 is higher than <1.2.3
  17962. const lowerLT = (a, b, options) => {
  17963. if (!a) {
  17964. return b
  17965. }
  17966. const comp = compare(a.semver, b.semver, options);
  17967. return comp < 0 ? a
  17968. : comp > 0 ? b
  17969. : b.operator === '<' && a.operator === '<=' ? b
  17970. : a
  17971. };
  17972. subset_1 = subset;
  17973. return subset_1;
  17974. }
  17975. var semver$1;
  17976. var hasRequiredSemver;
  17977. function requireSemver () {
  17978. if (hasRequiredSemver) return semver$1;
  17979. hasRequiredSemver = 1;
  17980. // just pre-load all the stuff that index.js lazily exports
  17981. const internalRe = requireRe();
  17982. const constants = requireConstants();
  17983. const SemVer = requireSemver$1();
  17984. const identifiers = requireIdentifiers();
  17985. const parse = requireParse();
  17986. const valid = requireValid$1();
  17987. const clean = requireClean();
  17988. const inc = requireInc();
  17989. const diff = requireDiff();
  17990. const major = requireMajor();
  17991. const minor = requireMinor();
  17992. const patch = requirePatch();
  17993. const prerelease = requirePrerelease();
  17994. const compare = requireCompare();
  17995. const rcompare = requireRcompare();
  17996. const compareLoose = requireCompareLoose();
  17997. const compareBuild = requireCompareBuild();
  17998. const sort = requireSort();
  17999. const rsort = requireRsort();
  18000. const gt = requireGt();
  18001. const lt = requireLt();
  18002. const eq = requireEq();
  18003. const neq = requireNeq();
  18004. const gte = requireGte();
  18005. const lte = requireLte();
  18006. const cmp = requireCmp();
  18007. const coerce = requireCoerce();
  18008. const Comparator = requireComparator();
  18009. const Range = requireRange();
  18010. const satisfies = requireSatisfies();
  18011. const toComparators = requireToComparators();
  18012. const maxSatisfying = requireMaxSatisfying();
  18013. const minSatisfying = requireMinSatisfying();
  18014. const minVersion = requireMinVersion();
  18015. const validRange = requireValid();
  18016. const outside = requireOutside();
  18017. const gtr = requireGtr();
  18018. const ltr = requireLtr();
  18019. const intersects = requireIntersects();
  18020. const simplifyRange = requireSimplify();
  18021. const subset = requireSubset();
  18022. semver$1 = {
  18023. parse,
  18024. valid,
  18025. clean,
  18026. inc,
  18027. diff,
  18028. major,
  18029. minor,
  18030. patch,
  18031. prerelease,
  18032. compare,
  18033. rcompare,
  18034. compareLoose,
  18035. compareBuild,
  18036. sort,
  18037. rsort,
  18038. gt,
  18039. lt,
  18040. eq,
  18041. neq,
  18042. gte,
  18043. lte,
  18044. cmp,
  18045. coerce,
  18046. Comparator,
  18047. Range,
  18048. satisfies,
  18049. toComparators,
  18050. maxSatisfying,
  18051. minSatisfying,
  18052. minVersion,
  18053. validRange,
  18054. outside,
  18055. gtr,
  18056. ltr,
  18057. intersects,
  18058. simplifyRange,
  18059. subset,
  18060. SemVer,
  18061. re: internalRe.re,
  18062. src: internalRe.src,
  18063. tokens: internalRe.t,
  18064. SEMVER_SPEC_VERSION: constants.SEMVER_SPEC_VERSION,
  18065. RELEASE_TYPES: constants.RELEASE_TYPES,
  18066. compareIdentifiers: identifiers.compareIdentifiers,
  18067. rcompareIdentifiers: identifiers.rcompareIdentifiers,
  18068. };
  18069. return semver$1;
  18070. }
  18071. var semverExports = requireSemver();
  18072. var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
  18073. /*!
  18074. * @license
  18075. * Copyright Google LLC All Rights Reserved.
  18076. *
  18077. * Use of this source code is governed by an MIT-style license that can be
  18078. * found in the LICENSE file at https://angular.dev/license
  18079. */
  18080. // Note: semver isn't available internally so this import will be commented out.
  18081. // When adding more dependencies here, the caretaker may have to update a patch internally.
  18082. /**
  18083. * Whether a version of `@angular/core` supports a specific feature.
  18084. * @param coreVersion Current version of core.
  18085. * @param minVersion Minimum required version for the feature.
  18086. */
  18087. function coreVersionSupportsFeature(coreVersion, minVersion) {
  18088. // A version of `19.2.13` usually means that core is at head so it supports
  18089. // all features. Use string interpolation prevent the placeholder from being replaced
  18090. // with the current version during build time.
  18091. if (coreVersion === `0.0.0-${'PLACEHOLDER'}`) {
  18092. return true;
  18093. }
  18094. return semver.satisfies(coreVersion, minVersion, { includePrerelease: true });
  18095. }
  18096. /**
  18097. * Discriminant type for a `CompilationTicket`.
  18098. */
  18099. var CompilationTicketKind;
  18100. (function (CompilationTicketKind) {
  18101. CompilationTicketKind[CompilationTicketKind["Fresh"] = 0] = "Fresh";
  18102. CompilationTicketKind[CompilationTicketKind["IncrementalTypeScript"] = 1] = "IncrementalTypeScript";
  18103. CompilationTicketKind[CompilationTicketKind["IncrementalResource"] = 2] = "IncrementalResource";
  18104. })(CompilationTicketKind || (CompilationTicketKind = {}));
  18105. /**
  18106. * Create a `CompilationTicket` for a brand new compilation, using no prior state.
  18107. */
  18108. function freshCompilationTicket(tsProgram, options, incrementalBuildStrategy, programDriver, perfRecorder, enableTemplateTypeChecker, usePoisonedData) {
  18109. return {
  18110. kind: CompilationTicketKind.Fresh,
  18111. tsProgram,
  18112. options,
  18113. incrementalBuildStrategy,
  18114. programDriver,
  18115. enableTemplateTypeChecker,
  18116. usePoisonedData,
  18117. perfRecorder: perfRecorder ?? ActivePerfRecorder.zeroedToNow(),
  18118. };
  18119. }
  18120. /**
  18121. * Create a `CompilationTicket` as efficiently as possible, based on a previous `NgCompiler`
  18122. * instance and a new `ts.Program`.
  18123. */
  18124. function incrementalFromCompilerTicket(oldCompiler, newProgram, incrementalBuildStrategy, programDriver, modifiedResourceFiles, perfRecorder) {
  18125. const oldProgram = oldCompiler.getCurrentProgram();
  18126. const oldState = oldCompiler.incrementalStrategy.getIncrementalState(oldProgram);
  18127. if (oldState === null) {
  18128. // No incremental step is possible here, since no IncrementalState was found for the old
  18129. // program.
  18130. return freshCompilationTicket(newProgram, oldCompiler.options, incrementalBuildStrategy, programDriver, perfRecorder, oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData);
  18131. }
  18132. if (perfRecorder === null) {
  18133. perfRecorder = ActivePerfRecorder.zeroedToNow();
  18134. }
  18135. const incrementalCompilation = IncrementalCompilation.incremental(newProgram, versionMapFromProgram(newProgram, programDriver), oldProgram, oldState, modifiedResourceFiles, perfRecorder);
  18136. return {
  18137. kind: CompilationTicketKind.IncrementalTypeScript,
  18138. enableTemplateTypeChecker: oldCompiler.enableTemplateTypeChecker,
  18139. usePoisonedData: oldCompiler.usePoisonedData,
  18140. options: oldCompiler.options,
  18141. incrementalBuildStrategy,
  18142. incrementalCompilation,
  18143. programDriver,
  18144. newProgram,
  18145. perfRecorder,
  18146. };
  18147. }
  18148. /**
  18149. * The heart of the Angular Ivy compiler.
  18150. *
  18151. * The `NgCompiler` provides an API for performing Angular compilation within a custom TypeScript
  18152. * compiler. Each instance of `NgCompiler` supports a single compilation, which might be
  18153. * incremental.
  18154. *
  18155. * `NgCompiler` is lazy, and does not perform any of the work of the compilation until one of its
  18156. * output methods (e.g. `getDiagnostics`) is called.
  18157. *
  18158. * See the README.md for more information.
  18159. */
  18160. class NgCompiler {
  18161. adapter;
  18162. options;
  18163. inputProgram;
  18164. programDriver;
  18165. incrementalStrategy;
  18166. incrementalCompilation;
  18167. usePoisonedData;
  18168. livePerfRecorder;
  18169. /**
  18170. * Lazily evaluated state of the compilation.
  18171. *
  18172. * This is created on demand by calling `ensureAnalyzed`.
  18173. */
  18174. compilation = null;
  18175. /**
  18176. * Any diagnostics related to the construction of the compilation.
  18177. *
  18178. * These are diagnostics which arose during setup of the host and/or program.
  18179. */
  18180. constructionDiagnostics = [];
  18181. /**
  18182. * Non-template diagnostics related to the program itself. Does not include template
  18183. * diagnostics because the template type checker memoizes them itself.
  18184. *
  18185. * This is set by (and memoizes) `getNonTemplateDiagnostics`.
  18186. */
  18187. nonTemplateDiagnostics = null;
  18188. closureCompilerEnabled;
  18189. currentProgram;
  18190. entryPoint;
  18191. moduleResolver;
  18192. resourceManager;
  18193. cycleAnalyzer;
  18194. ignoreForDiagnostics;
  18195. ignoreForEmit;
  18196. enableTemplateTypeChecker;
  18197. enableBlockSyntax;
  18198. enableLetSyntax;
  18199. angularCoreVersion;
  18200. enableHmr;
  18201. implicitStandaloneValue;
  18202. /**
  18203. * `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
  18204. * new compilation uses a fresh `PerfRecorder`. Thus, classes created with a lifespan of the
  18205. * `NgCompiler` use a `DelegatingPerfRecorder` so the `PerfRecorder` they write to can be updated
  18206. * with each fresh compilation.
  18207. */
  18208. delegatingPerfRecorder;
  18209. /**
  18210. * Convert a `CompilationTicket` into an `NgCompiler` instance for the requested compilation.
  18211. *
  18212. * Depending on the nature of the compilation request, the `NgCompiler` instance may be reused
  18213. * from a previous compilation and updated with any changes, it may be a new instance which
  18214. * incrementally reuses state from a previous compilation, or it may represent a fresh
  18215. * compilation entirely.
  18216. */
  18217. static fromTicket(ticket, adapter) {
  18218. switch (ticket.kind) {
  18219. case CompilationTicketKind.Fresh:
  18220. return new NgCompiler(adapter, ticket.options, ticket.tsProgram, ticket.programDriver, ticket.incrementalBuildStrategy, IncrementalCompilation.fresh(ticket.tsProgram, versionMapFromProgram(ticket.tsProgram, ticket.programDriver)), ticket.enableTemplateTypeChecker, ticket.usePoisonedData, ticket.perfRecorder);
  18221. case CompilationTicketKind.IncrementalTypeScript:
  18222. return new NgCompiler(adapter, ticket.options, ticket.newProgram, ticket.programDriver, ticket.incrementalBuildStrategy, ticket.incrementalCompilation, ticket.enableTemplateTypeChecker, ticket.usePoisonedData, ticket.perfRecorder);
  18223. case CompilationTicketKind.IncrementalResource:
  18224. const compiler = ticket.compiler;
  18225. compiler.updateWithChangedResources(ticket.modifiedResourceFiles, ticket.perfRecorder);
  18226. return compiler;
  18227. }
  18228. }
  18229. constructor(adapter, options, inputProgram, programDriver, incrementalStrategy, incrementalCompilation, enableTemplateTypeChecker, usePoisonedData, livePerfRecorder) {
  18230. this.adapter = adapter;
  18231. this.options = options;
  18232. this.inputProgram = inputProgram;
  18233. this.programDriver = programDriver;
  18234. this.incrementalStrategy = incrementalStrategy;
  18235. this.incrementalCompilation = incrementalCompilation;
  18236. this.usePoisonedData = usePoisonedData;
  18237. this.livePerfRecorder = livePerfRecorder;
  18238. this.angularCoreVersion = options['_angularCoreVersion'] ?? null;
  18239. this.delegatingPerfRecorder = new DelegatingPerfRecorder(this.perfRecorder);
  18240. this.usePoisonedData = usePoisonedData || !!options._compilePoisonedComponents;
  18241. this.enableTemplateTypeChecker =
  18242. enableTemplateTypeChecker || !!options._enableTemplateTypeChecker;
  18243. // TODO(crisbeto): remove this flag and base `enableBlockSyntax` on the `angularCoreVersion`.
  18244. this.enableBlockSyntax = options['_enableBlockSyntax'] ?? true;
  18245. this.enableLetSyntax = options['_enableLetSyntax'] ?? true;
  18246. // Standalone by default is enabled since v19. We need to toggle it here,
  18247. // because the language service extension may be running with the latest
  18248. // version of the compiler against an older version of Angular.
  18249. this.implicitStandaloneValue =
  18250. this.angularCoreVersion === null ||
  18251. coreVersionSupportsFeature(this.angularCoreVersion, '>= 19.0.0');
  18252. this.enableHmr = !!options['_enableHmr'];
  18253. this.constructionDiagnostics.push(...this.adapter.constructionDiagnostics, ...verifyCompatibleTypeCheckOptions(this.options));
  18254. this.currentProgram = inputProgram;
  18255. this.closureCompilerEnabled = !!this.options.annotateForClosureCompiler;
  18256. this.entryPoint =
  18257. adapter.entryPoint !== null ? checker.getSourceFileOrNull(inputProgram, adapter.entryPoint) : null;
  18258. const moduleResolutionCache = ts.createModuleResolutionCache(this.adapter.getCurrentDirectory(),
  18259. // doen't retain a reference to `this`, if other closures in the constructor here reference
  18260. // `this` internally then a closure created here would retain them. This can cause major
  18261. // memory leak issues since the `moduleResolutionCache` is a long-lived object and finds its
  18262. // way into all kinds of places inside TS internal objects.
  18263. this.adapter.getCanonicalFileName.bind(this.adapter));
  18264. this.moduleResolver = new ModuleResolver(inputProgram, this.options, this.adapter, moduleResolutionCache);
  18265. this.resourceManager = new AdapterResourceLoader(adapter, this.options);
  18266. this.cycleAnalyzer = new CycleAnalyzer(new ImportGraph(inputProgram.getTypeChecker(), this.delegatingPerfRecorder));
  18267. this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, inputProgram);
  18268. this.ignoreForDiagnostics = new Set(inputProgram.getSourceFiles().filter((sf) => this.adapter.isShim(sf)));
  18269. this.ignoreForEmit = this.adapter.ignoreForEmit;
  18270. let dtsFileCount = 0;
  18271. let nonDtsFileCount = 0;
  18272. for (const sf of inputProgram.getSourceFiles()) {
  18273. if (sf.isDeclarationFile) {
  18274. dtsFileCount++;
  18275. }
  18276. else {
  18277. nonDtsFileCount++;
  18278. }
  18279. }
  18280. livePerfRecorder.eventCount(checker.PerfEvent.InputDtsFile, dtsFileCount);
  18281. livePerfRecorder.eventCount(checker.PerfEvent.InputTsFile, nonDtsFileCount);
  18282. }
  18283. get perfRecorder() {
  18284. return this.livePerfRecorder;
  18285. }
  18286. updateWithChangedResources(changedResources, perfRecorder) {
  18287. this.livePerfRecorder = perfRecorder;
  18288. this.delegatingPerfRecorder.target = perfRecorder;
  18289. perfRecorder.inPhase(checker.PerfPhase.ResourceUpdate, () => {
  18290. if (this.compilation === null) {
  18291. // Analysis hasn't happened yet, so no update is necessary - any changes to resources will
  18292. // be captured by the initial analysis pass itself.
  18293. return;
  18294. }
  18295. this.resourceManager.invalidate();
  18296. const classesToUpdate = new Set();
  18297. for (const resourceFile of changedResources) {
  18298. for (const templateClass of this.getComponentsWithTemplateFile(resourceFile)) {
  18299. classesToUpdate.add(templateClass);
  18300. }
  18301. for (const styleClass of this.getComponentsWithStyleFile(resourceFile)) {
  18302. classesToUpdate.add(styleClass);
  18303. }
  18304. }
  18305. for (const clazz of classesToUpdate) {
  18306. this.compilation.traitCompiler.updateResources(clazz);
  18307. if (!ts.isClassDeclaration(clazz)) {
  18308. continue;
  18309. }
  18310. this.compilation.templateTypeChecker.invalidateClass(clazz);
  18311. }
  18312. });
  18313. }
  18314. /**
  18315. * Get the resource dependencies of a file.
  18316. *
  18317. * If the file is not part of the compilation, an empty array will be returned.
  18318. */
  18319. getResourceDependencies(file) {
  18320. this.ensureAnalyzed();
  18321. return this.incrementalCompilation.depGraph.getResourceDependencies(file);
  18322. }
  18323. /**
  18324. * Get all Angular-related diagnostics for this compilation.
  18325. */
  18326. getDiagnostics() {
  18327. const diagnostics = [...this.getNonTemplateDiagnostics()];
  18328. // Type check code may throw fatal diagnostic errors if e.g. the type check
  18329. // block cannot be generated. Gracefully return the associated diagnostic.
  18330. // Note: If a fatal diagnostic is raised, do not repeat the same diagnostics
  18331. // by running the extended template checking code, which will attempt to
  18332. // generate the same TCB.
  18333. try {
  18334. diagnostics.push(...this.getTemplateDiagnostics(), ...this.runAdditionalChecks());
  18335. }
  18336. catch (err) {
  18337. if (!checker.isFatalDiagnosticError(err)) {
  18338. throw err;
  18339. }
  18340. diagnostics.push(err.toDiagnostic());
  18341. }
  18342. return this.addMessageTextDetails(diagnostics);
  18343. }
  18344. /**
  18345. * Get all Angular-related diagnostics for this compilation.
  18346. *
  18347. * If a `ts.SourceFile` is passed, only diagnostics related to that file are returned.
  18348. */
  18349. getDiagnosticsForFile(file, optimizeFor) {
  18350. const diagnostics = [
  18351. ...this.getNonTemplateDiagnostics().filter((diag) => diag.file === file),
  18352. ];
  18353. // Type check code may throw fatal diagnostic errors if e.g. the type check
  18354. // block cannot be generated. Gracefully return the associated diagnostic.
  18355. // Note: If a fatal diagnostic is raised, do not repeat the same diagnostics
  18356. // by running the extended template checking code, which will attempt to
  18357. // generate the same TCB.
  18358. try {
  18359. diagnostics.push(...this.getTemplateDiagnosticsForFile(file, optimizeFor), ...this.runAdditionalChecks(file));
  18360. }
  18361. catch (err) {
  18362. if (!checker.isFatalDiagnosticError(err)) {
  18363. throw err;
  18364. }
  18365. diagnostics.push(err.toDiagnostic());
  18366. }
  18367. return this.addMessageTextDetails(diagnostics);
  18368. }
  18369. /**
  18370. * Get all `ts.Diagnostic`s currently available that pertain to the given component.
  18371. */
  18372. getDiagnosticsForComponent(component) {
  18373. const compilation = this.ensureAnalyzed();
  18374. const ttc = compilation.templateTypeChecker;
  18375. const diagnostics = [];
  18376. // Type check code may throw fatal diagnostic errors if e.g. the type check
  18377. // block cannot be generated. Gracefully return the associated diagnostic.
  18378. // Note: If a fatal diagnostic is raised, do not repeat the same diagnostics
  18379. // by running the extended template checking code, which will attempt to
  18380. // generate the same TCB.
  18381. try {
  18382. diagnostics.push(...ttc.getDiagnosticsForComponent(component));
  18383. const { extendedTemplateChecker, templateSemanticsChecker } = compilation;
  18384. if (templateSemanticsChecker !== null) {
  18385. diagnostics.push(...templateSemanticsChecker.getDiagnosticsForComponent(component));
  18386. }
  18387. if (this.options.strictTemplates && extendedTemplateChecker !== null) {
  18388. diagnostics.push(...extendedTemplateChecker.getDiagnosticsForComponent(component));
  18389. }
  18390. }
  18391. catch (err) {
  18392. if (!checker.isFatalDiagnosticError(err)) {
  18393. throw err;
  18394. }
  18395. diagnostics.push(err.toDiagnostic());
  18396. }
  18397. return this.addMessageTextDetails(diagnostics);
  18398. }
  18399. /**
  18400. * Add Angular.io error guide links to diagnostics for this compilation.
  18401. */
  18402. addMessageTextDetails(diagnostics) {
  18403. return diagnostics.map((diag) => {
  18404. if (diag.code && checker.COMPILER_ERRORS_WITH_GUIDES.has(checker.ngErrorCode(diag.code))) {
  18405. return {
  18406. ...diag,
  18407. messageText: diag.messageText +
  18408. `. Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/NG${checker.ngErrorCode(diag.code)}`,
  18409. };
  18410. }
  18411. return diag;
  18412. });
  18413. }
  18414. /**
  18415. * Get all setup-related diagnostics for this compilation.
  18416. */
  18417. getOptionDiagnostics() {
  18418. return this.constructionDiagnostics;
  18419. }
  18420. /**
  18421. * Get the current `ts.Program` known to this `NgCompiler`.
  18422. *
  18423. * Compilation begins with an input `ts.Program`, and during template type-checking operations new
  18424. * `ts.Program`s may be produced using the `ProgramDriver`. The most recent such `ts.Program` to
  18425. * be produced is available here.
  18426. *
  18427. * This `ts.Program` serves two key purposes:
  18428. *
  18429. * * As an incremental starting point for creating the next `ts.Program` based on files that the
  18430. * user has changed (for clients using the TS compiler program APIs).
  18431. *
  18432. * * As the "before" point for an incremental compilation invocation, to determine what's changed
  18433. * between the old and new programs (for all compilations).
  18434. */
  18435. getCurrentProgram() {
  18436. return this.currentProgram;
  18437. }
  18438. getTemplateTypeChecker() {
  18439. if (!this.enableTemplateTypeChecker) {
  18440. throw new Error('The `TemplateTypeChecker` does not work without `enableTemplateTypeChecker`.');
  18441. }
  18442. return this.ensureAnalyzed().templateTypeChecker;
  18443. }
  18444. /**
  18445. * Retrieves the `ts.Declaration`s for any component(s) which use the given template file.
  18446. */
  18447. getComponentsWithTemplateFile(templateFilePath) {
  18448. const { resourceRegistry } = this.ensureAnalyzed();
  18449. return resourceRegistry.getComponentsWithTemplate(checker.resolve(templateFilePath));
  18450. }
  18451. /**
  18452. * Retrieves the `ts.Declaration`s for any component(s) which use the given template file.
  18453. */
  18454. getComponentsWithStyleFile(styleFilePath) {
  18455. const { resourceRegistry } = this.ensureAnalyzed();
  18456. return resourceRegistry.getComponentsWithStyle(checker.resolve(styleFilePath));
  18457. }
  18458. /**
  18459. * Retrieves external resources for the given directive.
  18460. */
  18461. getDirectiveResources(classDecl) {
  18462. if (!checker.isNamedClassDeclaration(classDecl)) {
  18463. return null;
  18464. }
  18465. const { resourceRegistry } = this.ensureAnalyzed();
  18466. const styles = resourceRegistry.getStyles(classDecl);
  18467. const template = resourceRegistry.getTemplate(classDecl);
  18468. return { styles, template };
  18469. }
  18470. getMeta(classDecl) {
  18471. if (!checker.isNamedClassDeclaration(classDecl)) {
  18472. return null;
  18473. }
  18474. const ref = new checker.Reference(classDecl);
  18475. const { metaReader } = this.ensureAnalyzed();
  18476. const meta = metaReader.getPipeMetadata(ref) ?? metaReader.getDirectiveMetadata(ref);
  18477. if (meta === null) {
  18478. return null;
  18479. }
  18480. return meta;
  18481. }
  18482. /**
  18483. * Perform Angular's analysis step (as a precursor to `getDiagnostics` or `prepareEmit`)
  18484. * asynchronously.
  18485. *
  18486. * Normally, this operation happens lazily whenever `getDiagnostics` or `prepareEmit` are called.
  18487. * However, certain consumers may wish to allow for an asynchronous phase of analysis, where
  18488. * resources such as `styleUrls` are resolved asynchronously. In these cases `analyzeAsync` must
  18489. * be called first, and its `Promise` awaited prior to calling any other APIs of `NgCompiler`.
  18490. */
  18491. async analyzeAsync() {
  18492. if (this.compilation !== null) {
  18493. return;
  18494. }
  18495. await this.perfRecorder.inPhase(checker.PerfPhase.Analysis, async () => {
  18496. this.compilation = this.makeCompilation();
  18497. const promises = [];
  18498. for (const sf of this.inputProgram.getSourceFiles()) {
  18499. if (sf.isDeclarationFile) {
  18500. continue;
  18501. }
  18502. let analysisPromise = this.compilation.traitCompiler.analyzeAsync(sf);
  18503. if (analysisPromise !== undefined) {
  18504. promises.push(analysisPromise);
  18505. }
  18506. }
  18507. await Promise.all(promises);
  18508. this.perfRecorder.memory(checker.PerfCheckpoint.Analysis);
  18509. this.resolveCompilation(this.compilation.traitCompiler);
  18510. });
  18511. }
  18512. /**
  18513. * Fetch transformers and other information which is necessary for a consumer to `emit` the
  18514. * program with Angular-added definitions.
  18515. */
  18516. prepareEmit() {
  18517. const compilation = this.ensureAnalyzed();
  18518. // Untag all the files, otherwise TS 5.4 may end up emitting
  18519. // references to typecheck files (see #56945 and #57135).
  18520. checker.untagAllTsFiles(this.inputProgram);
  18521. const coreImportsFrom = compilation.isCore ? getR3SymbolsFile(this.inputProgram) : null;
  18522. let importRewriter;
  18523. if (coreImportsFrom !== null) {
  18524. importRewriter = new R3SymbolsImportRewriter(coreImportsFrom.fileName);
  18525. }
  18526. else {
  18527. importRewriter = new NoopImportRewriter();
  18528. }
  18529. const defaultImportTracker = new checker.DefaultImportTracker();
  18530. const before = [
  18531. ivyTransformFactory(compilation.traitCompiler, compilation.reflector, importRewriter, defaultImportTracker, compilation.localCompilationExtraImportsTracker, this.delegatingPerfRecorder, compilation.isCore, this.closureCompilerEnabled),
  18532. aliasTransformFactory(compilation.traitCompiler.exportStatements),
  18533. defaultImportTracker.importPreservingTransformer(),
  18534. ];
  18535. // If there are JIT declarations, wire up the JIT transform and efficiently
  18536. // run it against the target declarations.
  18537. if (compilation.supportJitMode && compilation.jitDeclarationRegistry.jitDeclarations.size > 0) {
  18538. const { jitDeclarations } = compilation.jitDeclarationRegistry;
  18539. const jitDeclarationsArray = Array.from(jitDeclarations);
  18540. const jitDeclarationOriginalNodes = new Set(jitDeclarationsArray.map((d) => ts.getOriginalNode(d)));
  18541. const sourceFilesWithJit = new Set(jitDeclarationsArray.map((d) => d.getSourceFile().fileName));
  18542. before.push((ctx) => {
  18543. const reflectionHost = new checker.TypeScriptReflectionHost(this.inputProgram.getTypeChecker());
  18544. const jitTransform = angularJitApplicationTransform(this.inputProgram, compilation.isCore, (node) => {
  18545. // Class may be synthetic at this point due to Ivy transform.
  18546. node = ts.getOriginalNode(node, ts.isClassDeclaration);
  18547. return reflectionHost.isClass(node) && jitDeclarationOriginalNodes.has(node);
  18548. })(ctx);
  18549. return (sourceFile) => {
  18550. if (!sourceFilesWithJit.has(sourceFile.fileName)) {
  18551. return sourceFile;
  18552. }
  18553. return jitTransform(sourceFile);
  18554. };
  18555. });
  18556. }
  18557. const afterDeclarations = [];
  18558. // In local compilation mode we don't make use of .d.ts files for Angular compilation, so their
  18559. // transformation can be ditched.
  18560. if (this.options.compilationMode !== 'experimental-local' &&
  18561. compilation.dtsTransforms !== null) {
  18562. afterDeclarations.push(declarationTransformFactory(compilation.dtsTransforms, compilation.reflector, compilation.refEmitter, importRewriter));
  18563. }
  18564. // Only add aliasing re-exports to the .d.ts output if the `AliasingHost` requests it.
  18565. if (compilation.aliasingHost !== null && compilation.aliasingHost.aliasExportsInDts) {
  18566. afterDeclarations.push(aliasTransformFactory(compilation.traitCompiler.exportStatements));
  18567. }
  18568. return { transformers: { before, afterDeclarations } };
  18569. }
  18570. /**
  18571. * Run the indexing process and return a `Map` of all indexed components.
  18572. *
  18573. * See the `indexing` package for more details.
  18574. */
  18575. getIndexedComponents() {
  18576. const compilation = this.ensureAnalyzed();
  18577. const context = new IndexingContext();
  18578. compilation.traitCompiler.index(context);
  18579. return generateAnalysis(context);
  18580. }
  18581. /**
  18582. * Gets information for the current program that may be used to generate API
  18583. * reference documentation. This includes Angular-specific information, such
  18584. * as component inputs and outputs.
  18585. *
  18586. * @param entryPoint Path to the entry point for the package for which API
  18587. * docs should be extracted.
  18588. *
  18589. * @returns A map of symbols with their associated module, eg: ApplicationRef => @angular/core
  18590. */
  18591. getApiDocumentation(entryPoint, privateModules) {
  18592. const compilation = this.ensureAnalyzed();
  18593. const checker = this.inputProgram.getTypeChecker();
  18594. const docsExtractor = new DocsExtractor(checker, compilation.metaReader);
  18595. const entryPointSourceFile = this.inputProgram.getSourceFiles().find((sourceFile) => {
  18596. // TODO: this will need to be more specific than `.includes`, but the exact path comparison
  18597. // will be easier to figure out when the pipeline is running end-to-end.
  18598. return sourceFile.fileName.includes(entryPoint);
  18599. });
  18600. if (!entryPointSourceFile) {
  18601. throw new Error(`Entry point "${entryPoint}" not found in program sources.`);
  18602. }
  18603. // TODO: Technically the current directory is not the root dir.
  18604. // Should probably be derived from the config.
  18605. const rootDir = this.inputProgram.getCurrentDirectory();
  18606. return docsExtractor.extractAll(entryPointSourceFile, rootDir, privateModules);
  18607. }
  18608. /**
  18609. * Collect i18n messages into the `Xi18nContext`.
  18610. */
  18611. xi18n(ctx) {
  18612. // Note that the 'resolve' phase is not strictly necessary for xi18n, but this is not currently
  18613. // optimized.
  18614. const compilation = this.ensureAnalyzed();
  18615. compilation.traitCompiler.xi18n(ctx);
  18616. }
  18617. /**
  18618. * Emits the JavaScript module that can be used to replace the metadata of a class during HMR.
  18619. * @param node Class for which to generate the update module.
  18620. */
  18621. emitHmrUpdateModule(node) {
  18622. const { traitCompiler, reflector } = this.ensureAnalyzed();
  18623. if (!reflector.isClass(node)) {
  18624. return null;
  18625. }
  18626. const callback = traitCompiler.compileHmrUpdateCallback(node);
  18627. if (callback === null) {
  18628. return null;
  18629. }
  18630. const sourceFile = node.getSourceFile();
  18631. const printer = ts.createPrinter();
  18632. const nodeText = printer.printNode(ts.EmitHint.Unspecified, callback, sourceFile);
  18633. return ts.transpileModule(nodeText, {
  18634. compilerOptions: {
  18635. ...this.options,
  18636. // Some module types can produce additional code (see #60795) whereas we need the
  18637. // HMR update module to use a native `export`. Override the `target` and `module`
  18638. // to ensure that it looks as expected.
  18639. module: ts.ModuleKind.ES2022,
  18640. target: ts.ScriptTarget.ES2022,
  18641. },
  18642. fileName: sourceFile.fileName,
  18643. reportDiagnostics: false,
  18644. }).outputText;
  18645. }
  18646. ensureAnalyzed() {
  18647. if (this.compilation === null) {
  18648. this.analyzeSync();
  18649. }
  18650. return this.compilation;
  18651. }
  18652. analyzeSync() {
  18653. this.perfRecorder.inPhase(checker.PerfPhase.Analysis, () => {
  18654. this.compilation = this.makeCompilation();
  18655. for (const sf of this.inputProgram.getSourceFiles()) {
  18656. if (sf.isDeclarationFile) {
  18657. continue;
  18658. }
  18659. this.compilation.traitCompiler.analyzeSync(sf);
  18660. }
  18661. this.perfRecorder.memory(checker.PerfCheckpoint.Analysis);
  18662. this.resolveCompilation(this.compilation.traitCompiler);
  18663. });
  18664. }
  18665. resolveCompilation(traitCompiler) {
  18666. this.perfRecorder.inPhase(checker.PerfPhase.Resolve, () => {
  18667. traitCompiler.resolve();
  18668. // At this point, analysis is complete and the compiler can now calculate which files need to
  18669. // be emitted, so do that.
  18670. this.incrementalCompilation.recordSuccessfulAnalysis(traitCompiler);
  18671. this.perfRecorder.memory(checker.PerfCheckpoint.Resolve);
  18672. });
  18673. }
  18674. get fullTemplateTypeCheck() {
  18675. // Determine the strictness level of type checking based on compiler options. As
  18676. // `strictTemplates` is a superset of `fullTemplateTypeCheck`, the former implies the latter.
  18677. // Also see `verifyCompatibleTypeCheckOptions` where it is verified that `fullTemplateTypeCheck`
  18678. // is not disabled when `strictTemplates` is enabled.
  18679. const strictTemplates = !!this.options.strictTemplates;
  18680. return strictTemplates || !!this.options.fullTemplateTypeCheck;
  18681. }
  18682. getTypeCheckingConfig() {
  18683. // Determine the strictness level of type checking based on compiler options. As
  18684. // `strictTemplates` is a superset of `fullTemplateTypeCheck`, the former implies the latter.
  18685. // Also see `verifyCompatibleTypeCheckOptions` where it is verified that `fullTemplateTypeCheck`
  18686. // is not disabled when `strictTemplates` is enabled.
  18687. const strictTemplates = !!this.options.strictTemplates;
  18688. const useInlineTypeConstructors = this.programDriver.supportsInlineOperations;
  18689. const checkTwoWayBoundEvents = this.options['_checkTwoWayBoundEvents'] ?? false;
  18690. // Check whether the loaded version of `@angular/core` in the `ts.Program` supports unwrapping
  18691. // writable signals for type-checking. If this check fails to find a suitable .d.ts file, fall
  18692. // back to version detection. Only Angular versions greater than 17.2 have the necessary symbols
  18693. // to type check signals in two-way bindings. We also allow version 0.0.0 in case somebody is
  18694. // using Angular at head.
  18695. let allowSignalsInTwoWayBindings = coreHasSymbol(this.inputProgram, checker.Identifiers.unwrapWritableSignal) ??
  18696. (this.angularCoreVersion === null ||
  18697. coreVersionSupportsFeature(this.angularCoreVersion, '>= 17.2.0'));
  18698. // First select a type-checking configuration, based on whether full template type-checking is
  18699. // requested.
  18700. let typeCheckingConfig;
  18701. if (this.fullTemplateTypeCheck) {
  18702. typeCheckingConfig = {
  18703. applyTemplateContextGuards: strictTemplates,
  18704. checkQueries: false,
  18705. checkTemplateBodies: true,
  18706. alwaysCheckSchemaInTemplateBodies: true,
  18707. checkTypeOfInputBindings: strictTemplates,
  18708. honorAccessModifiersForInputBindings: false,
  18709. checkControlFlowBodies: true,
  18710. strictNullInputBindings: strictTemplates,
  18711. checkTypeOfAttributes: strictTemplates,
  18712. // Even in full template type-checking mode, DOM binding checks are not quite ready yet.
  18713. checkTypeOfDomBindings: false,
  18714. checkTypeOfOutputEvents: strictTemplates,
  18715. checkTypeOfAnimationEvents: strictTemplates,
  18716. // Checking of DOM events currently has an adverse effect on developer experience,
  18717. // e.g. for `<input (blur)="update($event.target.value)">` enabling this check results in:
  18718. // - error TS2531: Object is possibly 'null'.
  18719. // - error TS2339: Property 'value' does not exist on type 'EventTarget'.
  18720. checkTypeOfDomEvents: strictTemplates,
  18721. checkTypeOfDomReferences: strictTemplates,
  18722. // Non-DOM references have the correct type in View Engine so there is no strictness flag.
  18723. checkTypeOfNonDomReferences: true,
  18724. // Pipes are checked in View Engine so there is no strictness flag.
  18725. checkTypeOfPipes: true,
  18726. strictSafeNavigationTypes: strictTemplates,
  18727. useContextGenericType: strictTemplates,
  18728. strictLiteralTypes: true,
  18729. enableTemplateTypeChecker: this.enableTemplateTypeChecker,
  18730. useInlineTypeConstructors,
  18731. // Warnings for suboptimal type inference are only enabled if in Language Service mode
  18732. // (providing the full TemplateTypeChecker API) and if strict mode is not enabled. In strict
  18733. // mode, the user is in full control of type inference.
  18734. suggestionsForSuboptimalTypeInference: this.enableTemplateTypeChecker && !strictTemplates,
  18735. controlFlowPreventingContentProjection: this.options.extendedDiagnostics?.defaultCategory || exports.DiagnosticCategoryLabel.Warning,
  18736. unusedStandaloneImports: this.options.extendedDiagnostics?.defaultCategory || exports.DiagnosticCategoryLabel.Warning,
  18737. allowSignalsInTwoWayBindings,
  18738. checkTwoWayBoundEvents,
  18739. };
  18740. }
  18741. else {
  18742. typeCheckingConfig = {
  18743. applyTemplateContextGuards: false,
  18744. checkQueries: false,
  18745. checkTemplateBodies: false,
  18746. checkControlFlowBodies: false,
  18747. // Enable deep schema checking in "basic" template type-checking mode only if Closure
  18748. // compilation is requested, which is a good proxy for "only in google3".
  18749. alwaysCheckSchemaInTemplateBodies: this.closureCompilerEnabled,
  18750. checkTypeOfInputBindings: false,
  18751. strictNullInputBindings: false,
  18752. honorAccessModifiersForInputBindings: false,
  18753. checkTypeOfAttributes: false,
  18754. checkTypeOfDomBindings: false,
  18755. checkTypeOfOutputEvents: false,
  18756. checkTypeOfAnimationEvents: false,
  18757. checkTypeOfDomEvents: false,
  18758. checkTypeOfDomReferences: false,
  18759. checkTypeOfNonDomReferences: false,
  18760. checkTypeOfPipes: false,
  18761. strictSafeNavigationTypes: false,
  18762. useContextGenericType: false,
  18763. strictLiteralTypes: false,
  18764. enableTemplateTypeChecker: this.enableTemplateTypeChecker,
  18765. useInlineTypeConstructors,
  18766. // In "basic" template type-checking mode, no warnings are produced since most things are
  18767. // not checked anyways.
  18768. suggestionsForSuboptimalTypeInference: false,
  18769. controlFlowPreventingContentProjection: this.options.extendedDiagnostics?.defaultCategory || exports.DiagnosticCategoryLabel.Warning,
  18770. unusedStandaloneImports: this.options.extendedDiagnostics?.defaultCategory || exports.DiagnosticCategoryLabel.Warning,
  18771. allowSignalsInTwoWayBindings,
  18772. checkTwoWayBoundEvents,
  18773. };
  18774. }
  18775. // Apply explicitly configured strictness flags on top of the default configuration
  18776. // based on "fullTemplateTypeCheck".
  18777. if (this.options.strictInputTypes !== undefined) {
  18778. typeCheckingConfig.checkTypeOfInputBindings = this.options.strictInputTypes;
  18779. typeCheckingConfig.applyTemplateContextGuards = this.options.strictInputTypes;
  18780. }
  18781. if (this.options.strictInputAccessModifiers !== undefined) {
  18782. typeCheckingConfig.honorAccessModifiersForInputBindings =
  18783. this.options.strictInputAccessModifiers;
  18784. }
  18785. if (this.options.strictNullInputTypes !== undefined) {
  18786. typeCheckingConfig.strictNullInputBindings = this.options.strictNullInputTypes;
  18787. }
  18788. if (this.options.strictOutputEventTypes !== undefined) {
  18789. typeCheckingConfig.checkTypeOfOutputEvents = this.options.strictOutputEventTypes;
  18790. typeCheckingConfig.checkTypeOfAnimationEvents = this.options.strictOutputEventTypes;
  18791. }
  18792. if (this.options.strictDomEventTypes !== undefined) {
  18793. typeCheckingConfig.checkTypeOfDomEvents = this.options.strictDomEventTypes;
  18794. }
  18795. if (this.options.strictSafeNavigationTypes !== undefined) {
  18796. typeCheckingConfig.strictSafeNavigationTypes = this.options.strictSafeNavigationTypes;
  18797. }
  18798. if (this.options.strictDomLocalRefTypes !== undefined) {
  18799. typeCheckingConfig.checkTypeOfDomReferences = this.options.strictDomLocalRefTypes;
  18800. }
  18801. if (this.options.strictAttributeTypes !== undefined) {
  18802. typeCheckingConfig.checkTypeOfAttributes = this.options.strictAttributeTypes;
  18803. }
  18804. if (this.options.strictContextGenerics !== undefined) {
  18805. typeCheckingConfig.useContextGenericType = this.options.strictContextGenerics;
  18806. }
  18807. if (this.options.strictLiteralTypes !== undefined) {
  18808. typeCheckingConfig.strictLiteralTypes = this.options.strictLiteralTypes;
  18809. }
  18810. if (this.options.extendedDiagnostics?.checks?.controlFlowPreventingContentProjection !== undefined) {
  18811. typeCheckingConfig.controlFlowPreventingContentProjection =
  18812. this.options.extendedDiagnostics.checks.controlFlowPreventingContentProjection;
  18813. }
  18814. if (this.options.extendedDiagnostics?.checks?.unusedStandaloneImports !== undefined) {
  18815. typeCheckingConfig.unusedStandaloneImports =
  18816. this.options.extendedDiagnostics.checks.unusedStandaloneImports;
  18817. }
  18818. return typeCheckingConfig;
  18819. }
  18820. getTemplateDiagnostics() {
  18821. const compilation = this.ensureAnalyzed();
  18822. const diagnostics = [];
  18823. // Get diagnostics for all files.
  18824. for (const sf of this.inputProgram.getSourceFiles()) {
  18825. if (sf.isDeclarationFile || this.adapter.isShim(sf)) {
  18826. continue;
  18827. }
  18828. diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, checker.OptimizeFor.WholeProgram));
  18829. }
  18830. const program = this.programDriver.getProgram();
  18831. this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, program);
  18832. this.currentProgram = program;
  18833. return diagnostics;
  18834. }
  18835. getTemplateDiagnosticsForFile(sf, optimizeFor) {
  18836. const compilation = this.ensureAnalyzed();
  18837. // Get the diagnostics.
  18838. const diagnostics = [];
  18839. if (!sf.isDeclarationFile && !this.adapter.isShim(sf)) {
  18840. diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor));
  18841. }
  18842. const program = this.programDriver.getProgram();
  18843. this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, program);
  18844. this.currentProgram = program;
  18845. return diagnostics;
  18846. }
  18847. getNonTemplateDiagnostics() {
  18848. if (this.nonTemplateDiagnostics === null) {
  18849. const compilation = this.ensureAnalyzed();
  18850. this.nonTemplateDiagnostics = [...compilation.traitCompiler.diagnostics];
  18851. if (this.entryPoint !== null && compilation.exportReferenceGraph !== null) {
  18852. this.nonTemplateDiagnostics.push(...checkForPrivateExports(this.entryPoint, this.inputProgram.getTypeChecker(), compilation.exportReferenceGraph));
  18853. }
  18854. }
  18855. return this.nonTemplateDiagnostics;
  18856. }
  18857. runAdditionalChecks(sf) {
  18858. const diagnostics = [];
  18859. const compilation = this.ensureAnalyzed();
  18860. const { extendedTemplateChecker, templateSemanticsChecker, sourceFileValidator } = compilation;
  18861. const files = sf ? [sf] : this.inputProgram.getSourceFiles();
  18862. for (const sf of files) {
  18863. if (sourceFileValidator !== null) {
  18864. const sourceFileDiagnostics = sourceFileValidator.getDiagnosticsForFile(sf);
  18865. if (sourceFileDiagnostics !== null) {
  18866. diagnostics.push(...sourceFileDiagnostics);
  18867. }
  18868. }
  18869. if (templateSemanticsChecker !== null) {
  18870. diagnostics.push(...compilation.traitCompiler.runAdditionalChecks(sf, (clazz, handler) => {
  18871. return handler.templateSemanticsCheck?.(clazz, templateSemanticsChecker) || null;
  18872. }));
  18873. }
  18874. if (this.options.strictTemplates && extendedTemplateChecker !== null) {
  18875. diagnostics.push(...compilation.traitCompiler.runAdditionalChecks(sf, (clazz, handler) => {
  18876. return handler.extendedTemplateCheck?.(clazz, extendedTemplateChecker) || null;
  18877. }));
  18878. }
  18879. }
  18880. return diagnostics;
  18881. }
  18882. makeCompilation() {
  18883. const isCore = this.options._isAngularCoreCompilation ?? isAngularCorePackage(this.inputProgram);
  18884. // Note: If this compilation builds `@angular/core`, we always build in full compilation
  18885. // mode. Code inside the core package is always compatible with itself, so it does not
  18886. // make sense to go through the indirection of partial compilation
  18887. let compilationMode = checker.CompilationMode.FULL;
  18888. if (!isCore) {
  18889. switch (this.options.compilationMode) {
  18890. case 'full':
  18891. compilationMode = checker.CompilationMode.FULL;
  18892. break;
  18893. case 'partial':
  18894. compilationMode = checker.CompilationMode.PARTIAL;
  18895. break;
  18896. case 'experimental-local':
  18897. compilationMode = checker.CompilationMode.LOCAL;
  18898. break;
  18899. }
  18900. }
  18901. const checker$1 = this.inputProgram.getTypeChecker();
  18902. const reflector = new checker.TypeScriptReflectionHost(checker$1, compilationMode === checker.CompilationMode.LOCAL);
  18903. // Construct the ReferenceEmitter.
  18904. let refEmitter;
  18905. let aliasingHost = null;
  18906. if (this.adapter.unifiedModulesHost === null ||
  18907. (!this.options['_useHostForImportGeneration'] &&
  18908. !this.options['_useHostForImportAndAliasGeneration'])) {
  18909. let localImportStrategy;
  18910. // The strategy used for local, in-project imports depends on whether TS has been configured
  18911. // with rootDirs. If so, then multiple directories may be mapped in the same "module
  18912. // namespace" and the logic of `LogicalProjectStrategy` is required to generate correct
  18913. // imports which may cross these multiple directories. Otherwise, plain relative imports are
  18914. // sufficient.
  18915. if (this.options.rootDirs !== undefined && this.options.rootDirs.length > 0) {
  18916. // rootDirs logic is in effect - use the `LogicalProjectStrategy` for in-project relative
  18917. // imports.
  18918. localImportStrategy = new checker.LogicalProjectStrategy(reflector, new checker.LogicalFileSystem([...this.adapter.rootDirs], this.adapter));
  18919. }
  18920. else {
  18921. // Plain relative imports are all that's needed.
  18922. localImportStrategy = new checker.RelativePathStrategy(reflector);
  18923. }
  18924. // The CompilerHost doesn't have fileNameToModuleName, so build an NPM-centric reference
  18925. // resolution strategy.
  18926. refEmitter = new checker.ReferenceEmitter([
  18927. // First, try to use local identifiers if available.
  18928. new checker.LocalIdentifierStrategy(),
  18929. // Next, attempt to use an absolute import.
  18930. new checker.AbsoluteModuleStrategy(this.inputProgram, checker$1, this.moduleResolver, reflector),
  18931. // Finally, check if the reference is being written into a file within the project's .ts
  18932. // sources, and use a relative import if so. If this fails, ReferenceEmitter will throw
  18933. // an error.
  18934. localImportStrategy,
  18935. ]);
  18936. // If an entrypoint is present, then all user imports should be directed through the
  18937. // entrypoint and private exports are not needed. The compiler will validate that all
  18938. // publicly visible directives/pipes are importable via this entrypoint.
  18939. if (this.entryPoint === null && this.options.generateDeepReexports === true) {
  18940. // No entrypoint is present and deep re-exports were requested, so configure the aliasing
  18941. // system to generate them.
  18942. aliasingHost = new PrivateExportAliasingHost(reflector);
  18943. }
  18944. }
  18945. else {
  18946. // The CompilerHost supports fileNameToModuleName, so use that to emit imports.
  18947. refEmitter = new checker.ReferenceEmitter([
  18948. // First, try to use local identifiers if available.
  18949. new checker.LocalIdentifierStrategy(),
  18950. // Then use aliased references (this is a workaround to StrictDeps checks).
  18951. ...(this.options['_useHostForImportAndAliasGeneration'] ? [new AliasStrategy()] : []),
  18952. // Then use fileNameToModuleName to emit imports.
  18953. new checker.UnifiedModulesStrategy(reflector, this.adapter.unifiedModulesHost),
  18954. ]);
  18955. if (this.options['_useHostForImportAndAliasGeneration']) {
  18956. aliasingHost = new UnifiedModulesAliasingHost(this.adapter.unifiedModulesHost);
  18957. }
  18958. }
  18959. const evaluator = new PartialEvaluator(reflector, checker$1, this.incrementalCompilation.depGraph);
  18960. const dtsReader = new DtsMetadataReader(checker$1, reflector);
  18961. const localMetaRegistry = new LocalMetadataRegistry();
  18962. const localMetaReader = localMetaRegistry;
  18963. const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasingHost);
  18964. const metaReader = new checker.CompoundMetadataReader([localMetaReader, dtsReader]);
  18965. const ngModuleIndex = new NgModuleIndexImpl(metaReader, localMetaReader);
  18966. const ngModuleScopeRegistry = new LocalModuleScopeRegistry(localMetaReader, metaReader, depScopeReader, refEmitter, aliasingHost);
  18967. const standaloneScopeReader = new StandaloneComponentScopeReader(metaReader, ngModuleScopeRegistry, depScopeReader);
  18968. const scopeReader = new CompoundComponentScopeReader([
  18969. ngModuleScopeRegistry,
  18970. standaloneScopeReader,
  18971. ]);
  18972. const semanticDepGraphUpdater = this.incrementalCompilation.semanticDepGraphUpdater;
  18973. const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, ngModuleScopeRegistry]);
  18974. const injectableRegistry = new InjectableClassRegistry(reflector, isCore);
  18975. const hostDirectivesResolver = new HostDirectivesResolver(metaReader);
  18976. const exportedProviderStatusResolver = new ExportedProviderStatusResolver(metaReader);
  18977. const importTracker = new ImportedSymbolsTracker();
  18978. const typeCheckScopeRegistry = new TypeCheckScopeRegistry(scopeReader, metaReader, hostDirectivesResolver);
  18979. // If a flat module entrypoint was specified, then track references via a `ReferenceGraph` in
  18980. // order to produce proper diagnostics for incorrectly exported directives/pipes/etc. If there
  18981. // is no flat module entrypoint then don't pay the cost of tracking references.
  18982. let referencesRegistry;
  18983. let exportReferenceGraph = null;
  18984. if (this.entryPoint !== null) {
  18985. exportReferenceGraph = new ReferenceGraph();
  18986. referencesRegistry = new ReferenceGraphAdapter(exportReferenceGraph);
  18987. }
  18988. else {
  18989. referencesRegistry = new NoopReferencesRegistry();
  18990. }
  18991. const dtsTransforms = new DtsTransformRegistry();
  18992. const resourceRegistry = new ResourceRegistry();
  18993. const deferredSymbolsTracker = new DeferredSymbolTracker(this.inputProgram.getTypeChecker(), this.options.onlyExplicitDeferDependencyImports ?? false);
  18994. let localCompilationExtraImportsTracker = null;
  18995. if (compilationMode === checker.CompilationMode.LOCAL && this.options.generateExtraImportsInLocalMode) {
  18996. localCompilationExtraImportsTracker = new LocalCompilationExtraImportsTracker(checker$1);
  18997. }
  18998. // Cycles are handled in full and local compilation modes by "remote scoping".
  18999. // "Remote scoping" does not work well with tree shaking for libraries.
  19000. // So in partial compilation mode, when building a library, a cycle will cause an error.
  19001. const cycleHandlingStrategy = compilationMode === checker.CompilationMode.PARTIAL
  19002. ? 1 /* CycleHandlingStrategy.Error */
  19003. : 0 /* CycleHandlingStrategy.UseRemoteScoping */;
  19004. const strictCtorDeps = this.options.strictInjectionParameters || false;
  19005. const supportJitMode = this.options['supportJitMode'] ?? true;
  19006. const supportTestBed = this.options['supportTestBed'] ?? true;
  19007. const externalRuntimeStyles = this.options['externalRuntimeStyles'] ?? false;
  19008. // Libraries compiled in partial mode could potentially be used with TestBed within an
  19009. // application. Since this is not known at library compilation time, support is required to
  19010. // prevent potential downstream application testing breakage.
  19011. if (supportTestBed === false && compilationMode === checker.CompilationMode.PARTIAL) {
  19012. throw new Error('TestBed support ("supportTestBed" option) cannot be disabled in partial compilation mode.');
  19013. }
  19014. if (supportJitMode === false && compilationMode === checker.CompilationMode.PARTIAL) {
  19015. throw new Error('JIT mode support ("supportJitMode" option) cannot be disabled in partial compilation mode.');
  19016. }
  19017. // Currently forbidOrphanComponents depends on the code generated behind ngJitMode flag. Until
  19018. // we come up with a better design for these flags, it is necessary to have the JIT mode in
  19019. // order for forbidOrphanComponents to be able to work properly.
  19020. if (supportJitMode === false && this.options.forbidOrphanComponents) {
  19021. throw new Error('JIT mode support ("supportJitMode" option) cannot be disabled when forbidOrphanComponents is set to true');
  19022. }
  19023. const jitDeclarationRegistry = new JitDeclarationRegistry();
  19024. // Set up the IvyCompilation, which manages state for the Ivy transformer.
  19025. const handlers = [
  19026. new ComponentDecoratorHandler(reflector, evaluator, metaRegistry, metaReader, scopeReader, this.adapter, ngModuleScopeRegistry, typeCheckScopeRegistry, resourceRegistry, isCore, strictCtorDeps, this.resourceManager, this.adapter.rootDirs, this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false, this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData, this.options.i18nNormalizeLineEndingsInICUs === true, this.moduleResolver, this.cycleAnalyzer, cycleHandlingStrategy, refEmitter, referencesRegistry, this.incrementalCompilation.depGraph, injectableRegistry, semanticDepGraphUpdater, this.closureCompilerEnabled, this.delegatingPerfRecorder, hostDirectivesResolver, importTracker, supportTestBed, compilationMode, deferredSymbolsTracker, !!this.options.forbidOrphanComponents, this.enableBlockSyntax, this.enableLetSyntax, externalRuntimeStyles, localCompilationExtraImportsTracker, jitDeclarationRegistry, this.options.i18nPreserveWhitespaceForLegacyExtraction ?? true, !!this.options.strictStandalone, this.enableHmr, this.implicitStandaloneValue),
  19027. // TODO(alxhub): understand why the cast here is necessary (something to do with `null`
  19028. // not being assignable to `unknown` when wrapped in `Readonly`).
  19029. new DirectiveDecoratorHandler(reflector, evaluator, metaRegistry, ngModuleScopeRegistry, metaReader, injectableRegistry, refEmitter, referencesRegistry, isCore, strictCtorDeps, semanticDepGraphUpdater, this.closureCompilerEnabled, this.delegatingPerfRecorder, importTracker, supportTestBed, compilationMode, jitDeclarationRegistry, !!this.options.strictStandalone, this.implicitStandaloneValue),
  19030. // Pipe handler must be before injectable handler in list so pipe factories are printed
  19031. // before injectable factories (so injectable factories can delegate to them)
  19032. new PipeDecoratorHandler(reflector, evaluator, metaRegistry, ngModuleScopeRegistry, injectableRegistry, isCore, this.delegatingPerfRecorder, supportTestBed, compilationMode, !!this.options.generateExtraImportsInLocalMode, !!this.options.strictStandalone, this.implicitStandaloneValue),
  19033. new InjectableDecoratorHandler(reflector, evaluator, isCore, strictCtorDeps, injectableRegistry, this.delegatingPerfRecorder, supportTestBed, compilationMode),
  19034. new NgModuleDecoratorHandler(reflector, evaluator, metaReader, metaRegistry, ngModuleScopeRegistry, referencesRegistry, exportedProviderStatusResolver, semanticDepGraphUpdater, isCore, refEmitter, this.closureCompilerEnabled, this.options.onlyPublishPublicTypingsForNgModules ?? false, injectableRegistry, this.delegatingPerfRecorder, supportTestBed, supportJitMode, compilationMode, localCompilationExtraImportsTracker, jitDeclarationRegistry),
  19035. ];
  19036. const traitCompiler = new TraitCompiler(handlers, reflector, this.delegatingPerfRecorder, this.incrementalCompilation, this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms, semanticDepGraphUpdater, this.adapter);
  19037. // Template type-checking may use the `ProgramDriver` to produce new `ts.Program`(s). If this
  19038. // happens, they need to be tracked by the `NgCompiler`.
  19039. const notifyingDriver = new NotifyingProgramDriverWrapper(this.programDriver, (program) => {
  19040. this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, program);
  19041. this.currentProgram = program;
  19042. });
  19043. const typeCheckingConfig = this.getTypeCheckingConfig();
  19044. const templateTypeChecker = new checker.TemplateTypeCheckerImpl(this.inputProgram, notifyingDriver, traitCompiler, typeCheckingConfig, refEmitter, reflector, this.adapter, this.incrementalCompilation, metaReader, localMetaReader, ngModuleIndex, scopeReader, typeCheckScopeRegistry, this.delegatingPerfRecorder);
  19045. // Only construct the extended template checker if the configuration is valid and usable.
  19046. const extendedTemplateChecker = this.constructionDiagnostics.length === 0
  19047. ? new ExtendedTemplateCheckerImpl(templateTypeChecker, checker$1, ALL_DIAGNOSTIC_FACTORIES, this.options)
  19048. : null;
  19049. const templateSemanticsChecker = this.constructionDiagnostics.length === 0
  19050. ? new TemplateSemanticsCheckerImpl(templateTypeChecker)
  19051. : null;
  19052. const sourceFileValidator = this.constructionDiagnostics.length === 0
  19053. ? new SourceFileValidator(reflector, importTracker, templateTypeChecker, typeCheckingConfig)
  19054. : null;
  19055. return {
  19056. isCore,
  19057. traitCompiler,
  19058. reflector,
  19059. scopeRegistry: ngModuleScopeRegistry,
  19060. dtsTransforms,
  19061. exportReferenceGraph,
  19062. metaReader,
  19063. typeCheckScopeRegistry,
  19064. aliasingHost,
  19065. refEmitter,
  19066. templateTypeChecker,
  19067. resourceRegistry,
  19068. extendedTemplateChecker,
  19069. localCompilationExtraImportsTracker,
  19070. jitDeclarationRegistry,
  19071. templateSemanticsChecker,
  19072. sourceFileValidator,
  19073. supportJitMode,
  19074. };
  19075. }
  19076. }
  19077. /**
  19078. * Determine if the given `Program` is @angular/core.
  19079. */
  19080. function isAngularCorePackage(program) {
  19081. // Look for its_just_angular.ts somewhere in the program.
  19082. const r3Symbols = getR3SymbolsFile(program);
  19083. if (r3Symbols === null) {
  19084. return false;
  19085. }
  19086. // Look for the constant ITS_JUST_ANGULAR in that file.
  19087. return r3Symbols.statements.some((stmt) => {
  19088. // The statement must be a variable declaration statement.
  19089. if (!ts.isVariableStatement(stmt)) {
  19090. return false;
  19091. }
  19092. // It must be exported.
  19093. const modifiers = ts.getModifiers(stmt);
  19094. if (modifiers === undefined ||
  19095. !modifiers.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)) {
  19096. return false;
  19097. }
  19098. // It must declare ITS_JUST_ANGULAR.
  19099. return stmt.declarationList.declarations.some((decl) => {
  19100. // The declaration must match the name.
  19101. if (!ts.isIdentifier(decl.name) || decl.name.text !== 'ITS_JUST_ANGULAR') {
  19102. return false;
  19103. }
  19104. // It must initialize the variable to true.
  19105. if (decl.initializer === undefined || decl.initializer.kind !== ts.SyntaxKind.TrueKeyword) {
  19106. return false;
  19107. }
  19108. // This definition matches.
  19109. return true;
  19110. });
  19111. });
  19112. }
  19113. /**
  19114. * Find the 'r3_symbols.ts' file in the given `Program`, or return `null` if it wasn't there.
  19115. */
  19116. function getR3SymbolsFile(program) {
  19117. return (program.getSourceFiles().find((file) => file.fileName.indexOf('r3_symbols.ts') >= 0) || null);
  19118. }
  19119. /**
  19120. * Since "strictTemplates" is a true superset of type checking capabilities compared to
  19121. * "fullTemplateTypeCheck", it is required that the latter is not explicitly disabled if the
  19122. * former is enabled.
  19123. */
  19124. function* verifyCompatibleTypeCheckOptions(options) {
  19125. if (options.fullTemplateTypeCheck === false && options.strictTemplates === true) {
  19126. yield makeConfigDiagnostic({
  19127. category: ts.DiagnosticCategory.Error,
  19128. code: checker.ErrorCode.CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK,
  19129. messageText: `
  19130. Angular compiler option "strictTemplates" is enabled, however "fullTemplateTypeCheck" is disabled.
  19131. Having the "strictTemplates" flag enabled implies that "fullTemplateTypeCheck" is also enabled, so
  19132. the latter can not be explicitly disabled.
  19133. One of the following actions is required:
  19134. 1. Remove the "fullTemplateTypeCheck" option.
  19135. 2. Remove "strictTemplates" or set it to 'false'.
  19136. More information about the template type checking compiler options can be found in the documentation:
  19137. https://angular.dev/tools/cli/template-typecheck
  19138. `.trim(),
  19139. });
  19140. }
  19141. if (options.extendedDiagnostics && options.strictTemplates === false) {
  19142. yield makeConfigDiagnostic({
  19143. category: ts.DiagnosticCategory.Error,
  19144. code: checker.ErrorCode.CONFIG_EXTENDED_DIAGNOSTICS_IMPLIES_STRICT_TEMPLATES,
  19145. messageText: `
  19146. Angular compiler option "extendedDiagnostics" is configured, however "strictTemplates" is disabled.
  19147. Using "extendedDiagnostics" requires that "strictTemplates" is also enabled.
  19148. One of the following actions is required:
  19149. 1. Remove "strictTemplates: false" to enable it.
  19150. 2. Remove "extendedDiagnostics" configuration to disable them.
  19151. `.trim(),
  19152. });
  19153. }
  19154. const allowedCategoryLabels = Array.from(Object.values(exports.DiagnosticCategoryLabel));
  19155. const defaultCategory = options.extendedDiagnostics?.defaultCategory;
  19156. if (defaultCategory && !allowedCategoryLabels.includes(defaultCategory)) {
  19157. yield makeConfigDiagnostic({
  19158. category: ts.DiagnosticCategory.Error,
  19159. code: checker.ErrorCode.CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CATEGORY_LABEL,
  19160. messageText: `
  19161. Angular compiler option "extendedDiagnostics.defaultCategory" has an unknown diagnostic category: "${defaultCategory}".
  19162. Allowed diagnostic categories are:
  19163. ${allowedCategoryLabels.join('\n')}
  19164. `.trim(),
  19165. });
  19166. }
  19167. for (const [checkName, category] of Object.entries(options.extendedDiagnostics?.checks ?? {})) {
  19168. if (!SUPPORTED_DIAGNOSTIC_NAMES.has(checkName)) {
  19169. yield makeConfigDiagnostic({
  19170. category: ts.DiagnosticCategory.Error,
  19171. code: checker.ErrorCode.CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CHECK,
  19172. messageText: `
  19173. Angular compiler option "extendedDiagnostics.checks" has an unknown check: "${checkName}".
  19174. Allowed check names are:
  19175. ${Array.from(SUPPORTED_DIAGNOSTIC_NAMES).join('\n')}
  19176. `.trim(),
  19177. });
  19178. }
  19179. if (!allowedCategoryLabels.includes(category)) {
  19180. yield makeConfigDiagnostic({
  19181. category: ts.DiagnosticCategory.Error,
  19182. code: checker.ErrorCode.CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CATEGORY_LABEL,
  19183. messageText: `
  19184. Angular compiler option "extendedDiagnostics.checks['${checkName}']" has an unknown diagnostic category: "${category}".
  19185. Allowed diagnostic categories are:
  19186. ${allowedCategoryLabels.join('\n')}
  19187. `.trim(),
  19188. });
  19189. }
  19190. }
  19191. }
  19192. function makeConfigDiagnostic({ category, code, messageText, }) {
  19193. return {
  19194. category,
  19195. code: checker.ngErrorCode(code),
  19196. file: undefined,
  19197. start: undefined,
  19198. length: undefined,
  19199. messageText,
  19200. };
  19201. }
  19202. class ReferenceGraphAdapter {
  19203. graph;
  19204. constructor(graph) {
  19205. this.graph = graph;
  19206. }
  19207. add(source, ...references) {
  19208. for (const { node } of references) {
  19209. let sourceFile = node.getSourceFile();
  19210. if (sourceFile === undefined) {
  19211. sourceFile = ts.getOriginalNode(node).getSourceFile();
  19212. }
  19213. // Only record local references (not references into .d.ts files).
  19214. if (sourceFile === undefined || !checker.isDtsPath(sourceFile.fileName)) {
  19215. this.graph.add(source, node);
  19216. }
  19217. }
  19218. }
  19219. }
  19220. class NotifyingProgramDriverWrapper {
  19221. delegate;
  19222. notifyNewProgram;
  19223. getSourceFileVersion;
  19224. constructor(delegate, notifyNewProgram) {
  19225. this.delegate = delegate;
  19226. this.notifyNewProgram = notifyNewProgram;
  19227. this.getSourceFileVersion = this.delegate.getSourceFileVersion?.bind(this);
  19228. }
  19229. get supportsInlineOperations() {
  19230. return this.delegate.supportsInlineOperations;
  19231. }
  19232. getProgram() {
  19233. return this.delegate.getProgram();
  19234. }
  19235. updateFiles(contents, updateMode) {
  19236. this.delegate.updateFiles(contents, updateMode);
  19237. this.notifyNewProgram(this.delegate.getProgram());
  19238. }
  19239. }
  19240. function versionMapFromProgram(program, driver) {
  19241. if (driver.getSourceFileVersion === undefined) {
  19242. return null;
  19243. }
  19244. const versions = new Map();
  19245. for (const possiblyRedirectedSourceFile of program.getSourceFiles()) {
  19246. const sf = checker.toUnredirectedSourceFile(possiblyRedirectedSourceFile);
  19247. versions.set(checker.absoluteFromSourceFile(sf), driver.getSourceFileVersion(sf));
  19248. }
  19249. return versions;
  19250. }
  19251. // A persistent source of bugs in CompilerHost delegation has been the addition by TS of new,
  19252. // optional methods on ts.CompilerHost. Since these methods are optional, it's not a type error that
  19253. // the delegating host doesn't implement or delegate them. This causes subtle runtime failures. No
  19254. // more. This infrastructure ensures that failing to delegate a method is a compile-time error.
  19255. /**
  19256. * Delegates all methods of `ExtendedTsCompilerHost` to a delegate, with the exception of
  19257. * `getSourceFile` and `fileExists` which are implemented in `NgCompilerHost`.
  19258. *
  19259. * If a new method is added to `ts.CompilerHost` which is not delegated, a type error will be
  19260. * generated for this class.
  19261. */
  19262. class DelegatingCompilerHost {
  19263. delegate;
  19264. createHash;
  19265. directoryExists;
  19266. fileNameToModuleName;
  19267. getCancellationToken;
  19268. getCanonicalFileName;
  19269. getCurrentDirectory;
  19270. getDefaultLibFileName;
  19271. getDefaultLibLocation;
  19272. getDirectories;
  19273. getEnvironmentVariable;
  19274. getModifiedResourceFiles;
  19275. getNewLine;
  19276. getParsedCommandLine;
  19277. getSourceFileByPath;
  19278. readDirectory;
  19279. readFile;
  19280. readResource;
  19281. transformResource;
  19282. realpath;
  19283. resolveModuleNames;
  19284. resolveTypeReferenceDirectives;
  19285. resourceNameToFileName;
  19286. trace;
  19287. useCaseSensitiveFileNames;
  19288. writeFile;
  19289. getModuleResolutionCache;
  19290. hasInvalidatedResolutions;
  19291. resolveModuleNameLiterals;
  19292. resolveTypeReferenceDirectiveReferences;
  19293. // jsDocParsingMode is not a method like the other elements above
  19294. // TODO: ignore usage can be dropped once 5.2 support is dropped
  19295. get jsDocParsingMode() {
  19296. // @ts-ignore
  19297. return this.delegate.jsDocParsingMode;
  19298. }
  19299. set jsDocParsingMode(mode) {
  19300. // @ts-ignore
  19301. this.delegate.jsDocParsingMode = mode;
  19302. }
  19303. constructor(delegate) {
  19304. this.delegate = delegate;
  19305. // Excluded are 'getSourceFile' and 'fileExists', which are actually implemented by
  19306. // NgCompilerHost
  19307. // below.
  19308. this.createHash = this.delegateMethod('createHash');
  19309. this.directoryExists = this.delegateMethod('directoryExists');
  19310. this.fileNameToModuleName = this.delegateMethod('fileNameToModuleName');
  19311. this.getCancellationToken = this.delegateMethod('getCancellationToken');
  19312. this.getCanonicalFileName = this.delegateMethod('getCanonicalFileName');
  19313. this.getCurrentDirectory = this.delegateMethod('getCurrentDirectory');
  19314. this.getDefaultLibFileName = this.delegateMethod('getDefaultLibFileName');
  19315. this.getDefaultLibLocation = this.delegateMethod('getDefaultLibLocation');
  19316. this.getDirectories = this.delegateMethod('getDirectories');
  19317. this.getEnvironmentVariable = this.delegateMethod('getEnvironmentVariable');
  19318. this.getModifiedResourceFiles = this.delegateMethod('getModifiedResourceFiles');
  19319. this.getNewLine = this.delegateMethod('getNewLine');
  19320. this.getParsedCommandLine = this.delegateMethod('getParsedCommandLine');
  19321. this.getSourceFileByPath = this.delegateMethod('getSourceFileByPath');
  19322. this.readDirectory = this.delegateMethod('readDirectory');
  19323. this.readFile = this.delegateMethod('readFile');
  19324. this.readResource = this.delegateMethod('readResource');
  19325. this.transformResource = this.delegateMethod('transformResource');
  19326. this.realpath = this.delegateMethod('realpath');
  19327. this.resolveModuleNames = this.delegateMethod('resolveModuleNames');
  19328. this.resolveTypeReferenceDirectives = this.delegateMethod('resolveTypeReferenceDirectives');
  19329. this.resourceNameToFileName = this.delegateMethod('resourceNameToFileName');
  19330. this.trace = this.delegateMethod('trace');
  19331. this.useCaseSensitiveFileNames = this.delegateMethod('useCaseSensitiveFileNames');
  19332. this.writeFile = this.delegateMethod('writeFile');
  19333. this.getModuleResolutionCache = this.delegateMethod('getModuleResolutionCache');
  19334. this.hasInvalidatedResolutions = this.delegateMethod('hasInvalidatedResolutions');
  19335. this.resolveModuleNameLiterals = this.delegateMethod('resolveModuleNameLiterals');
  19336. this.resolveTypeReferenceDirectiveReferences = this.delegateMethod('resolveTypeReferenceDirectiveReferences');
  19337. }
  19338. delegateMethod(name) {
  19339. return this.delegate[name] !== undefined
  19340. ? this.delegate[name].bind(this.delegate)
  19341. : undefined;
  19342. }
  19343. }
  19344. /**
  19345. * A wrapper around `ts.CompilerHost` (plus any extension methods from `ExtendedTsCompilerHost`).
  19346. *
  19347. * In order for a consumer to include Angular compilation in their TypeScript compiler, the
  19348. * `ts.Program` must be created with a host that adds Angular-specific files (e.g.
  19349. * the template type-checking file, etc) to the compilation. `NgCompilerHost` is the
  19350. * host implementation which supports this.
  19351. *
  19352. * The interface implementations here ensure that `NgCompilerHost` fully delegates to
  19353. * `ExtendedTsCompilerHost` methods whenever present.
  19354. */
  19355. class NgCompilerHost extends DelegatingCompilerHost {
  19356. shimAdapter;
  19357. shimTagger;
  19358. entryPoint = null;
  19359. constructionDiagnostics;
  19360. inputFiles;
  19361. rootDirs;
  19362. constructor(delegate, inputFiles, rootDirs, shimAdapter, shimTagger, entryPoint, diagnostics) {
  19363. super(delegate);
  19364. this.shimAdapter = shimAdapter;
  19365. this.shimTagger = shimTagger;
  19366. this.entryPoint = entryPoint;
  19367. this.constructionDiagnostics = diagnostics;
  19368. this.inputFiles = [...inputFiles, ...shimAdapter.extraInputFiles];
  19369. this.rootDirs = rootDirs;
  19370. if (this.resolveModuleNames === undefined) {
  19371. // In order to reuse the module resolution cache during the creation of the type-check
  19372. // program, we'll need to provide `resolveModuleNames` if the delegate did not provide one.
  19373. this.resolveModuleNames = this.createCachedResolveModuleNamesFunction();
  19374. }
  19375. }
  19376. /**
  19377. * Retrieves a set of `ts.SourceFile`s which should not be emitted as JS files.
  19378. *
  19379. * Available after this host is used to create a `ts.Program` (which causes all the files in the
  19380. * program to be enumerated).
  19381. */
  19382. get ignoreForEmit() {
  19383. return this.shimAdapter.ignoreForEmit;
  19384. }
  19385. /**
  19386. * Retrieve the array of shim extension prefixes for which shims were created for each original
  19387. * file.
  19388. */
  19389. get shimExtensionPrefixes() {
  19390. return this.shimAdapter.extensionPrefixes;
  19391. }
  19392. /**
  19393. * Performs cleanup that needs to happen after a `ts.Program` has been created using this host.
  19394. */
  19395. postProgramCreationCleanup() {
  19396. this.shimTagger.finalize();
  19397. }
  19398. /**
  19399. * Create an `NgCompilerHost` from a delegate host, an array of input filenames, and the full set
  19400. * of TypeScript and Angular compiler options.
  19401. */
  19402. static wrap(delegate, inputFiles, options, oldProgram) {
  19403. const topLevelShimGenerators = [];
  19404. const perFileShimGenerators = [];
  19405. const rootDirs = checker.getRootDirs(delegate, options);
  19406. perFileShimGenerators.push(new checker.TypeCheckShimGenerator());
  19407. let diagnostics = [];
  19408. const normalizedTsInputFiles = [];
  19409. for (const inputFile of inputFiles) {
  19410. if (!checker.isNonDeclarationTsPath(inputFile)) {
  19411. continue;
  19412. }
  19413. normalizedTsInputFiles.push(checker.resolve(inputFile));
  19414. }
  19415. let entryPoint = null;
  19416. if (options.flatModuleOutFile != null && options.flatModuleOutFile !== '') {
  19417. entryPoint = findFlatIndexEntryPoint(normalizedTsInputFiles);
  19418. if (entryPoint === null) {
  19419. // This error message talks specifically about having a single .ts file in "files". However
  19420. // the actual logic is a bit more permissive. If a single file exists, that will be taken,
  19421. // otherwise the highest level (shortest path) "index.ts" file will be used as the flat
  19422. // module entry point instead. If neither of these conditions apply, the error below is
  19423. // given.
  19424. //
  19425. // The user is not informed about the "index.ts" option as this behavior is deprecated -
  19426. // an explicit entrypoint should always be specified.
  19427. diagnostics.push({
  19428. category: ts.DiagnosticCategory.Error,
  19429. code: checker.ngErrorCode(checker.ErrorCode.CONFIG_FLAT_MODULE_NO_INDEX),
  19430. file: undefined,
  19431. start: undefined,
  19432. length: undefined,
  19433. messageText: 'Angular compiler option "flatModuleOutFile" requires one and only one .ts file in the "files" field.',
  19434. });
  19435. }
  19436. else {
  19437. const flatModuleId = options.flatModuleId || null;
  19438. const flatModuleOutFile = normalizeSeparators(options.flatModuleOutFile);
  19439. const flatIndexGenerator = new FlatIndexGenerator(entryPoint, flatModuleOutFile, flatModuleId);
  19440. topLevelShimGenerators.push(flatIndexGenerator);
  19441. }
  19442. }
  19443. const shimAdapter = new ShimAdapter(delegate, normalizedTsInputFiles, topLevelShimGenerators, perFileShimGenerators, oldProgram);
  19444. const shimTagger = new ShimReferenceTagger(perFileShimGenerators.map((gen) => gen.extensionPrefix));
  19445. return new NgCompilerHost(delegate, inputFiles, rootDirs, shimAdapter, shimTagger, entryPoint, diagnostics);
  19446. }
  19447. /**
  19448. * Check whether the given `ts.SourceFile` is a shim file.
  19449. *
  19450. * If this returns false, the file is user-provided.
  19451. */
  19452. isShim(sf) {
  19453. return checker.isShim(sf);
  19454. }
  19455. /**
  19456. * Check whether the given `ts.SourceFile` is a resource file.
  19457. *
  19458. * This simply returns `false` for the compiler-cli since resource files are not added as root
  19459. * files to the project.
  19460. */
  19461. isResource(sf) {
  19462. return false;
  19463. }
  19464. getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) {
  19465. // Is this a previously known shim?
  19466. const shimSf = this.shimAdapter.maybeGenerate(checker.resolve(fileName));
  19467. if (shimSf !== null) {
  19468. // Yes, so return it.
  19469. return shimSf;
  19470. }
  19471. // No, so it's a file which might need shims (or a file which doesn't exist).
  19472. const sf = this.delegate.getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
  19473. if (sf === undefined) {
  19474. return undefined;
  19475. }
  19476. this.shimTagger.tag(sf);
  19477. return sf;
  19478. }
  19479. fileExists(fileName) {
  19480. // Consider the file as existing whenever
  19481. // 1) it really does exist in the delegate host, or
  19482. // 2) at least one of the shim generators recognizes it
  19483. // Note that we can pass the file name as branded absolute fs path because TypeScript
  19484. // internally only passes POSIX-like paths.
  19485. //
  19486. // Also note that the `maybeGenerate` check below checks for both `null` and `undefined`.
  19487. return (this.delegate.fileExists(fileName) ||
  19488. this.shimAdapter.maybeGenerate(checker.resolve(fileName)) != null);
  19489. }
  19490. get unifiedModulesHost() {
  19491. return this.fileNameToModuleName !== undefined ? this : null;
  19492. }
  19493. createCachedResolveModuleNamesFunction() {
  19494. const moduleResolutionCache = ts.createModuleResolutionCache(this.getCurrentDirectory(), this.getCanonicalFileName.bind(this));
  19495. return (moduleNames, containingFile, reusedNames, redirectedReference, options) => {
  19496. return moduleNames.map((moduleName) => {
  19497. const module = ts.resolveModuleName(moduleName, containingFile, options, this, moduleResolutionCache, redirectedReference);
  19498. return module.resolvedModule;
  19499. });
  19500. };
  19501. }
  19502. }
  19503. /**
  19504. * Entrypoint to the Angular Compiler (Ivy+) which sits behind the `api.Program` interface, allowing
  19505. * it to be a drop-in replacement for the legacy View Engine compiler to tooling such as the
  19506. * command-line main() function or the Angular CLI.
  19507. */
  19508. class NgtscProgram {
  19509. options;
  19510. compiler;
  19511. /**
  19512. * The primary TypeScript program, which is used for analysis and emit.
  19513. */
  19514. tsProgram;
  19515. host;
  19516. incrementalStrategy;
  19517. constructor(rootNames, options, delegateHost, oldProgram) {
  19518. this.options = options;
  19519. const perfRecorder = ActivePerfRecorder.zeroedToNow();
  19520. perfRecorder.phase(checker.PerfPhase.Setup);
  19521. // First, check whether the current TS version is supported.
  19522. if (!options.disableTypeScriptVersionCheck) {
  19523. verifySupportedTypeScriptVersion();
  19524. }
  19525. // In local compilation mode there are almost always (many) emit errors due to imports that
  19526. // cannot be resolved. So we should emit regardless.
  19527. if (options.compilationMode === 'experimental-local') {
  19528. options.noEmitOnError = false;
  19529. }
  19530. const reuseProgram = oldProgram?.compiler.getCurrentProgram();
  19531. this.host = NgCompilerHost.wrap(delegateHost, rootNames, options, reuseProgram ?? null);
  19532. if (reuseProgram !== undefined) {
  19533. // Prior to reusing the old program, restore shim tagging for all its `ts.SourceFile`s.
  19534. // TypeScript checks the `referencedFiles` of `ts.SourceFile`s for changes when evaluating
  19535. // incremental reuse of data from the old program, so it's important that these match in order
  19536. // to get the most benefit out of reuse.
  19537. checker.retagAllTsFiles(reuseProgram);
  19538. }
  19539. this.tsProgram = perfRecorder.inPhase(checker.PerfPhase.TypeScriptProgramCreate, () => ts.createProgram(this.host.inputFiles, options, this.host, reuseProgram));
  19540. perfRecorder.phase(checker.PerfPhase.Unaccounted);
  19541. perfRecorder.memory(checker.PerfCheckpoint.TypeScriptProgramCreate);
  19542. this.host.postProgramCreationCleanup();
  19543. const programDriver = new TsCreateProgramDriver(this.tsProgram, this.host, this.options, this.host.shimExtensionPrefixes);
  19544. this.incrementalStrategy =
  19545. oldProgram !== undefined
  19546. ? oldProgram.incrementalStrategy.toNextBuildStrategy()
  19547. : new TrackedIncrementalBuildStrategy();
  19548. const modifiedResourceFiles = new Set();
  19549. if (this.host.getModifiedResourceFiles !== undefined) {
  19550. const strings = this.host.getModifiedResourceFiles();
  19551. if (strings !== undefined) {
  19552. for (const fileString of strings) {
  19553. modifiedResourceFiles.add(checker.absoluteFrom(fileString));
  19554. }
  19555. }
  19556. }
  19557. let ticket;
  19558. if (oldProgram === undefined) {
  19559. ticket = freshCompilationTicket(this.tsProgram, options, this.incrementalStrategy, programDriver, perfRecorder,
  19560. /* enableTemplateTypeChecker */ false,
  19561. /* usePoisonedData */ false);
  19562. }
  19563. else {
  19564. ticket = incrementalFromCompilerTicket(oldProgram.compiler, this.tsProgram, this.incrementalStrategy, programDriver, modifiedResourceFiles, perfRecorder);
  19565. }
  19566. // Create the NgCompiler which will drive the rest of the compilation.
  19567. this.compiler = NgCompiler.fromTicket(ticket, this.host);
  19568. }
  19569. getTsProgram() {
  19570. return this.tsProgram;
  19571. }
  19572. getReuseTsProgram() {
  19573. return this.compiler.getCurrentProgram();
  19574. }
  19575. getTsOptionDiagnostics(cancellationToken) {
  19576. return this.compiler.perfRecorder.inPhase(checker.PerfPhase.TypeScriptDiagnostics, () => this.tsProgram.getOptionsDiagnostics(cancellationToken));
  19577. }
  19578. getTsSyntacticDiagnostics(sourceFile, cancellationToken) {
  19579. return this.compiler.perfRecorder.inPhase(checker.PerfPhase.TypeScriptDiagnostics, () => {
  19580. const ignoredFiles = this.compiler.ignoreForDiagnostics;
  19581. let res;
  19582. if (sourceFile !== undefined) {
  19583. if (ignoredFiles.has(sourceFile)) {
  19584. return [];
  19585. }
  19586. res = this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
  19587. }
  19588. else {
  19589. const diagnostics = [];
  19590. for (const sf of this.tsProgram.getSourceFiles()) {
  19591. if (!ignoredFiles.has(sf)) {
  19592. diagnostics.push(...this.tsProgram.getSyntacticDiagnostics(sf, cancellationToken));
  19593. }
  19594. }
  19595. res = diagnostics;
  19596. }
  19597. return res;
  19598. });
  19599. }
  19600. getTsSemanticDiagnostics(sourceFile, cancellationToken) {
  19601. // No TS semantic check should be done in local compilation mode, as it is always full of errors
  19602. // due to cross file imports.
  19603. if (this.options.compilationMode === 'experimental-local') {
  19604. return [];
  19605. }
  19606. return this.compiler.perfRecorder.inPhase(checker.PerfPhase.TypeScriptDiagnostics, () => {
  19607. const ignoredFiles = this.compiler.ignoreForDiagnostics;
  19608. let res;
  19609. if (sourceFile !== undefined) {
  19610. if (ignoredFiles.has(sourceFile)) {
  19611. return [];
  19612. }
  19613. res = this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
  19614. }
  19615. else {
  19616. const diagnostics = [];
  19617. for (const sf of this.tsProgram.getSourceFiles()) {
  19618. if (!ignoredFiles.has(sf)) {
  19619. diagnostics.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
  19620. }
  19621. }
  19622. res = diagnostics;
  19623. }
  19624. return res;
  19625. });
  19626. }
  19627. getNgOptionDiagnostics(cancellationToken) {
  19628. return this.compiler.getOptionDiagnostics();
  19629. }
  19630. getNgStructuralDiagnostics(cancellationToken) {
  19631. return [];
  19632. }
  19633. getNgSemanticDiagnostics(fileName, cancellationToken) {
  19634. let sf = undefined;
  19635. if (fileName !== undefined) {
  19636. sf = this.tsProgram.getSourceFile(fileName);
  19637. if (sf === undefined) {
  19638. // There are no diagnostics for files which don't exist in the program - maybe the caller
  19639. // has stale data?
  19640. return [];
  19641. }
  19642. }
  19643. if (sf === undefined) {
  19644. return this.compiler.getDiagnostics();
  19645. }
  19646. else {
  19647. return this.compiler.getDiagnosticsForFile(sf, checker.OptimizeFor.WholeProgram);
  19648. }
  19649. }
  19650. /**
  19651. * Ensure that the `NgCompiler` has properly analyzed the program, and allow for the asynchronous
  19652. * loading of any resources during the process.
  19653. *
  19654. * This is used by the Angular CLI to allow for spawning (async) child compilations for things
  19655. * like SASS files used in `styleUrls`.
  19656. */
  19657. loadNgStructureAsync() {
  19658. return this.compiler.analyzeAsync();
  19659. }
  19660. listLazyRoutes(entryRoute) {
  19661. return [];
  19662. }
  19663. emitXi18n() {
  19664. const ctx = new MessageBundle(new checker.HtmlParser(), [], {}, this.options.i18nOutLocale ?? null, this.options.i18nPreserveWhitespaceForLegacyExtraction);
  19665. this.compiler.xi18n(ctx);
  19666. i18nExtract(this.options.i18nOutFormat ?? null, this.options.i18nOutFile ?? null, this.host, this.options, ctx, checker.resolve);
  19667. }
  19668. emit(opts) {
  19669. // Check if emission of the i18n messages bundle was requested.
  19670. if (opts !== undefined &&
  19671. opts.emitFlags !== undefined &&
  19672. opts.emitFlags & exports.EmitFlags.I18nBundle) {
  19673. this.emitXi18n();
  19674. // `api.EmitFlags` is a View Engine compiler concept. We only pay attention to the absence of
  19675. // the other flags here if i18n emit was requested (since this is usually done in the xi18n
  19676. // flow, where we don't want to emit JS at all).
  19677. if (!(opts.emitFlags & exports.EmitFlags.JS)) {
  19678. return {
  19679. diagnostics: [],
  19680. emitSkipped: true,
  19681. emittedFiles: [],
  19682. };
  19683. }
  19684. }
  19685. const forceEmit = opts?.forceEmit ?? false;
  19686. this.compiler.perfRecorder.memory(checker.PerfCheckpoint.PreEmit);
  19687. const res = this.compiler.perfRecorder.inPhase(checker.PerfPhase.TypeScriptEmit, () => {
  19688. const { transformers } = this.compiler.prepareEmit();
  19689. const ignoreFiles = this.compiler.ignoreForEmit;
  19690. const emitCallback = (opts?.emitCallback ??
  19691. defaultEmitCallback);
  19692. const writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
  19693. if (sourceFiles !== undefined) {
  19694. // Record successful writes for any `ts.SourceFile` (that's not a declaration file)
  19695. // that's an input to this write.
  19696. for (const writtenSf of sourceFiles) {
  19697. if (writtenSf.isDeclarationFile) {
  19698. continue;
  19699. }
  19700. this.compiler.incrementalCompilation.recordSuccessfulEmit(writtenSf);
  19701. }
  19702. }
  19703. this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
  19704. };
  19705. const customTransforms = opts && opts.customTransformers;
  19706. const beforeTransforms = transformers.before || [];
  19707. const afterDeclarationsTransforms = transformers.afterDeclarations;
  19708. if (customTransforms !== undefined && customTransforms.beforeTs !== undefined) {
  19709. beforeTransforms.push(...customTransforms.beforeTs);
  19710. }
  19711. const emitResults = [];
  19712. for (const targetSourceFile of this.tsProgram.getSourceFiles()) {
  19713. if (targetSourceFile.isDeclarationFile || ignoreFiles.has(targetSourceFile)) {
  19714. continue;
  19715. }
  19716. if (!forceEmit && this.compiler.incrementalCompilation.safeToSkipEmit(targetSourceFile)) {
  19717. this.compiler.perfRecorder.eventCount(checker.PerfEvent.EmitSkipSourceFile);
  19718. continue;
  19719. }
  19720. this.compiler.perfRecorder.eventCount(checker.PerfEvent.EmitSourceFile);
  19721. emitResults.push(emitCallback({
  19722. targetSourceFile,
  19723. program: this.tsProgram,
  19724. host: this.host,
  19725. options: this.options,
  19726. emitOnlyDtsFiles: false,
  19727. writeFile,
  19728. customTransformers: {
  19729. before: beforeTransforms,
  19730. after: customTransforms && customTransforms.afterTs,
  19731. afterDeclarations: afterDeclarationsTransforms,
  19732. },
  19733. }));
  19734. }
  19735. this.compiler.perfRecorder.memory(checker.PerfCheckpoint.Emit);
  19736. // Run the emit, including a custom transformer that will downlevel the Ivy decorators in
  19737. // code.
  19738. return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults);
  19739. });
  19740. // Record performance analysis information to disk if we've been asked to do so.
  19741. if (this.options.tracePerformance !== undefined) {
  19742. const perf = this.compiler.perfRecorder.finalize();
  19743. checker.getFileSystem().writeFile(checker.getFileSystem().resolve(this.options.tracePerformance), JSON.stringify(perf, null, 2));
  19744. }
  19745. return res;
  19746. }
  19747. getIndexedComponents() {
  19748. return this.compiler.getIndexedComponents();
  19749. }
  19750. /**
  19751. * Gets information for the current program that may be used to generate API
  19752. * reference documentation. This includes Angular-specific information, such
  19753. * as component inputs and outputs.
  19754. *
  19755. * @param entryPoint Path to the entry point for the package for which API
  19756. * docs should be extracted.
  19757. */
  19758. getApiDocumentation(entryPoint, privateModules) {
  19759. return this.compiler.getApiDocumentation(entryPoint, privateModules);
  19760. }
  19761. getEmittedSourceFiles() {
  19762. throw new Error('Method not implemented.');
  19763. }
  19764. }
  19765. const defaultEmitCallback = ({ program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, }) => program.emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
  19766. function mergeEmitResults(emitResults) {
  19767. const diagnostics = [];
  19768. let emitSkipped = false;
  19769. const emittedFiles = [];
  19770. for (const er of emitResults) {
  19771. diagnostics.push(...er.diagnostics);
  19772. emitSkipped = emitSkipped || er.emitSkipped;
  19773. emittedFiles.push(...(er.emittedFiles || []));
  19774. }
  19775. return { diagnostics, emitSkipped, emittedFiles };
  19776. }
  19777. var LogLevel;
  19778. (function (LogLevel) {
  19779. LogLevel[LogLevel["debug"] = 0] = "debug";
  19780. LogLevel[LogLevel["info"] = 1] = "info";
  19781. LogLevel[LogLevel["warn"] = 2] = "warn";
  19782. LogLevel[LogLevel["error"] = 3] = "error";
  19783. })(LogLevel || (LogLevel = {}));
  19784. checker.setFileSystem(new checker.NodeJSFileSystem());
  19785. exports.DtsMetadataReader = DtsMetadataReader;
  19786. exports.NgtscProgram = NgtscProgram;
  19787. exports.PartialEvaluator = PartialEvaluator;
  19788. exports.UNKNOWN_ERROR_CODE = UNKNOWN_ERROR_CODE;
  19789. exports.extractTemplate = extractTemplate;