syntax.js 520 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { CSS_TYPE } = require("../ModuleSourceTypeConstants");
  7. const LocConverter = require("../util/LocConverter");
  8. const GenericSourceProcessor = require("../util/SourceProcessor");
  9. const { deferredWrite } = GenericSourceProcessor;
  10. const {
  11. EMBEDDED_LANGUAGES,
  12. askEmbeddedRenderer,
  13. buildDataURI,
  14. collectEmbeddedDiagnostics,
  15. decodeDataURIPayload,
  16. embeddedText,
  17. languageOfMediaType,
  18. parseDataURI
  19. } = require("../util/dataURL");
  20. const { makeCacheable } = require("../util/identifier");
  21. /**
  22. * Renders source this stylesheet embeds — a `data:` URL's payload today.
  23. * Returning it unchanged, or anything but text, declines it, and the URL is
  24. * emitted as written.
  25. * @typedef {(source: string, info: { type: string, hostType: string }) => string | undefined} EmbeddedSourceRenderer
  26. */
  27. /**
  28. * One embedded source recorded for a caller that can only answer
  29. * asynchronously, and the text to print once it has.
  30. * @typedef {import("../util/dataURL").DeferredEmbeddedSource} DeferredEmbeddedSource
  31. */
  32. const {
  33. ABSOLUTE_UNIT_SCALE,
  34. ALPHA_VALUE_PROPERTIES,
  35. ANGLE_UNITS,
  36. AUTO_SECOND_VALUE_PROPERTIES,
  37. BOX_FAMILY_PREFIX,
  38. BOX_LONGHANDS,
  39. BOX_SHORTHANDS,
  40. CALC_REJECTING_PROPERTIES,
  41. CANONICAL_NAMES,
  42. CLAMPED_VALUE_RANGES,
  43. COLOR_ARGUMENT_FUNCTIONS,
  44. COLOR_KEYWORDS,
  45. COLOR_NAME_TO_SHORTEST,
  46. COLOR_ONLY_PROPERTIES,
  47. COMPOUND_CONTINUATIONS,
  48. CSS_WIDE_KEYWORDS,
  49. CUBIC_BEZIER_KEYWORDS,
  50. CUSTOM_IDENT_LIST_PROPERTIES,
  51. DEFAULT_GRADIENT_DIRECTIONS,
  52. DISPLAY_SHORT_FORMS,
  53. DROPPABLE_WHEN_EMPTY_AT_RULES,
  54. EASING_KEYWORDS,
  55. FAMILY_LONGHANDS,
  56. FAMILY_SLOT_CLASSES,
  57. FAMILY_SLOT_KEYWORDS,
  58. FEATURELESS_PSEUDO_CLASSES,
  59. FILTER_FUNCTION_OMITTED,
  60. FLEX_KEYWORDS,
  61. FONT_SIZE_KEYWORDS,
  62. FONT_STRETCH_PERCENTAGES,
  63. FONT_WEIGHT_NUMBERS,
  64. GENERIC_FONT_FAMILIES,
  65. GRADIENT_LAST_POSITIONS,
  66. INITIAL_VALUE_KEYWORDS,
  67. INTEGER_PROPERTIES,
  68. KEYWORD_ONLY_PROPERTIES,
  69. LEGACY_PSEUDO_ELEMENTS,
  70. LENGTH_ONLY_FUNCTIONS,
  71. LINEAR_GRADIENTS,
  72. MATH_FUNCTIONS,
  73. MATH_FUNCTION_ARITY,
  74. MATH_FUNCTION_FOLD,
  75. MATH_FUNCTION_KEYWORDS,
  76. MATH_FUNCTION_SUM_ARGUMENTS,
  77. MERGEABLE_AT_RULES,
  78. MERGE_LONGHANDS,
  79. NEGATIVE_ACCEPTING_PROPERTIES,
  80. NEVER,
  81. NTH_NAMED_EQUIVALENTS,
  82. NTH_PSEUDO_FUNCTIONS,
  83. OMITTABLE_INITIAL_KEYWORDS,
  84. ONE_VALUE_PAIR_SHORTHANDS,
  85. PAIR_LONGHANDS,
  86. PLACE_SHORTHANDS,
  87. POSITION_PROPERTIES,
  88. POSITION_X_KEYWORDS,
  89. POSITION_Y_KEYWORDS,
  90. PREFIXED_AT_RULES,
  91. PREFIXED_PROPERTIES,
  92. PREFIXED_SELECTORS,
  93. PREFIXED_SPELLING_KEYWORDS,
  94. PREFIXED_VALUES,
  95. PREFIX_WINDOWS,
  96. PREFIX_WINDOW_STARTS,
  97. RATIO_PROPERTIES,
  98. REPEAT_STYLE_KEYWORDS,
  99. REPEAT_STYLE_PROPERTIES,
  100. RGB_TO_NAME,
  101. SELECTOR_FUNCTIONS,
  102. SELECTOR_SUPPORTED_FROM,
  103. SHADOW_PROPERTIES,
  104. SHORTHAND_INITIAL_KEYWORDS,
  105. SLASH_BOX_SHORTHANDS,
  106. SLASH_LONGHANDS,
  107. STEPPED_FUNCTIONS,
  108. SUBSTITUTION_FUNCTIONS,
  109. SUPPORTED_FROM,
  110. SUPPORT_BROWSERS,
  111. SUPPORT_PROFILES,
  112. TRANSITION_BEHAVIORS,
  113. UNIT_CONVERSION_TARGETS,
  114. UNIT_GROUP_BASE,
  115. UNSHARED_LONGHAND_KEYWORDS,
  116. X_AXIS_TRANSFORMS,
  117. ZERO_ANGLE_FUNCTIONS,
  118. ZERO_UNIT_KEEPING_PROPERTIES,
  119. exactAdd,
  120. exactDivide,
  121. exactMultiply
  122. } = require("./data");
  123. // spec: https://drafts.csswg.org/css-syntax/
  124. /**
  125. * @typedef {object} CssWhitespaceToken
  126. * @property {number} type
  127. * @property {number} start byte offset of the first whitespace code point
  128. * @property {number} end byte offset just past the last whitespace code point
  129. */
  130. /**
  131. * @typedef {object} CssCommentToken
  132. * @property {number} type
  133. * @property {number} start byte offset of the opening `/`
  134. * @property {number} end byte offset just past the closing `/`
  135. */
  136. /**
  137. * @typedef {object} CssStringToken
  138. * @property {number} type
  139. * @property {number} start byte offset of the opening quote
  140. * @property {number} end byte offset just past the closing quote (or EOF for unterminated strings)
  141. */
  142. /**
  143. * @typedef {object} CssBadStringToken
  144. * @property {number} type
  145. * @property {number} start byte offset of the opening quote
  146. * @property {number} end byte offset where parsing gave up (typically the newline that broke the string)
  147. */
  148. /**
  149. * @typedef {object} CssLeftCurlyBracketToken
  150. * @property {number} type
  151. * @property {number} start byte offset of `{`
  152. * @property {number} end `start + 1`
  153. */
  154. /**
  155. * @typedef {object} CssRightCurlyBracketToken
  156. * @property {number} type
  157. * @property {number} start byte offset of `}`
  158. * @property {number} end `start + 1`
  159. */
  160. /**
  161. * @typedef {object} CssLeftSquareBracketToken
  162. * @property {number} type
  163. * @property {number} start byte offset of `[`
  164. * @property {number} end `start + 1`
  165. */
  166. /**
  167. * @typedef {object} CssRightSquareBracketToken
  168. * @property {number} type
  169. * @property {number} start byte offset of `]`
  170. * @property {number} end `start + 1`
  171. */
  172. /**
  173. * @typedef {object} CssLeftParenthesisToken
  174. * @property {number} type
  175. * @property {number} start byte offset of `(`
  176. * @property {number} end `start + 1`
  177. */
  178. /**
  179. * @typedef {object} CssRightParenthesisToken
  180. * @property {number} type
  181. * @property {number} start byte offset of `)`
  182. * @property {number} end `start + 1`
  183. */
  184. /**
  185. * @typedef {object} CssFunctionToken
  186. * @property {number} type
  187. * @property {number} start byte offset of the function name's first code point
  188. * @property {number} end byte offset just past the `(` that closes the function token
  189. */
  190. /**
  191. * @typedef {object} CssUrlToken
  192. * @property {number} type
  193. * @property {number} start byte offset of the `url(` keyword (i.e. the `u`)
  194. * @property {number} end byte offset just past the closing `)` (or EOF)
  195. * @property {number} contentStart byte offset of the first code point of the unquoted URL content (post leading whitespace)
  196. * @property {number} contentEnd byte offset just past the last code point of the unquoted URL content (pre trailing whitespace / `)` / EOF)
  197. */
  198. /**
  199. * @typedef {object} CssBadUrlToken
  200. * @property {number} type
  201. * @property {number} start byte offset of the `url(` keyword
  202. * @property {number} end byte offset where parsing gave up (past the recovery `)` or EOF)
  203. */
  204. /**
  205. * @typedef {object} CssColonToken
  206. * @property {number} type
  207. * @property {number} start byte offset of `:`
  208. * @property {number} end `start + 1`
  209. */
  210. /**
  211. * @typedef {object} CssAtKeywordToken
  212. * @property {number} type
  213. * @property {number} start byte offset of `@`
  214. * @property {number} end byte offset just past the last ident-sequence code point
  215. */
  216. /**
  217. * @typedef {object} CssDelimToken
  218. * @property {number} type
  219. * @property {number} start byte offset of the delim code point
  220. * @property {number} end `start + 1`
  221. */
  222. /**
  223. * @typedef {object} CssIdentToken
  224. * @property {number} type
  225. * @property {number} start byte offset of the first ident code point
  226. * @property {number} end byte offset just past the last ident-sequence code point
  227. */
  228. /**
  229. * @typedef {object} CssPercentageToken
  230. * @property {number} type
  231. * @property {number} start byte offset of the first numeric code point
  232. * @property {number} end byte offset just past the `%`
  233. */
  234. /**
  235. * @typedef {object} CssNumberToken
  236. * @property {number} type
  237. * @property {number} start byte offset of the first numeric code point
  238. * @property {number} end byte offset just past the last numeric code point
  239. */
  240. /**
  241. * @typedef {object} CssDimensionToken
  242. * @property {number} type
  243. * @property {number} start byte offset of the first numeric code point
  244. * @property {number} end byte offset just past the last unit ident code point
  245. * @property {number} unitStart byte offset of the first unit-ident code point (== end of the numeric run)
  246. */
  247. /**
  248. * @typedef {object} CssHashToken
  249. * @property {number} type
  250. * @property {number} start byte offset of `#`
  251. * @property {number} end byte offset just past the last ident-sequence code point
  252. * @property {boolean} isId true when the hash starts an ident sequence (`#foo`), false for non-ident hashes (`#1abc`)
  253. */
  254. /**
  255. * @typedef {object} CssSemicolonToken
  256. * @property {number} type
  257. * @property {number} start byte offset of `;`
  258. * @property {number} end `start + 1`
  259. */
  260. /**
  261. * @typedef {object} CssCommaToken
  262. * @property {number} type
  263. * @property {number} start byte offset of `,`
  264. * @property {number} end `start + 1`
  265. */
  266. /**
  267. * @typedef {object} CssCdoToken
  268. * @property {number} type
  269. * @property {number} start byte offset of `<`
  270. * @property {number} end byte offset just past `<!--`
  271. */
  272. /**
  273. * @typedef {object} CssCdcToken
  274. * @property {number} type
  275. * @property {number} start byte offset of `-`
  276. * @property {number} end byte offset just past `-->`
  277. */
  278. /**
  279. * @typedef {CssWhitespaceToken | CssCommentToken | CssStringToken | CssBadStringToken | CssLeftCurlyBracketToken | CssRightCurlyBracketToken | CssLeftSquareBracketToken | CssRightSquareBracketToken | CssLeftParenthesisToken | CssRightParenthesisToken | CssFunctionToken | CssUrlToken | CssBadUrlToken | CssColonToken | CssAtKeywordToken | CssDelimToken | CssIdentToken | CssPercentageToken | CssNumberToken | CssDimensionToken | CssHashToken | CssSemicolonToken | CssCommaToken | CssCdoToken | CssCdcToken} CssToken
  280. */
  281. const CC_LINE_FEED = "\n".charCodeAt(0);
  282. const CC_CARRIAGE_RETURN = "\r".charCodeAt(0);
  283. const CC_FORM_FEED = "\f".charCodeAt(0);
  284. const CC_TAB = "\t".charCodeAt(0);
  285. const CC_SPACE = " ".charCodeAt(0);
  286. const CC_SOLIDUS = "/".charCodeAt(0);
  287. const CC_REVERSE_SOLIDUS = "\\".charCodeAt(0);
  288. const CC_ASTERISK = "*".charCodeAt(0);
  289. const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
  290. const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0);
  291. const CC_LEFT_CURLY = "{".charCodeAt(0);
  292. const CC_RIGHT_CURLY = "}".charCodeAt(0);
  293. const CC_LEFT_SQUARE = "[".charCodeAt(0);
  294. const CC_RIGHT_SQUARE = "]".charCodeAt(0);
  295. const CC_QUOTATION_MARK = '"'.charCodeAt(0);
  296. const CC_APOSTROPHE = "'".charCodeAt(0);
  297. const CC_FULL_STOP = ".".charCodeAt(0);
  298. const CC_COLON = ":".charCodeAt(0);
  299. const CC_SEMICOLON = ";".charCodeAt(0);
  300. const CC_COMMA = ",".charCodeAt(0);
  301. const CC_PERCENTAGE = "%".charCodeAt(0);
  302. const CC_AT_SIGN = "@".charCodeAt(0);
  303. const CC_LOW_LINE = "_".charCodeAt(0);
  304. const CC_LOWER_A = "a".charCodeAt(0);
  305. const CC_LOWER_D = "d".charCodeAt(0);
  306. const CC_LOWER_F = "f".charCodeAt(0);
  307. const CC_LOWER_E = "e".charCodeAt(0);
  308. const CC_LOWER_U = "u".charCodeAt(0);
  309. const CC_LOWER_R = "r".charCodeAt(0);
  310. const CC_LOWER_L = "l".charCodeAt(0);
  311. const CC_LOWER_T = "t".charCodeAt(0);
  312. const CC_LOWER_Z = "z".charCodeAt(0);
  313. const CC_EXCLAMATION = "!".charCodeAt(0);
  314. const CC_UPPER_A = "A".charCodeAt(0);
  315. const CC_UPPER_F = "F".charCodeAt(0);
  316. const CC_UPPER_E = "E".charCodeAt(0);
  317. const CC_UPPER_Z = "Z".charCodeAt(0);
  318. const CC_0 = "0".charCodeAt(0);
  319. const CC_9 = "9".charCodeAt(0);
  320. const CC_NUMBER_SIGN = "#".charCodeAt(0);
  321. const CC_PLUS_SIGN = "+".charCodeAt(0);
  322. const CC_HYPHEN_MINUS = "-".charCodeAt(0);
  323. const CC_LESS_THAN_SIGN = "<".charCodeAt(0);
  324. const CC_GREATER_THAN_SIGN = ">".charCodeAt(0);
  325. const CC_TILDE = "~".charCodeAt(0);
  326. const CC_EQUALS_SIGN = "=".charCodeAt(0);
  327. // Lexer token types (CSS Syntax Level 3 §4) plus the `<eof-token>`. Numeric so
  328. // the per-token `type` slot stays compact and `next` / `consume` / the consume
  329. // algorithms dispatch on integer `===` instead of string comparison. Exported
  330. // alongside `readToken` (the per-token lexer primitive) for the unit test.
  331. const TT_COMMENT = 1;
  332. const TT_WHITESPACE = 2;
  333. const TT_STRING = 3;
  334. const TT_BAD_STRING_TOKEN = 4;
  335. const TT_HASH = 5;
  336. const TT_DELIM = 6;
  337. // The three opening brackets are kept contiguous (7..9) so "is this an opening
  338. // bracket?" is a single range check (`>= TT_LEFT_PARENTHESIS && <= TT_LEFT_CURLY_BRACKET`).
  339. const TT_LEFT_PARENTHESIS = 7;
  340. const TT_LEFT_SQUARE_BRACKET = 8;
  341. const TT_LEFT_CURLY_BRACKET = 9;
  342. const TT_RIGHT_PARENTHESIS = 10;
  343. const TT_RIGHT_SQUARE_BRACKET = 11;
  344. const TT_RIGHT_CURLY_BRACKET = 12;
  345. const TT_COMMA = 13;
  346. const TT_COLON = 14;
  347. const TT_SEMICOLON = 15;
  348. const TT_AT_KEYWORD = 16;
  349. const TT_FUNCTION = 17;
  350. const TT_URL = 18;
  351. const TT_BAD_URL_TOKEN = 19;
  352. const TT_IDENTIFIER = 20;
  353. const TT_NUMBER = 21;
  354. const TT_PERCENTAGE = 22;
  355. const TT_DIMENSION = 23;
  356. const TT_CDO = 24;
  357. const TT_CDC = 25;
  358. const TT_EOF = 26;
  359. // The opening bracket types (7..9) and their mirror closers (10..12) are laid
  360. // out so a closer is always `opener + 3`; `consumeASimpleBlock` uses that
  361. // directly. The associated block char is a dense array indexed by the opener's
  362. // offset from `TT_LEFT_PARENTHESIS` — a plain element load instead of a numeric
  363. // object-key lookup.
  364. /** @type {SimpleBlockToken[]} */
  365. const BLOCK_TOKEN_CHAR = ["(", "[", "{"];
  366. /**
  367. * @param {number} cc char code
  368. * @returns {boolean} true, if cc is a newline (per the spec: LF, CR, or FF)
  369. */
  370. const _isNewline = (cc) =>
  371. cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED;
  372. /**
  373. * If the source had a CR followed by an LF, advance past the LF —
  374. * the spec normalises CRLF to LF during preprocessing.
  375. * @param {number} cc char code already consumed (the CR)
  376. * @param {string} input input
  377. * @param {number} pos position just past `cc`
  378. * @returns {number} position past the CRLF pair (or unchanged for bare CR)
  379. */
  380. const consumeExtraNewline = (cc, input, pos) => {
  381. if (cc === CC_CARRIAGE_RETURN && input.charCodeAt(pos) === CC_LINE_FEED) {
  382. pos++;
  383. }
  384. return pos;
  385. };
  386. /**
  387. * @param {number} cc char code
  388. * @returns {boolean} true, if cc is space or tab
  389. */
  390. const _isSpace = (cc) => cc === CC_SPACE || cc === CC_TAB;
  391. /**
  392. * @param {number} cc char code
  393. * @returns {boolean} true, if cc is whitespace (space/tab/newline)
  394. */
  395. // Space-first: U+0020 is the common case, so it short-circuits before the
  396. // rarer tab / newline tests.
  397. const _isWhiteSpace = (cc) => _isSpace(cc) || _isNewline(cc);
  398. // Whitespace membership table for the run-consumption loop — one load instead
  399. // of up to five compares per char. EOF (NaN) / non-ASCII index to undefined.
  400. const _wsTable = new Uint8Array(128);
  401. _wsTable[CC_SPACE] = 1;
  402. _wsTable[CC_TAB] = 1;
  403. _wsTable[CC_LINE_FEED] = 1;
  404. _wsTable[CC_CARRIAGE_RETURN] = 1;
  405. _wsTable[CC_FORM_FEED] = 1;
  406. /**
  407. * @param {number} cc char code
  408. * @returns {boolean} true, if cc is a digit
  409. */
  410. const _isDigit = (cc) => cc >= CC_0 && cc <= CC_9;
  411. /**
  412. * @param {number} cc char code
  413. * @returns {boolean} true, if cc is a hex digit
  414. */
  415. const _isHexDigit = (cc) =>
  416. _isDigit(cc) ||
  417. (cc >= CC_UPPER_A && cc <= CC_UPPER_F) ||
  418. (cc >= CC_LOWER_A && cc <= CC_LOWER_F);
  419. /**
  420. * @param {number} cc char code
  421. * @returns {boolean} is letter (a-z / A-Z)
  422. */
  423. const _isLetter = (cc) =>
  424. (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
  425. (cc >= CC_UPPER_A && cc <= CC_UPPER_Z);
  426. /**
  427. * Spec: ident-start = letter / non-ASCII / `_`. Internal helper that
  428. * accepts an explicit char code (lookahead).
  429. * @param {number} cc char code
  430. * @returns {boolean} true, if cc is an ident-start code point
  431. */
  432. const _isIdentStartCodePointCC = (cc) =>
  433. _isLetter(cc) || cc >= 0x80 || cc === CC_LOW_LINE;
  434. /**
  435. * Spec: ident-code = ident-start / digit / hyphen-minus.
  436. */
  437. // Full `charCodeAt` range (0..0xFFFF) so the per-code-point ident test is one
  438. // table load with no `cc < 128` branch — `_consumeAnIdentSequence` runs this on
  439. // every character of every ident / class / property name (the tokenizer's
  440. // hottest loop). Every non-ASCII code unit (>= 0x80) is an ident code point per
  441. // spec, so those default to 1; only the ASCII rows carry real classification.
  442. // Callers must index with `cc | 0`: EOF (`charCodeAt` → NaN) becomes 0 (NUL,
  443. // not an ident) — a raw NaN index is an out-of-range access that permanently
  444. // degrades the load site's IC.
  445. const _identCharTable = new Uint8Array(0x10000).fill(1);
  446. for (let i = 0; i < 128; i++) {
  447. _identCharTable[i] =
  448. _isLetter(i) || i === CC_LOW_LINE || _isDigit(i) || i === CC_HYPHEN_MINUS
  449. ? 1
  450. : 0;
  451. }
  452. /**
  453. * @param {number} cc char code
  454. * @returns {boolean} true, if cc is an ident-sequence code point
  455. */
  456. const _isIdentCodePoint = (cc) => _identCharTable[cc | 0] === 1;
  457. /**
  458. * ASCII case-insensitive equality against a lowercase literal — avoids the
  459. * `toLowerCase()` allocation and matches CSS's ASCII case-insensitive keyword
  460. * matching. `lit` must be lowercase ASCII.
  461. * @param {string} s string to test
  462. * @param {string} lit lowercase ASCII literal to match
  463. * @returns {boolean} true, if `s` equals `lit` ignoring ASCII case
  464. */
  465. const equalsLowerCase = (s, lit) => {
  466. if (s.length !== lit.length) return false;
  467. for (let i = 0; i < lit.length; i++) {
  468. let c = s.charCodeAt(i);
  469. if (c >= CC_UPPER_A && c <= CC_UPPER_Z) c |= 0x20;
  470. if (c !== lit.charCodeAt(i)) return false;
  471. }
  472. return true;
  473. };
  474. /**
  475. * Case-sensitive equality of a source range against a literal — no slice.
  476. * @param {string} input source
  477. * @param {number} start range start
  478. * @param {number} end range end (exclusive)
  479. * @param {string} lit literal to match
  480. * @returns {boolean} true when the range equals `lit`
  481. */
  482. const rangeEquals = (input, start, end, lit) =>
  483. end - start === lit.length && input.startsWith(lit, start);
  484. /**
  485. * ASCII case-insensitive equality of a source range against a lowercase ASCII literal — no slice.
  486. * @param {string} input source
  487. * @param {number} start range start
  488. * @param {number} end range end (exclusive)
  489. * @param {string} lit lowercase ASCII literal to match
  490. * @returns {boolean} true when the range equals `lit` ignoring ASCII case
  491. */
  492. const rangeEqualsLowerCase = (input, start, end, lit) => {
  493. if (end - start !== lit.length) return false;
  494. for (let i = 0; i < lit.length; i++) {
  495. let c = input.charCodeAt(start + i);
  496. if (c >= CC_UPPER_A && c <= CC_UPPER_Z) c |= 0x20;
  497. if (c !== lit.charCodeAt(i)) return false;
  498. }
  499. return true;
  500. };
  501. /**
  502. * `s.toLowerCase()` that returns `s` itself (no allocation) when it can't
  503. * change — no ASCII uppercase and no non-ASCII (whose Unicode case mapping is
  504. * left to the real `toLowerCase`).
  505. * @param {string} s string
  506. * @returns {string} lowercased string
  507. */
  508. const toLowerCaseIfNeeded = (s) => {
  509. for (let i = 0; i < s.length; i++) {
  510. const c = s.charCodeAt(i);
  511. if ((c >= CC_UPPER_A && c <= CC_UPPER_Z) || c > 127) return s.toLowerCase();
  512. }
  513. return s;
  514. };
  515. // Every ASCII uppercase letter in the tail of a name that carries one; the fold
  516. // below is the only place either is used, and only for such a name.
  517. const _ASCII_UPPER_RE = /[A-Z]/g;
  518. /** @type {(one: string) => string} */
  519. const _toAsciiLower = (one) => String.fromCharCode(one.charCodeAt(0) | 0x20);
  520. /**
  521. * A name CSS matches ASCII case-insensitively — a property, at-keyword,
  522. * function, pseudo, media feature or unit — written lowercase, so the same name
  523. * is the same bytes wherever it is spelled. Only ASCII letters map, the matching
  524. * being ASCII-only; a name carrying an escape is written back as authored, since
  525. * `\G` and `\g` name different characters.
  526. * @param {string} s the name
  527. * @returns {string} it, lowercased
  528. */
  529. const asciiLowerCaseName = (s) => {
  530. // Asked natively first: `toLowerCase` hands back the very string it was given
  531. // where nothing folds, so this is a pointer compare and no allocation for
  532. // every name in a stylesheet written in one case — which walking the
  533. // characters here was costing more than the fold itself.
  534. if (s.toLowerCase() === s) return s;
  535. // Something folds, but only ASCII may: `Ä` is its own character, and a name
  536. // whose only capital is one of those is written back as authored.
  537. let at = -1;
  538. for (let i = 0; i < s.length; i++) {
  539. const c = s.charCodeAt(i);
  540. if (c >= CC_UPPER_A && c <= CC_UPPER_Z) {
  541. at = i;
  542. break;
  543. }
  544. }
  545. if (at === -1) return s;
  546. // An escape names a character by case (`\G` is not `\g`), so a name carrying
  547. // one is written back as authored.
  548. if (s.includes("\\")) return s;
  549. return s.slice(0, at) + s.slice(at).replace(_ASCII_UPPER_RE, _toAsciiLower);
  550. };
  551. /**
  552. * A custom property name (`<dashed-ident>`): a `--`-prefixed identifier other than bare `--`.
  553. * @param {string} identifier identifier
  554. * @returns {boolean} true when identifier is dashed, otherwise false
  555. */
  556. const isDashedIdentifier = (identifier) =>
  557. identifier.startsWith("--") && identifier.length >= 3;
  558. /**
  559. * Consume an escaped code point.
  560. * @param {string} input input
  561. * @param {number} pos position just past the `\`
  562. * @returns {number} position past the escape sequence
  563. */
  564. const _consumeAnEscapedCodePoint = (input, pos) => {
  565. // Caller has verified the `\` and the next code point form a valid
  566. // escape. Hex digits: consume up to 6 hex digits, then one optional
  567. // whitespace. Non-hex: consume one code point.
  568. // `\` at EOF: nothing to consume; return pos so callers don't overrun.
  569. if (pos >= input.length) return pos;
  570. const cc = input.charCodeAt(pos);
  571. pos++;
  572. if (pos === input.length) return pos;
  573. if (_isHexDigit(cc)) {
  574. for (let i = 0; i < 5; i++) {
  575. if (!_isHexDigit(input.charCodeAt(pos))) break;
  576. pos++;
  577. }
  578. const trail = input.charCodeAt(pos);
  579. if (_isWhiteSpace(trail)) {
  580. pos++;
  581. pos = consumeExtraNewline(trail, input, pos);
  582. }
  583. }
  584. return pos;
  585. };
  586. /**
  587. * CSS Syntax §4.3.7, entered at the `\` so a caller scanning raw source can
  588. * step over an escape without re-deriving what it spans.
  589. * @param {string} input input
  590. * @param {number} pos position of the `\`
  591. * @returns {number} position past the escape sequence
  592. */
  593. const skipEscape = (input, pos) => _consumeAnEscapedCodePoint(input, pos + 1);
  594. /**
  595. * Spec: "two code points are a valid escape" — first is `\`, second is
  596. * not a newline.
  597. * @param {string} input input
  598. * @param {number} pos position of the second code point
  599. * @param {number=} f first code point (defaults to `input.charCodeAt(pos - 1)`)
  600. * @param {number=} s second code point (defaults to `input.charCodeAt(pos)`)
  601. * @returns {boolean} true, if the two code points form a valid escape
  602. */
  603. const _ifTwoCodePointsAreValidEscape = (input, pos, f, s) => {
  604. const first = f || input.charCodeAt(pos - 1);
  605. const second = s || input.charCodeAt(pos);
  606. if (first !== CC_REVERSE_SOLIDUS) return false;
  607. if (_isNewline(second)) return false;
  608. return true;
  609. };
  610. /**
  611. * Spec: "three code points would start an ident sequence".
  612. * @param {string} input input
  613. * @param {number} pos position
  614. * @param {number=} f first code point (defaults to `input.charCodeAt(pos - 1)`)
  615. * @param {number=} s second code point (defaults to `input.charCodeAt(pos)`)
  616. * @param {number=} t third code point (defaults to `input.charCodeAt(pos + 1)`)
  617. * @returns {boolean} true, if the three code points start an ident sequence
  618. */
  619. const _ifThreeCodePointsWouldStartAnIdentSequence = (input, pos, f, s, t) => {
  620. const first = f || input.charCodeAt(pos - 1);
  621. const second = s || input.charCodeAt(pos);
  622. const third = t || input.charCodeAt(pos + 1);
  623. if (first === CC_HYPHEN_MINUS) {
  624. return (
  625. _isIdentStartCodePointCC(second) ||
  626. second === CC_HYPHEN_MINUS ||
  627. _ifTwoCodePointsAreValidEscape(input, pos, second, third)
  628. );
  629. }
  630. if (_isIdentStartCodePointCC(first)) return true;
  631. if (first === CC_REVERSE_SOLIDUS) {
  632. return _ifTwoCodePointsAreValidEscape(input, pos, first, second);
  633. }
  634. return false;
  635. };
  636. /**
  637. * Spec: "three code points would start a number".
  638. * @param {string} input input
  639. * @param {number} pos position
  640. * @param {number=} f first code point
  641. * @param {number=} s second code point
  642. * @param {number=} t third code point
  643. * @returns {boolean} true, if the three code points start a number
  644. */
  645. const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => {
  646. const first = f || input.charCodeAt(pos - 1);
  647. const second = s || input.charCodeAt(pos);
  648. const third = t || input.charCodeAt(pos + 1);
  649. if (first === CC_PLUS_SIGN || first === CC_HYPHEN_MINUS) {
  650. if (_isDigit(second)) return true;
  651. return second === CC_FULL_STOP && _isDigit(third);
  652. }
  653. if (first === CC_FULL_STOP) return _isDigit(second);
  654. /* istanbul ignore next -- @preserve: spec-general; every caller passes `pos` just past a +/-/. so `first` is never a bare digit here */
  655. return _isDigit(first);
  656. };
  657. /**
  658. * Consume an ident sequence (no validation of the first code points).
  659. * @param {string} input input
  660. * @param {number} pos position
  661. * @returns {number} position just past the last ident-sequence code point
  662. */
  663. const _consumeAnIdentSequence = (input, pos) => {
  664. // Hot loop (every ident, at-keyword, hash, function name, unit). Both checks
  665. // are inlined from `_isIdentCodePoint` / `_ifTwoCodePointsAreValidEscape`: the
  666. // ident test is a single full-range table load (no `cc < 128` branch), and the
  667. // escape test reads the following code point only when `cc` is a `\` (rare)
  668. // instead of eagerly.
  669. for (;;) {
  670. const cc = input.charCodeAt(pos) | 0;
  671. pos++;
  672. if (_identCharTable[cc] === 1) {
  673. continue;
  674. }
  675. if (cc === CC_REVERSE_SOLIDUS && !_isNewline(input.charCodeAt(pos))) {
  676. pos = _consumeAnEscapedCodePoint(input, pos);
  677. continue;
  678. }
  679. return pos - 1;
  680. }
  681. };
  682. /**
  683. * @param {number} cc char code
  684. * @returns {boolean} true, if cc is a non-printable code point
  685. */
  686. const _isNonPrintableCodePoint = (cc) =>
  687. (cc >= 0x00 && cc <= 0x08) ||
  688. cc === 0x0b ||
  689. (cc >= 0x0e && cc <= 0x1f) ||
  690. cc === 0x7f;
  691. /**
  692. * Consume the body of a number per the spec (does not classify integer
  693. * vs number — caller / token type handles that).
  694. * @param {string} input input
  695. * @param {number} pos position at the first numeric / sign code point
  696. * @returns {number} position just past the number
  697. */
  698. const _consumeANumber = (input, pos) => {
  699. let cc = input.charCodeAt(pos);
  700. if (cc === CC_HYPHEN_MINUS || cc === CC_PLUS_SIGN) {
  701. pos++;
  702. }
  703. while (_isDigit(input.charCodeAt(pos))) pos++;
  704. if (
  705. input.charCodeAt(pos) === CC_FULL_STOP &&
  706. _isDigit(input.charCodeAt(pos + 1))
  707. ) {
  708. pos++;
  709. while (_isDigit(input.charCodeAt(pos))) pos++;
  710. }
  711. cc = input.charCodeAt(pos);
  712. if (
  713. (cc === CC_LOWER_E || cc === CC_UPPER_E) &&
  714. (((input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS ||
  715. input.charCodeAt(pos + 1) === CC_PLUS_SIGN) &&
  716. _isDigit(input.charCodeAt(pos + 2))) ||
  717. _isDigit(input.charCodeAt(pos + 1)))
  718. ) {
  719. pos++;
  720. cc = input.charCodeAt(pos);
  721. if (cc === CC_PLUS_SIGN || cc === CC_HYPHEN_MINUS) {
  722. pos++;
  723. }
  724. while (_isDigit(input.charCodeAt(pos))) pos++;
  725. }
  726. return pos;
  727. };
  728. /**
  729. * Spec recovery: when the tokenizer realises it's mid-bad-url, consume
  730. * until `)` or EOF.
  731. * @param {string} input input
  732. * @param {number} pos position
  733. * @returns {number} position past the recovery `)` or EOF
  734. */
  735. const _consumeTheRemnantsOfABadUrl = (input, pos) => {
  736. for (;;) {
  737. if (pos === input.length) return pos;
  738. const cc = input.charCodeAt(pos);
  739. pos++;
  740. if (cc === CC_RIGHT_PARENTHESIS) return pos;
  741. if (_ifTwoCodePointsAreValidEscape(input, pos)) {
  742. pos = _consumeAnEscapedCodePoint(input, pos);
  743. }
  744. }
  745. };
  746. /**
  747. * A mutable lexer token. The `next` / `consume` hot path reuses a single
  748. * instance per `TokenStream` (the lexer writes into it instead of allocating
  749. * one object per token), which also keeps the parser's `t.type` reads
  750. * monomorphic. All fields are present from construction so the shape never
  751. * transitions; type-specific fields (`isId` / `contentStart` / `contentEnd` /
  752. * `unitStart`) carry stale values for unrelated token types and are only read
  753. * by `tokenToNode` for the matching type. Pass a fresh one per `readToken` call
  754. * to collect the raw token list (e.g. tests).
  755. * @typedef {object} MutableToken
  756. * @property {number} type one of the `TT_*` constants
  757. * @property {number} start byte offset of the token's first code point
  758. * @property {number} end byte offset just past the token's last code point
  759. * @property {boolean} isId hash tokens: starts an ident sequence
  760. * @property {number} contentStart url tokens: first content code point
  761. * @property {number} contentEnd url tokens: just past the last content code point
  762. * @property {number} unitStart dimension tokens: first unit-ident code point
  763. */
  764. /**
  765. * @returns {MutableToken} a fresh lexer token with the canonical shape
  766. */
  767. const createToken = () => ({
  768. type: TT_EOF,
  769. start: 0,
  770. end: 0,
  771. isId: false,
  772. contentStart: 0,
  773. contentEnd: 0,
  774. unitStart: 0
  775. });
  776. /**
  777. * Populate `out`'s common fields and return it — the lexer functions' return
  778. * statement (kept tiny so V8 can inline it).
  779. * @param {MutableToken} out token to populate
  780. * @param {number} type one of the `TT_*` constants
  781. * @param {number} start byte offset of the token's first code point
  782. * @param {number} end byte offset just past the token's last code point
  783. * @returns {MutableToken} `out`
  784. */
  785. const fill = (out, type, start, end) => {
  786. out.type = type;
  787. out.start = start;
  788. out.end = end;
  789. return out;
  790. };
  791. /**
  792. * Whitespace token. Caller advances past the leading code point so
  793. * `start = pos - 1`.
  794. * @param {string} input input
  795. * @param {number} pos position just past the first whitespace code point
  796. * @param {MutableToken} out token to populate
  797. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  798. */
  799. function consumeSpace(input, pos, out) {
  800. const start = pos - 1;
  801. while (_wsTable[input.charCodeAt(pos)] === 1) pos++;
  802. return fill(out, TT_WHITESPACE, start, pos);
  803. }
  804. // Sticky fast-forward classes: a native run-skip over the ordinary characters of
  805. // a string / url token, so long values (data: URIs, base64) don't cost one JS
  806. // char read each. The negated classes match exactly the per-char terminators the
  807. // loops below handle (quotes / backslash / newlines for strings; plus `(`, `)`,
  808. // whitespace and non-printable code points for urls).
  809. const _STRING_SAFE = /[^"'\\\n\r\f]+/y;
  810. // eslint-disable-next-line no-control-regex -- url terminators include the control range and DEL (they make a bad-url)
  811. const _URL_SAFE = /[^\u0000-\u0020\u007F"'()\\]+/y;
  812. /**
  813. * Consume a string token. Caller advanced past the opening quote so
  814. * `pos - 1` holds the ending code point and `pos - 1` is the start.
  815. * @param {string} input input
  816. * @param {number} pos position just past the opening quote
  817. * @param {MutableToken} out token to populate
  818. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  819. */
  820. function consumeAStringToken(input, pos, out) {
  821. const start = pos - 1;
  822. const endingCodePoint = input.charCodeAt(pos - 1);
  823. for (;;) {
  824. _STRING_SAFE.lastIndex = pos;
  825. if (_STRING_SAFE.test(input)) pos = _STRING_SAFE.lastIndex;
  826. if (pos === input.length) {
  827. return fill(out, TT_STRING, start, pos);
  828. }
  829. const cc = input.charCodeAt(pos);
  830. pos++;
  831. if (cc === endingCodePoint) {
  832. return fill(out, TT_STRING, start, pos);
  833. }
  834. if (_isNewline(cc)) {
  835. pos--;
  836. return fill(out, TT_BAD_STRING_TOKEN, start, pos);
  837. }
  838. if (cc === CC_REVERSE_SOLIDUS) {
  839. // `\` at EOF: string ends here; emit the token so ranges cover all input.
  840. if (pos === input.length) return fill(out, TT_STRING, start, pos);
  841. if (_isNewline(input.charCodeAt(pos))) {
  842. const ccNl = input.charCodeAt(pos);
  843. pos++;
  844. pos = consumeExtraNewline(ccNl, input, pos);
  845. } else if (_ifTwoCodePointsAreValidEscape(input, pos)) {
  846. pos = _consumeAnEscapedCodePoint(input, pos);
  847. }
  848. }
  849. }
  850. }
  851. /**
  852. * `#` — hash or delim.
  853. * @param {string} input input
  854. * @param {number} pos position just past `#`
  855. * @param {MutableToken} out token to populate
  856. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  857. */
  858. function consumeNumberSign(input, pos, out) {
  859. const start = pos - 1;
  860. const first = input.charCodeAt(pos);
  861. const second = input.charCodeAt(pos + 1);
  862. if (
  863. _isIdentCodePoint(first) ||
  864. _ifTwoCodePointsAreValidEscape(input, pos, first, second)
  865. ) {
  866. const third = input.charCodeAt(pos + 2);
  867. out.isId = _ifThreeCodePointsWouldStartAnIdentSequence(
  868. input,
  869. pos,
  870. first,
  871. second,
  872. third
  873. );
  874. pos = _consumeAnIdentSequence(input, pos);
  875. return fill(out, TT_HASH, start, pos);
  876. }
  877. return fill(out, TT_DELIM, start, pos);
  878. }
  879. /**
  880. * `-` — number / cdc / ident / delim.
  881. * @param {string} input input
  882. * @param {number} pos position just past `-`
  883. * @param {MutableToken} out token to populate
  884. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  885. */
  886. function consumeHyphenMinus(input, pos, out) {
  887. // Read the two lookahead code points once; the lead is the known `-`.
  888. const second = input.charCodeAt(pos);
  889. const third = input.charCodeAt(pos + 1);
  890. if (
  891. _ifThreeCodePointsWouldStartANumber(
  892. input,
  893. pos,
  894. CC_HYPHEN_MINUS,
  895. second,
  896. third
  897. )
  898. ) {
  899. pos--;
  900. return consumeANumericToken(input, pos, out);
  901. }
  902. if (second === CC_HYPHEN_MINUS && third === CC_GREATER_THAN_SIGN) {
  903. return fill(out, TT_CDC, pos - 1, pos + 2);
  904. }
  905. if (
  906. _ifThreeCodePointsWouldStartAnIdentSequence(
  907. input,
  908. pos,
  909. CC_HYPHEN_MINUS,
  910. second,
  911. third
  912. )
  913. ) {
  914. pos--;
  915. return consumeAnIdentLikeToken(input, pos, out);
  916. }
  917. return fill(out, TT_DELIM, pos - 1, pos);
  918. }
  919. /**
  920. * `.` — number or delim.
  921. * @param {string} input input
  922. * @param {number} pos position just past `.`
  923. * @param {MutableToken} out token to populate
  924. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  925. */
  926. function consumeFullStop(input, pos, out) {
  927. const start = pos - 1;
  928. if (_ifThreeCodePointsWouldStartANumber(input, pos)) {
  929. pos--;
  930. return consumeANumericToken(input, pos, out);
  931. }
  932. return fill(out, TT_DELIM, start, pos);
  933. }
  934. /**
  935. * `+` — number or delim.
  936. * @param {string} input input
  937. * @param {number} pos position just past `+`
  938. * @param {MutableToken} out token to populate
  939. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  940. */
  941. function consumePlusSign(input, pos, out) {
  942. const start = pos - 1;
  943. if (_ifThreeCodePointsWouldStartANumber(input, pos)) {
  944. pos--;
  945. return consumeANumericToken(input, pos, out);
  946. }
  947. return fill(out, TT_DELIM, start, pos);
  948. }
  949. /**
  950. * Numeric token: number / percentage / dimension.
  951. * @param {string} input input
  952. * @param {number} pos position at the first numeric/sign code point
  953. * @param {MutableToken} out token to populate
  954. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  955. */
  956. function consumeANumericToken(input, pos, out) {
  957. const start = pos;
  958. pos = _consumeANumber(input, pos);
  959. const first = input.charCodeAt(pos);
  960. // A unit can only begin with `-`, `\`, or an ident-start code point — exactly
  961. // the cases where the §4 "would start an ident sequence" check can be true. For
  962. // a plain number (next char is whitespace / `;` / `,` / `)` / EOF, the common
  963. // case) skip the two lookahead reads and the call entirely.
  964. if (
  965. (first === CC_HYPHEN_MINUS ||
  966. first === CC_REVERSE_SOLIDUS ||
  967. _isIdentStartCodePointCC(first)) &&
  968. _ifThreeCodePointsWouldStartAnIdentSequence(
  969. input,
  970. pos,
  971. first,
  972. input.charCodeAt(pos + 1),
  973. input.charCodeAt(pos + 2)
  974. )
  975. ) {
  976. out.unitStart = pos;
  977. pos = _consumeAnIdentSequence(input, pos);
  978. return fill(out, TT_DIMENSION, start, pos);
  979. }
  980. if (first === CC_PERCENTAGE) {
  981. return fill(out, TT_PERCENTAGE, start, pos + 1);
  982. }
  983. return fill(out, TT_NUMBER, start, pos);
  984. }
  985. /**
  986. * Consume an unquoted url token. Caller has already eaten `url(` and
  987. * any leading whitespace.
  988. * @param {string} input input
  989. * @param {number} pos position at the first content code point
  990. * @param {number} fnStart byte offset of the `u` in `url(`
  991. * @param {MutableToken} out token to populate
  992. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  993. */
  994. function consumeAUrlToken(input, pos, fnStart, out) {
  995. while (_isWhiteSpace(input.charCodeAt(pos))) pos++;
  996. const contentStart = pos;
  997. out.contentStart = contentStart;
  998. for (;;) {
  999. _URL_SAFE.lastIndex = pos;
  1000. if (_URL_SAFE.test(input)) pos = _URL_SAFE.lastIndex;
  1001. if (pos === input.length) {
  1002. out.contentEnd = pos;
  1003. return fill(out, TT_URL, fnStart, pos);
  1004. }
  1005. const cc = input.charCodeAt(pos);
  1006. pos++;
  1007. if (cc === CC_RIGHT_PARENTHESIS) {
  1008. out.contentEnd = pos - 1;
  1009. return fill(out, TT_URL, fnStart, pos);
  1010. }
  1011. if (_isWhiteSpace(cc)) {
  1012. const end = pos - 1;
  1013. while (_isWhiteSpace(input.charCodeAt(pos))) pos++;
  1014. if (pos === input.length) {
  1015. out.contentEnd = end;
  1016. return fill(out, TT_URL, fnStart, pos);
  1017. }
  1018. if (input.charCodeAt(pos) === CC_RIGHT_PARENTHESIS) {
  1019. pos++;
  1020. out.contentEnd = end;
  1021. return fill(out, TT_URL, fnStart, pos);
  1022. }
  1023. pos = _consumeTheRemnantsOfABadUrl(input, pos);
  1024. return fill(out, TT_BAD_URL_TOKEN, fnStart, pos);
  1025. }
  1026. if (
  1027. cc === CC_QUOTATION_MARK ||
  1028. cc === CC_APOSTROPHE ||
  1029. cc === CC_LEFT_PARENTHESIS ||
  1030. _isNonPrintableCodePoint(cc)
  1031. ) {
  1032. pos = _consumeTheRemnantsOfABadUrl(input, pos);
  1033. return fill(out, TT_BAD_URL_TOKEN, fnStart, pos);
  1034. }
  1035. if (cc === CC_REVERSE_SOLIDUS) {
  1036. if (_ifTwoCodePointsAreValidEscape(input, pos)) {
  1037. pos = _consumeAnEscapedCodePoint(input, pos);
  1038. } else {
  1039. pos = _consumeTheRemnantsOfABadUrl(input, pos);
  1040. return fill(out, TT_BAD_URL_TOKEN, fnStart, pos);
  1041. }
  1042. }
  1043. }
  1044. }
  1045. /** Longest `url` spelling: each code point as `\` + 6 hex digits + CRLF. */
  1046. const MAX_ESCAPED_URL_LENGTH = 3 * (1 + 6 + 2);
  1047. /**
  1048. * Whether an ident spans `url` — `\75 rl` names it too, so the unescaped value
  1049. * decides. The gates keep every other function name off the slice.
  1050. * @param {string} input input
  1051. * @param {number} start ident start offset
  1052. * @param {number} end ident end offset (exclusive)
  1053. * @returns {boolean} true when the ident names `url`
  1054. */
  1055. const _identNamesUrl = (input, start, end) => {
  1056. const length = end - start;
  1057. if (length === 3) {
  1058. return (
  1059. (input.charCodeAt(start) | 0x20) === CC_LOWER_U &&
  1060. (input.charCodeAt(start + 1) | 0x20) === CC_LOWER_R &&
  1061. (input.charCodeAt(start + 2) | 0x20) === CC_LOWER_L
  1062. );
  1063. }
  1064. if (length < 4 || length > MAX_ESCAPED_URL_LENGTH) return false;
  1065. const first = input.charCodeAt(start);
  1066. if ((first | 0x20) !== CC_LOWER_U && first !== CC_REVERSE_SOLIDUS) {
  1067. return false;
  1068. }
  1069. return equalsLowerCase(unescapeIdentifier(input.slice(start, end)), "url");
  1070. };
  1071. /**
  1072. * Consume an ident-like token: ident / function / url / bad-url.
  1073. * @param {string} input input
  1074. * @param {number} pos position at the first ident-start code point
  1075. * @param {MutableToken} out token to populate
  1076. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  1077. */
  1078. function consumeAnIdentLikeToken(input, pos, out) {
  1079. const start = pos;
  1080. pos = _consumeAnIdentSequence(input, pos);
  1081. if (
  1082. input.charCodeAt(pos) === CC_LEFT_PARENTHESIS &&
  1083. _identNamesUrl(input, start, pos)
  1084. ) {
  1085. pos++;
  1086. const end = pos;
  1087. while (
  1088. _isWhiteSpace(input.charCodeAt(pos)) &&
  1089. _isWhiteSpace(input.charCodeAt(pos + 1))
  1090. ) {
  1091. pos++;
  1092. }
  1093. if (
  1094. input.charCodeAt(pos) === CC_QUOTATION_MARK ||
  1095. input.charCodeAt(pos) === CC_APOSTROPHE ||
  1096. (_isWhiteSpace(input.charCodeAt(pos)) &&
  1097. (input.charCodeAt(pos + 1) === CC_QUOTATION_MARK ||
  1098. input.charCodeAt(pos + 1) === CC_APOSTROPHE))
  1099. ) {
  1100. // End at `end` (the `(`'s closer position), not `pos` — the
  1101. // lookahead-eaten whitespace must be re-tokenized as a whitespace
  1102. // token rather than swallowed silently. The reader resumes at
  1103. // `token.end`, so returning `end` here does that.
  1104. return fill(out, TT_FUNCTION, start, end);
  1105. }
  1106. return consumeAUrlToken(input, pos, start, out);
  1107. }
  1108. if (input.charCodeAt(pos) === CC_LEFT_PARENTHESIS) {
  1109. pos++;
  1110. return fill(out, TT_FUNCTION, start, pos);
  1111. }
  1112. return fill(out, TT_IDENTIFIER, start, pos);
  1113. }
  1114. /**
  1115. * `<` — CDO or delim.
  1116. * @param {string} input input
  1117. * @param {number} pos position just past `<`
  1118. * @param {MutableToken} out token to populate
  1119. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  1120. */
  1121. function consumeLessThan(input, pos, out) {
  1122. if (
  1123. input.charCodeAt(pos) === CC_EXCLAMATION &&
  1124. input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS &&
  1125. input.charCodeAt(pos + 2) === CC_HYPHEN_MINUS
  1126. ) {
  1127. return fill(out, TT_CDO, pos - 1, pos + 3);
  1128. }
  1129. return fill(out, TT_DELIM, pos - 1, pos);
  1130. }
  1131. /**
  1132. * `@` — at-keyword or delim.
  1133. * @param {string} input input
  1134. * @param {number} pos position just past `@`
  1135. * @param {MutableToken} out token to populate
  1136. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  1137. */
  1138. function consumeCommercialAt(input, pos, out) {
  1139. const start = pos - 1;
  1140. if (
  1141. _ifThreeCodePointsWouldStartAnIdentSequence(
  1142. input,
  1143. pos,
  1144. input.charCodeAt(pos),
  1145. input.charCodeAt(pos + 1),
  1146. input.charCodeAt(pos + 2)
  1147. )
  1148. ) {
  1149. pos = _consumeAnIdentSequence(input, pos);
  1150. return fill(out, TT_AT_KEYWORD, start, pos);
  1151. }
  1152. return fill(out, TT_DELIM, start, pos);
  1153. }
  1154. /**
  1155. * `\` — escape starts an ident-like token, otherwise it's a delim.
  1156. * @param {string} input input
  1157. * @param {number} pos position just past `\`
  1158. * @param {MutableToken} out token to populate
  1159. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  1160. */
  1161. function consumeReverseSolidus(input, pos, out) {
  1162. if (_ifTwoCodePointsAreValidEscape(input, pos)) {
  1163. pos--;
  1164. return consumeAnIdentLikeToken(input, pos, out);
  1165. }
  1166. return fill(out, TT_DELIM, pos - 1, pos);
  1167. }
  1168. // `consumeAToken` dispatch: the §4 token rules keyed by the lead code point are
  1169. // === Tokenizer lead-character dispatch (CSS Syntax Level 3 §4 "consume a token") ===
  1170. //
  1171. // `consumeAToken` selects a sub-routine from the first ("lead") code point of each
  1172. // token. The §4 rules are keyed on specific code points (`"` `#` `(` digit
  1173. // ident-start …) that sit SPARSELY across the ASCII range, so a plain `switch (cc)`
  1174. // compiles to a jump table spanning U+0009..U+007D in which the most common lead —
  1175. // an ident-start letter — is not a case and reaches its handler only after the
  1176. // digit/whitespace tests miss. `_charClass` precomputes, for every ASCII code
  1177. // point, a dense handler id (`HC_*`, 0..12) so `consumeAToken` is one array load +
  1178. // a compact 13-entry jump table and idents dispatch directly. Non-ASCII
  1179. // (cc >= 128) is always ident-start per §4, so it skips the table.
  1180. //
  1181. // Extending for a spec change: repoint the code point in the build loop below; if
  1182. // it needs a new sub-routine, add an `HC_*` id, a `case` in `consumeAToken`, and a
  1183. // row here. This list is the authoritative "which lead code point dispatches
  1184. // where" map (§4 "consume a token", step by lead code point):
  1185. //
  1186. // HC_WHITESPACE whitespace U+0009 TAB U+000A LF U+000C FF U+000D CR U+0020 SPACE
  1187. // HC_STRING string start U+0022 " U+0027 '
  1188. // HC_SINGLE one-char token ( ) , : ; [ ] { } (its token type comes from `_singleTT`)
  1189. // HC_NUMBER_SIGN hash / delim U+0023 #
  1190. // HC_PLUS_SIGN number / delim U+002B +
  1191. // HC_HYPHEN_MINUS number / CDC / ident / delim U+002D -
  1192. // HC_FULL_STOP number / delim U+002E .
  1193. // HC_LESS_THAN CDO / delim U+003C <
  1194. // HC_AT_SIGN at-keyword / delim U+0040 @
  1195. // HC_REVERSE_SOLIDUS escape / delim U+005C \
  1196. // HC_DIGIT number U+0030..U+0039 0-9
  1197. // HC_IDENT ident-like U+0041..U+005A A-Z U+0061..U+007A a-z U+005F _ (plus cc >= 128)
  1198. // HC_DELIM anything else -> a single <delim-token>
  1199. //
  1200. // `_singleTT[cc]` is the token type for the HC_SINGLE code points (a second table
  1201. // so they share one handler instead of one `case` each). The default class 0 is
  1202. // the delim handler (anything not matched below), so it needs no named constant.
  1203. const HC_WHITESPACE = 1;
  1204. const HC_STRING = 2;
  1205. const HC_SINGLE = 3;
  1206. const HC_NUMBER_SIGN = 4;
  1207. const HC_PLUS_SIGN = 5;
  1208. const HC_HYPHEN_MINUS = 6;
  1209. const HC_FULL_STOP = 7;
  1210. const HC_LESS_THAN = 8;
  1211. const HC_AT_SIGN = 9;
  1212. const HC_REVERSE_SOLIDUS = 10;
  1213. const HC_DIGIT = 11;
  1214. const HC_IDENT = 12;
  1215. // Full `charCodeAt` range so `consumeAToken` dispatches with one table load and
  1216. // no `cc < 128` branch. Every non-ASCII code point (>= 0x80) is an ident-start
  1217. // lead per §4, so those rows are seeded to `HC_IDENT`; the ASCII rows below
  1218. // overwrite 0..127 with their real class.
  1219. const _charClass = new Uint8Array(0x10000).fill(HC_IDENT, 128);
  1220. const _singleTT = new Uint8Array(128);
  1221. _singleTT[CC_LEFT_PARENTHESIS] = TT_LEFT_PARENTHESIS;
  1222. _singleTT[CC_RIGHT_PARENTHESIS] = TT_RIGHT_PARENTHESIS;
  1223. _singleTT[CC_COMMA] = TT_COMMA;
  1224. _singleTT[CC_COLON] = TT_COLON;
  1225. _singleTT[CC_SEMICOLON] = TT_SEMICOLON;
  1226. _singleTT[CC_LEFT_SQUARE] = TT_LEFT_SQUARE_BRACKET;
  1227. _singleTT[CC_RIGHT_SQUARE] = TT_RIGHT_SQUARE_BRACKET;
  1228. _singleTT[CC_LEFT_CURLY] = TT_LEFT_CURLY_BRACKET;
  1229. _singleTT[CC_RIGHT_CURLY] = TT_RIGHT_CURLY_BRACKET;
  1230. // Each ASCII code point belongs to exactly one class; HC_SINGLE is seeded from
  1231. // `_singleTT` above, the rest follow §4's lead-code-point rules, and everything
  1232. // unmatched stays the delim class (0). Keep this in sync with the table above.
  1233. for (let i = 0; i < 128; i++) {
  1234. if (_singleTT[i] !== 0) {
  1235. _charClass[i] = HC_SINGLE;
  1236. } else if (_isWhiteSpace(i)) {
  1237. _charClass[i] = HC_WHITESPACE;
  1238. } else if (i === CC_QUOTATION_MARK || i === CC_APOSTROPHE) {
  1239. _charClass[i] = HC_STRING;
  1240. } else if (i === CC_NUMBER_SIGN) {
  1241. _charClass[i] = HC_NUMBER_SIGN;
  1242. } else if (i === CC_PLUS_SIGN) {
  1243. _charClass[i] = HC_PLUS_SIGN;
  1244. } else if (i === CC_HYPHEN_MINUS) {
  1245. _charClass[i] = HC_HYPHEN_MINUS;
  1246. } else if (i === CC_FULL_STOP) {
  1247. _charClass[i] = HC_FULL_STOP;
  1248. } else if (i === CC_LESS_THAN_SIGN) {
  1249. _charClass[i] = HC_LESS_THAN;
  1250. } else if (i === CC_AT_SIGN) {
  1251. _charClass[i] = HC_AT_SIGN;
  1252. } else if (i === CC_REVERSE_SOLIDUS) {
  1253. _charClass[i] = HC_REVERSE_SOLIDUS;
  1254. } else if (_isDigit(i)) {
  1255. _charClass[i] = HC_DIGIT;
  1256. } else if (_isIdentStartCodePointCC(i)) {
  1257. _charClass[i] = HC_IDENT;
  1258. }
  1259. // else stays the delim class (0)
  1260. }
  1261. /**
  1262. * Per-character dispatcher. The outer loop has already advanced past
  1263. * the lead code point (`pos - 1` is the lead).
  1264. * @param {string} input input
  1265. * @param {number} pos position just past the lead code point
  1266. * @param {number} cc the lead code point (`input.charCodeAt(pos - 1)`, already read by the caller)
  1267. * @param {MutableToken} out token to populate
  1268. * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
  1269. */
  1270. function consumeAToken(input, pos, cc, out) {
  1271. // `u` / `U` would start a unicode-range token in the spec; those are not
  1272. // produced, so they map to HC_IDENT and fall through to ident-like.
  1273. switch (_charClass[cc]) {
  1274. // Run of whitespace → one <whitespace-token>.
  1275. case HC_WHITESPACE:
  1276. return consumeSpace(input, pos, out);
  1277. // `"` / `'` → <string-token> (or <bad-string-token> on a raw newline).
  1278. case HC_STRING:
  1279. return consumeAStringToken(input, pos, out);
  1280. // One-code-point token: its type is looked up in `_singleTT` (the `(` `)`
  1281. // `,` `:` `;` `[` `]` `{` `}` set), so all of them share this arm.
  1282. case HC_SINGLE:
  1283. return fill(out, _singleTT[cc], pos - 1, pos);
  1284. // `#` → <hash-token> if an ident/escape follows, else a <delim-token>.
  1285. case HC_NUMBER_SIGN:
  1286. return consumeNumberSign(input, pos, out);
  1287. // `+` → <number-token> if it starts a number, else a <delim-token>.
  1288. case HC_PLUS_SIGN:
  1289. return consumePlusSign(input, pos, out);
  1290. // `-` → number / <CDC-token> (`-->`) / ident / <delim-token>.
  1291. case HC_HYPHEN_MINUS:
  1292. return consumeHyphenMinus(input, pos, out);
  1293. // `.` → <number-token> if a digit follows, else a <delim-token>.
  1294. case HC_FULL_STOP:
  1295. return consumeFullStop(input, pos, out);
  1296. // `<` → <CDO-token> (`<!--`), else a <delim-token>.
  1297. case HC_LESS_THAN:
  1298. return consumeLessThan(input, pos, out);
  1299. // `@` → <at-keyword-token> if an ident follows, else a <delim-token>.
  1300. case HC_AT_SIGN:
  1301. return consumeCommercialAt(input, pos, out);
  1302. // `\` → ident-like token if it's a valid escape, else a <delim-token>.
  1303. case HC_REVERSE_SOLIDUS:
  1304. return consumeReverseSolidus(input, pos, out);
  1305. // Digit → numeric token; `pos - 1` re-includes the digit the caller passed.
  1306. case HC_DIGIT:
  1307. return consumeANumericToken(input, pos - 1, out);
  1308. // Ident-start (letter / `_` / non-ASCII, incl. `u`/`U`) → ident / function /
  1309. // url token; `pos - 1` re-includes the lead code point.
  1310. case HC_IDENT:
  1311. return consumeAnIdentLikeToken(input, pos - 1, out);
  1312. default:
  1313. // HC_DELIM. EOF is impossible here (caller guarded with the outer
  1314. // loop's `pos < input.length` check). Anything else: a <delim-token>.
  1315. return fill(out, TT_DELIM, pos - 1, pos);
  1316. }
  1317. }
  1318. /**
  1319. * Read one raw token (comment / whitespace / value token) starting at byte
  1320. * `pos`, writing it into the caller-supplied `out` and returning `out`. The
  1321. * token's `end` is the next read position. Returns `undefined` at end-of-input —
  1322. * `pos >= length`, an unterminated comment, or a string ending on a trailing
  1323. * escape. This is the one tokenizer entry point: `TokenStream#next` reuses a
  1324. * single `out` across calls so the parse hot path allocates no per-token object,
  1325. * while `HtmlGenerator`'s `<style>` scan and the tests loop over it with their
  1326. * own `out`. Comment tokens are returned here; `next` filters them.
  1327. * @param {string} input input
  1328. * @param {number} pos byte offset to read from
  1329. * @param {MutableToken} out token to populate
  1330. * @returns {MutableToken | undefined} the token, or undefined at EOF
  1331. */
  1332. function readToken(input, pos, out) {
  1333. if (pos >= input.length) return undefined;
  1334. const cc = input.charCodeAt(pos);
  1335. // One-code-point token: filled without reaching the dispatch, mirroring
  1336. // `TokenStream#next`. `/` is a delim, never HC_SINGLE, so this stays ahead of
  1337. // the comment check.
  1338. if (_charClass[cc] === HC_SINGLE) {
  1339. return fill(out, _singleTT[cc], pos, pos + 1);
  1340. }
  1341. // Comment: `/*…*/` is yielded as a token (`TokenStream#next` steps over it).
  1342. if (cc === CC_SOLIDUS && input.charCodeAt(pos + 1) === CC_ASTERISK) {
  1343. const start = pos;
  1344. // Jump to the closing `*/` in one native scan instead of a per-character
  1345. // loop — comment bodies (license banners, source comments) can be long.
  1346. // No close: unterminated comment runs to EOF so ranges cover all input.
  1347. const close = input.indexOf("*/", pos + 2);
  1348. return fill(
  1349. out,
  1350. TT_COMMENT,
  1351. start,
  1352. close === -1 ? input.length : close + 2
  1353. );
  1354. }
  1355. // `consumeAToken` dispatches on the lead code point at `pos` (it expects the
  1356. // position just past the lead and the already-read lead code point).
  1357. return consumeAToken(input, pos + 1, cc, out);
  1358. }
  1359. // AST shape mirrors tabatkins/parse-css (the CSS Syntax Level 3 reference), with two deviations: nodes carry a `range` byte offset pair + a lazy `loc` getter, and have no methods beyond it.
  1360. /**
  1361. * AST node / leaf-token `type` discriminators (spec name where it has one, else
  1362. * parse-css's PascalCase). Numeric for the same reasons as the `TT_*` token
  1363. * constants: a compact `Node#type` slot and integer `===` / `Map` keys on the
  1364. * visitor hot path. Kept as a `NodeType` namespace (not bare constants) because
  1365. * consumers reference members as `NodeType.AtRule`; exported so visitor maps
  1366. * (`SourceProcessor#use`) and `CssParser` name nodes instead of a string
  1367. * literal. A lexer token type never reaches a `Node#type`.
  1368. * @enum {number}
  1369. */
  1370. const NodeType = {
  1371. Ident: 1,
  1372. Function: 2,
  1373. AtKeyword: 3,
  1374. Hash: 4,
  1375. String: 5,
  1376. BadString: 6,
  1377. Url: 7,
  1378. BadUrl: 8,
  1379. Delim: 9,
  1380. Number: 10,
  1381. Percentage: 11,
  1382. Dimension: 12,
  1383. Whitespace: 13,
  1384. Colon: 14,
  1385. Semicolon: 15,
  1386. Comma: 16,
  1387. // Preserved tokens for stray closers / CDO / CDC (kept as component values per §5.4.8 "consume a token and return it").
  1388. RightParenthesis: 17,
  1389. RightSquareBracket: 18,
  1390. RightCurlyBracket: 19,
  1391. CDO: 20,
  1392. CDC: 21,
  1393. SimpleBlock: 22,
  1394. Declaration: 23,
  1395. AtRule: 24,
  1396. QualifiedRule: 25,
  1397. Stylesheet: 26,
  1398. // Comments are never tree nodes; this type exists only so a `NodeType.Comment`
  1399. // visitor can be registered (fired during tokenization — see `grammar`).
  1400. Comment: 27,
  1401. // NOT A SPEC NODE — a workaround. §5.4 discards input a block's contents
  1402. // rejects; this keeps the source so minifying can't lose what the
  1403. // unminified asset shows. Only built while printing; a walk-only parse
  1404. // never sees one.
  1405. Raw: 28
  1406. };
  1407. const {
  1408. Ident: T_IDENT,
  1409. Function: T_FUNCTION,
  1410. AtKeyword: T_AT_KEYWORD,
  1411. Hash: T_HASH,
  1412. String: T_STRING,
  1413. BadString: T_BAD_STRING,
  1414. Url: T_URL,
  1415. BadUrl: T_BAD_URL,
  1416. Delim: T_DELIM,
  1417. Number: T_NUMBER,
  1418. Percentage: T_PERCENTAGE,
  1419. Dimension: T_DIMENSION,
  1420. Whitespace: T_WHITESPACE,
  1421. Colon: T_COLON,
  1422. Semicolon: T_SEMICOLON,
  1423. Comma: T_COMMA,
  1424. RightParenthesis: T_RIGHT_PARENTHESIS,
  1425. RightSquareBracket: T_RIGHT_SQUARE_BRACKET,
  1426. RightCurlyBracket: T_RIGHT_CURLY_BRACKET,
  1427. CDO: T_CDO,
  1428. CDC: T_CDC,
  1429. SimpleBlock: T_SIMPLE_BLOCK,
  1430. Declaration: T_DECLARATION,
  1431. AtRule: T_AT_RULE,
  1432. QualifiedRule: T_QUALIFIED_RULE,
  1433. Stylesheet: T_STYLESHEET,
  1434. Comment: T_COMMENT,
  1435. Raw: T_RAW
  1436. } = NodeType;
  1437. /**
  1438. * Base AST node — the property-accessor view the `parseA*` entry points return
  1439. * (see `_makeReader`). Every concrete node carries the `[start, end)` byte
  1440. * `range` of the source slice it covers; `loc` is computed on demand from the
  1441. * shared `LocConverter`, so line/column conversion is only paid when a consumer
  1442. * needs it. The concrete node typedefs below extend this via `&`.
  1443. *
  1444. * Inside the parser a node ref is an integer id into the columns; the reader
  1445. * exposes this property shape over a retained snapshot of those columns.
  1446. * @typedef {object} Node
  1447. * @property {number} type node-type discriminator
  1448. * @property {number} start byte offset of the node's first code point
  1449. * @property {number} end byte offset just past the node's last code point
  1450. * @property {[number, number]} range the `[start, end)` byte range
  1451. * @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location (1-based line, 0-based column)
  1452. * @property {() => string} toString source slice for this node
  1453. * @property {string} unescapedName name with CSS escapes resolved (name-bearing nodes only)
  1454. */
  1455. /**
  1456. * @param {string} s numeric text
  1457. * @returns {"+" | "-" | ""} the spec sign ("" when unsigned)
  1458. */
  1459. const _signOf = (s) => {
  1460. const c = s.charCodeAt(0);
  1461. return c === CC_PLUS_SIGN ? "+" : c === CC_HYPHEN_MINUS ? "-" : "";
  1462. };
  1463. /**
  1464. * @param {string} s numeric text (no unit / `%`)
  1465. * @returns {"integer" | "number"} the spec type flag
  1466. */
  1467. const _typeFlagOf = (s) =>
  1468. s.includes(".") || s.includes("e") || s.includes("E") ? "number" : "integer";
  1469. /**
  1470. * Leaf token node (property-accessor view) — `value` is the raw source slice
  1471. * (identifier text, quoted string including quotes, a dimension's full `123px`,
  1472. * …; hash / at-keyword drop their `#` / `@` prefix, url uses its content range).
  1473. * The `NumberToken` / `HashToken` / `UrlToken` / `DimensionToken` typedefs below
  1474. * narrow the value accessors. `numericValue` / `typeFlag` / `sign` / `unit` are
  1475. * derived from the source on read and are only meaningful on the matching token
  1476. * type; `contentStart` / `contentEnd` mark a url token's inner content range.
  1477. * @typedef {Node & { value: string, unescaped: string, numericValue: number, typeFlag: "integer" | "number" | "id" | "unrestricted", sign: "+" | "-" | "", unit: string, contentStart: number, contentEnd: number }} Token
  1478. */
  1479. /**
  1480. * Number token (`123`, `-1.5`, `+2e3`). `value` is the raw source slice (the spec's "value"); `numericValue` / `typeFlag` / `sign` are lazy getters derived from it (see `Token`).
  1481. * @typedef {Token & { numericValue: number, typeFlag: "integer" | "number", sign: "+" | "-" | "" }} NumberToken
  1482. */
  1483. /**
  1484. * Percentage token (`50%`). `value` is the raw slice including `%`; `numericValue` (without `%`) and `sign` are lazy getters.
  1485. * @typedef {Token & { numericValue: number, sign: "+" | "-" | "" }} PercentageToken
  1486. */
  1487. /**
  1488. * Dimension token (`100px`, `1.5em`). `value` is the raw slice (number + unit); `numericValue` / `typeFlag` / `sign` (of the numeric part) and `unit` (lower-cased) are lazy getters.
  1489. * @typedef {Token & { numericValue: number, typeFlag: "integer" | "number", sign: "+" | "-" | "", unit: string }} DimensionToken
  1490. */
  1491. // Spec "Assert: …" preconditions are comments only (callers satisfy them); a future `strict` option could reinstate them as throws.
  1492. /**
  1493. * Hash token (`#foo`). `value` is the name without the leading `#`; `typeFlag` is the spec type flag ("id" when the name forms a valid `<id>` selector, "unrestricted" otherwise).
  1494. * @typedef {Token & { typeFlag: "id" | "unrestricted" }} HashToken
  1495. */
  1496. /**
  1497. * Old-style unquoted URL token (`url(unquoted)`). `value` is the unquoted body;
  1498. * `contentStart` / `contentEnd` mark the inner content range in the source.
  1499. * @typedef {Token & { contentStart: number, contentEnd: number }} UrlToken
  1500. */
  1501. /**
  1502. * Function node: `name(component-values...)`. `name` is the raw source slice
  1503. * before the `(` (callers lowercase / unescape as needed); `nameStart` / `nameEnd`
  1504. * are its `[start, end)` byte offsets; `value` is the component values inside the parentheses.
  1505. * @typedef {Node & { name: string, nameStart: number, nameEnd: number, value: ComponentValue[] }} FunctionNode
  1506. */
  1507. /** @typedef {"[" | "(" | "{"} SimpleBlockToken */
  1508. /**
  1509. * Simple block (`[...]`, `(...)` not preceded by an ident, `{...}`). `token` is
  1510. * the opening character. `value` is the component values inside. This shape is
  1511. * produced by `consumeASimpleBlock` (§5.4.9) and appears in preludes.
  1512. *
  1513. * Note: `consumeABlock` (§5.4.4) returns the parsed block's separate `decls` /
  1514. * `rules` lists (per §5.4.5), not a SimpleBlock wrapper — see
  1515. * `AtRule` / `QualifiedRule`'s `declarations` and `childRules` fields.
  1516. * @typedef {Node & { token: SimpleBlockToken, value: ComponentValue[] }} SimpleBlock
  1517. */
  1518. /**
  1519. * A CSS component value (CSS Syntax §5.4.8): a preserved token, a function, or
  1520. * a simple block (`Token` also covers `HashToken` / `UrlToken`).
  1521. * @typedef {Token | FunctionNode | SimpleBlock} ComponentValue
  1522. */
  1523. /**
  1524. * A CSS rule — an at-rule or a qualified rule.
  1525. * @typedef {AtRule | QualifiedRule} Rule
  1526. */
  1527. /**
  1528. * Declaration: `name: value [!important][;]`. `name` is the raw property-name
  1529. * slice; `value` is the trimmed component-value list (whitespace stripped from
  1530. * both ends); `important` records a stripped `!important`.
  1531. * @typedef {Node & { name: string, nameStart: number, nameEnd: number, value: ComponentValue[], important: boolean }} Declaration
  1532. */
  1533. /**
  1534. * At-rule: `@name <prelude> ;` or `@name <prelude> { ... }`. `name` is the
  1535. * at-keyword without the leading `@`; `prelude` is the component values up to
  1536. * the at-rule's `;` / block / enclosing `}`. Per §5.4.2 the block is consumed
  1537. * into separate `declarations` (a `Declaration[]`) and `childRules` (a `Rule[]`,
  1538. * each an at-rule or qualified rule); both are `null` for a `;`-terminated
  1539. * at-rule. `blockStart` / `blockEnd` are the `{` start / `}` end offsets
  1540. * (webpack extension, not in spec; the spec doesn't track brace positions), or
  1541. * `-1` / `-1` when there is no block. `range[1]` points past `}` for a block, or
  1542. * at the `;` / `}` / EOF position otherwise (callers check the byte at `range[1]`
  1543. * to tell them apart).
  1544. * @typedef {Node & { name: string, nameStart: number, nameEnd: number, prelude: ComponentValue[], declarations: Declaration[] | null, childRules: Rule[] | null, blockStart: number, blockEnd: number }} AtRule
  1545. */
  1546. /**
  1547. * Qualified rule: `<prelude> { <block> }`. `prelude` is the component values
  1548. * before the `{` (selectors, keyframe parameters, …); `declarations` and
  1549. * `childRules` are the parsed `{ ... }` body (split per tabatkins/parse-css.js
  1550. * reference impl), or both `null` when EOF was hit before `{`. `blockStart` /
  1551. * `blockEnd` are the `{` start / `}` end offsets (webpack extension), or `-1` /
  1552. * `-1` when there is no block.
  1553. * @typedef {Node & { prelude: ComponentValue[], declarations: Declaration[] | null, childRules: Rule[] | null, blockStart: number, blockEnd: number }} QualifiedRule
  1554. */
  1555. /**
  1556. * Stylesheet (CSS Syntax §5.3.4): the result of `parseAStylesheet`. `rules`
  1557. * holds the top-level at-rules / qualified rules (top-level declarations are
  1558. * parse errors and never produced).
  1559. * @typedef {Node & { rules: Rule[] }} Stylesheet
  1560. */
  1561. // Lexer-token-type → AST-node-type map. A single `_makeLeaf` call site (vs a
  1562. // ~20-case switch with an alloc in each arm) keeps V8 on the fast monomorphic
  1563. // path — the switch form showed up as generic stubs in profiles. URL is the one
  1564. // type with extra own state, handled first.
  1565. const _ttToNodeType = new Uint8Array(27);
  1566. _ttToNodeType[TT_WHITESPACE] = T_WHITESPACE;
  1567. _ttToNodeType[TT_IDENTIFIER] = T_IDENT;
  1568. _ttToNodeType[TT_STRING] = T_STRING;
  1569. _ttToNodeType[TT_DELIM] = T_DELIM;
  1570. _ttToNodeType[TT_NUMBER] = T_NUMBER;
  1571. _ttToNodeType[TT_PERCENTAGE] = T_PERCENTAGE;
  1572. _ttToNodeType[TT_DIMENSION] = T_DIMENSION;
  1573. _ttToNodeType[TT_HASH] = T_HASH;
  1574. _ttToNodeType[TT_AT_KEYWORD] = T_AT_KEYWORD;
  1575. _ttToNodeType[TT_BAD_STRING_TOKEN] = T_BAD_STRING;
  1576. _ttToNodeType[TT_BAD_URL_TOKEN] = T_BAD_URL;
  1577. _ttToNodeType[TT_COLON] = T_COLON;
  1578. _ttToNodeType[TT_COMMA] = T_COMMA;
  1579. _ttToNodeType[TT_SEMICOLON] = T_SEMICOLON;
  1580. _ttToNodeType[TT_RIGHT_PARENTHESIS] = T_RIGHT_PARENTHESIS;
  1581. _ttToNodeType[TT_RIGHT_SQUARE_BRACKET] = T_RIGHT_SQUARE_BRACKET;
  1582. _ttToNodeType[TT_RIGHT_CURLY_BRACKET] = T_RIGHT_CURLY_BRACKET;
  1583. _ttToNodeType[TT_CDO] = T_CDO;
  1584. _ttToNodeType[TT_CDC] = T_CDC;
  1585. // === AST construction ===
  1586. // Nodes live in one struct-of-arrays node store: a node ref is an integer id
  1587. // into parallel typed-array columns, so per-node allocation is avoided entirely.
  1588. // The consume algorithms build nodes through the `_make*` / `_set*` primitives
  1589. // below, which write those columns directly. The streaming `grammar` walks each
  1590. // top-level node and recycles the columns; the `parseA*` entry points instead
  1591. // retain the columns as a snapshot and hand back property-accessor nodes over it
  1592. // (see `_makeReader`). Child lists are plain arrays of node ids in both modes.
  1593. // Active skip state (from `CssProcessOptions.skip`), applied by the grammar.
  1594. // `_skipTypes` is indexed by `NodeType` (1 = skip): drop that component-value
  1595. // leaf / container from declaration value and function-arg lists. The two
  1596. // prelude flags scan a rule's prelude without materializing its tree (url tokens
  1597. // / functions kept, so `url()` in a selector or `@import url(…)` still resolves).
  1598. // A skipped node is still tokenized (positions stay correct) but never pushed,
  1599. // so it is never walked or read — the caller must only skip what nothing reads.
  1600. // `parseA*` leave these at their no-skip defaults so they build the full tree.
  1601. const _NO_SKIP_TYPES = new Uint8Array(32);
  1602. // Shared frozen empty list for block bodies with no decls / no child rules (the
  1603. // common case — most rules carry only declarations). Every consumer reads these
  1604. // lists read-only and null-guards, so one immutable instance replaces ~one empty
  1605. // array allocation per rule; frozen so any errant push fails loud.
  1606. const _EMPTY_LIST = /** @type {Rule[]} */ (
  1607. /** @type {unknown} */ (Object.freeze([]))
  1608. );
  1609. // `consumeABlock`'s and `consumeABlocksContentsInto`'s results, written instead
  1610. // of returned — see there. Read them immediately; the next block overwrites them.
  1611. /** @type {Declaration[]} */
  1612. let _blockDecls = /** @type {Declaration[]} */ (
  1613. /** @type {unknown} */ (_EMPTY_LIST)
  1614. );
  1615. /** @type {Rule[]} */
  1616. let _blockRules = _EMPTY_LIST;
  1617. let _blockStart = 0;
  1618. let _blockEnd = 0;
  1619. /** @type {Declaration[]} */
  1620. let _bcDecls = /** @type {Declaration[]} */ (
  1621. /** @type {unknown} */ (_EMPTY_LIST)
  1622. );
  1623. /** @type {Rule[]} */
  1624. let _bcRules = _EMPTY_LIST;
  1625. // Whether the block just consumed streamed: read with `_bcDecls` / `_bcRules`.
  1626. // A frame slot cannot answer this, because a block that never streamed may never
  1627. // have written one (see `_streamPublishFrame`).
  1628. let _bcStreamed = false;
  1629. /** @type {Uint8Array} */
  1630. let _skipTypes = _NO_SKIP_TYPES;
  1631. // Fast-path flag: true only when a real skip set is active, so the (dominant)
  1632. // no-skip parses pay one boolean test instead of a node-type lookup per value.
  1633. let _skipActive = false;
  1634. let _skipSelectorPrelude = false;
  1635. let _skipAtRulePrelude = false;
  1636. // Scratch content-list pool: a container's value / prelude is built in a plain
  1637. // array, then sealed into the flat value buffer by `_setValue`, which returns
  1638. // the array to the pool — so a parse allocates almost no per-list arrays. An
  1639. // abandoned (never-sealed) list simply falls out of the pool.
  1640. /** @type {Node[][]} */
  1641. const _listPool = [];
  1642. const _takeList = () =>
  1643. _listPool.length > 0
  1644. ? /** @type {Node[]} */ (_listPool.pop())
  1645. : /** @type {Node[]} */ ([]);
  1646. // -- struct-of-arrays store: nodes live in reused typed-array columns --
  1647. // A node ref is its integer id; fields live in parallel arrays indexed by id.
  1648. // Two reused int slots (`_aux0/1`) plus a flags byte carry the per-type
  1649. // extras; child lists hang off three object arrays. Aux slot meaning by type:
  1650. // url: aux0 contentStart, aux1 contentEnd
  1651. // function: aux0 nameEnd
  1652. // declaration: aux0 nameEnd, flags bit0 important
  1653. // at-rule: aux0 nameEnd, aux1 blockStart (blockEnd == end)
  1654. // qualified: aux1 blockStart (blockEnd == end)
  1655. // `name` / `nameStart` / a simple block's `token` are derived from the source
  1656. // on read (see the accessors), so they need no slot. A node's main content
  1657. // (value | prelude | stylesheet rules) is a `_flat` span (see below).
  1658. // `grammar` resets `_nodeCount` to 0 after each top-level rule's walk, so the
  1659. // buffers are reused across rules and the parse allocates almost nothing.
  1660. let _capacity = 0;
  1661. let _nodeCount = 0;
  1662. let _types = new Uint8Array(0);
  1663. let _starts = new Int32Array(0);
  1664. let _ends = new Int32Array(0);
  1665. let _aux0 = new Int32Array(0);
  1666. let _aux1 = new Int32Array(0);
  1667. let _flags = new Uint8Array(0);
  1668. // Content-list spans: a container's value / prelude is `_flat[start, start+len)`
  1669. // (node refs), recycled per top-level rule like the node columns.
  1670. let _listStarts = new Int32Array(0);
  1671. let _listLens = new Int32Array(0);
  1672. let _flat = new Int32Array(0);
  1673. let _flatTop = 0;
  1674. // Peak usage of the current parse, and use-once regrow hints: after an
  1675. // over-capacity shrink the next grow jumps straight back to the previous
  1676. // parse's peak (one exact-fit allocation instead of re-doubling up).
  1677. let _peak = 0;
  1678. let _flatPeak = 0;
  1679. let _growHint = 0;
  1680. let _flatGrowHint = 0;
  1681. /** @param {number} need minimum flat-buffer capacity */
  1682. const _flatGrow = (need) => {
  1683. let cap = _flat.length || 4096;
  1684. if (_flatGrowHint > cap) cap = _flatGrowHint;
  1685. _flatGrowHint = 0;
  1686. while (cap < need) cap *= 2;
  1687. const next = new Int32Array(cap);
  1688. next.set(_flat);
  1689. _flat = next;
  1690. };
  1691. // Rule bodies live behind an id-indexed Int32 column holding `1 + index` into
  1692. // two dense append-only arrays (0 = no body): scattered id-indexed stores on a
  1693. // plain array degrade it to dictionary elements on large non-recycling parses.
  1694. // Reassigned (not mutated) when a `parseA*` parse hands its columns to a
  1695. // retained snapshot, so the next parse starts on fresh arrays.
  1696. let _bodyIdx = new Int32Array(0);
  1697. /** @type {Node[][]} */
  1698. let _declBodies = [];
  1699. /** @type {Node[][]} */
  1700. let _ruleBodies = [];
  1701. let _input = "";
  1702. // Where a comment the source never closed opened, or -1. §4.3.2 runs one to
  1703. // EOF, so at most one is open and only a span reaching the end sits inside it.
  1704. let _openCommentStart = -1;
  1705. let _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
  1706. // Node refs are integers here but typed `Node` across the parser; these are
  1707. // identity casts that just satisfy the type system at the boundary.
  1708. /** @type {(n: Node) => number} */
  1709. const _nodeIndex = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
  1710. /** @type {(i: number) => Node} */
  1711. const _nodeRef = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
  1712. /** @param {number} need minimum capacity */
  1713. const _grow = (need) => {
  1714. let cap = _capacity || 4096;
  1715. if (_growHint > cap) cap = _growHint;
  1716. _growHint = 0;
  1717. while (cap < need) cap *= 2;
  1718. const ty = new Uint8Array(cap);
  1719. ty.set(_types);
  1720. _types = ty;
  1721. const st = new Int32Array(cap);
  1722. st.set(_starts);
  1723. _starts = st;
  1724. const en = new Int32Array(cap);
  1725. en.set(_ends);
  1726. _ends = en;
  1727. const a0 = new Int32Array(cap);
  1728. a0.set(_aux0);
  1729. _aux0 = a0;
  1730. const a1 = new Int32Array(cap);
  1731. a1.set(_aux1);
  1732. _aux1 = a1;
  1733. const fl = new Uint8Array(cap);
  1734. fl.set(_flags);
  1735. _flags = fl;
  1736. const ls = new Int32Array(cap);
  1737. ls.set(_listStarts);
  1738. _listStarts = ls;
  1739. const ll = new Int32Array(cap);
  1740. ll.set(_listLens);
  1741. _listLens = ll;
  1742. const bi = new Int32Array(cap);
  1743. bi.set(_bodyIdx);
  1744. _bodyIdx = bi;
  1745. _capacity = cap;
  1746. };
  1747. /** @type {(type: number, start: number, end: number) => Node} */
  1748. const _makeLeaf = (type, start, end) => {
  1749. // Ids are 1-based: a node ref is used in truthiness checks (`if (!parent)`),
  1750. // so 0 must stay reserved for "no node".
  1751. // Leaves never read the flag / list slots — `_makeContainer` clears
  1752. // them instead, keeping the dominant leaf allocation at three writes.
  1753. const i = _nodeCount + 1;
  1754. if (i >= _capacity) _grow(i + 1);
  1755. _types[i] = type;
  1756. _starts[i] = start;
  1757. _ends[i] = end;
  1758. _nodeCount = i;
  1759. return _nodeRef(i);
  1760. };
  1761. /** @type {(type: number, start: number, end: number) => Node} */
  1762. const _makeContainer = (type, start, end) => {
  1763. const r = _makeLeaf(type, start, end);
  1764. const i = _nodeIndex(r);
  1765. _flags[i] = 0;
  1766. // Clear the content-span length so a reused id never exposes a previous
  1767. // node's children (content lists are flat spans, so zeroing the length
  1768. // suffices). `blockStart` (aux1) is NOT defaulted here: only at-rules and
  1769. // qualified rules carry a block, and they set it on every return path
  1770. // (`_setBlock`, or `-1` for the no-block forms). `blockEnd` is not stored — a
  1771. // block rule's `end` is its `blockEnd` (see `_setBlock`), else it is `-1`.
  1772. _listLens[i] = 0;
  1773. // The body slot is read only for rules (the walk guards on type), so only
  1774. // rules clear it — a recycled id must never expose a previous rule's body.
  1775. if (type === T_AT_RULE || type === T_QUALIFIED_RULE) {
  1776. _bodyIdx[i] = 0;
  1777. }
  1778. return r;
  1779. };
  1780. // Raw token value (the lazy `Token.value` form): hash / at-keyword drop their
  1781. // one-char prefix, url uses its content range. Shared by the parser's
  1782. // mid-parse reads and the accessor.
  1783. /**
  1784. * @param {number} i node id
  1785. * @returns {string} raw token value
  1786. */
  1787. const _valueOf = (i) => {
  1788. const ty = _types[i];
  1789. if (ty === T_HASH || ty === T_AT_KEYWORD) {
  1790. return _input.slice(_starts[i] + 1, _ends[i]);
  1791. }
  1792. if (ty === T_URL) return _input.slice(_aux0[i], _aux1[i]);
  1793. return _input.slice(_starts[i], _ends[i]);
  1794. };
  1795. // Module-level constants (not per-parse closures), so each consume-algorithm
  1796. // call site keeps one function identity and stays monomorphic.
  1797. /** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
  1798. const _makeUrl = (start, end, cs, ce) => {
  1799. const r = _makeLeaf(T_URL, start, end);
  1800. _aux0[_nodeIndex(r)] = cs;
  1801. _aux1[_nodeIndex(r)] = ce;
  1802. return r;
  1803. };
  1804. /** @type {(start: number) => Node} */
  1805. const _makeStylesheet = (start) => _makeContainer(T_STYLESHEET, start, start);
  1806. // Workaround node (see `NodeType.Raw`), trimmed of the whitespace the block's
  1807. // own separators already cover; an all-whitespace span yields no node.
  1808. /** @type {(start: number, end: number) => Node | undefined} */
  1809. const _makeRaw = (start, end) => {
  1810. let from = start;
  1811. let to = end;
  1812. while (from < to && _isWhiteSpace(_input.charCodeAt(to - 1))) to--;
  1813. while (from < to && _isWhiteSpace(_input.charCodeAt(from))) from++;
  1814. return from === to ? undefined : _makeLeaf(T_RAW, from, to);
  1815. };
  1816. // name / nameStart are derived from start + nameEnd; only nameEnd is stored.
  1817. /** @type {(r: Node, nameStart: number, nameEnd: number) => void} */
  1818. const _setName = (r, ns, ne) => {
  1819. _aux0[_nodeIndex(r)] = ne;
  1820. };
  1821. /** @type {(r: Node, v: number) => void} */
  1822. const _setEnd = (r, v) => {
  1823. _ends[_nodeIndex(r)] = v;
  1824. };
  1825. // `blockEnd` is not stored: for a block rule the parser sets `end` to the
  1826. // block end right after this (so `A.blockEnd` reads `end`); a `-1` blockStart
  1827. // marks the no-block forms, where `blockEnd` derives to `-1`.
  1828. /** @type {(r: Node, blockStart: number) => void} */
  1829. const _setBlock = (r, bs) => {
  1830. _aux1[_nodeIndex(r)] = bs;
  1831. };
  1832. /** @type {(r: Node) => void} */
  1833. const _setImportant = (r) => {
  1834. _flags[_nodeIndex(r)] |= 1;
  1835. };
  1836. // A simple block's token is derived from its opening char on read.
  1837. /** @type {(r: Node, ch: SimpleBlockToken) => void} */
  1838. const _setToken = (r, ch) => {};
  1839. /** @type {(r: Node, list: Node[]) => void} */
  1840. const _setValue = (r, list) => {
  1841. // Seal the finished list: copy its refs into the flat buffer and hand the
  1842. // scratch array back to the pool. The caller never touches `list` again.
  1843. const len = list.length;
  1844. // Empty seal (common in non-modules skip mode, where value/prelude leaves are
  1845. // dropped): `_makeContainer` already left `_listLens` at 0, so skip the flat
  1846. // writes entirely and just recycle the scratch array.
  1847. if (len !== 0) {
  1848. const i = _nodeIndex(r);
  1849. const start = _flatTop;
  1850. if (start + len > _flat.length) _flatGrow(start + len);
  1851. for (let k = 0; k < len; k++) {
  1852. _flat[start + k] = _nodeIndex(list[k]);
  1853. }
  1854. _flatTop = start + len;
  1855. _listStarts[i] = start;
  1856. _listLens[i] = len;
  1857. // Emptied by popping, not `length = 0`: the latter drops the array's
  1858. // backing store, so every pooled list reallocates one on its next push.
  1859. for (let k = len; k > 0; k--) list.pop();
  1860. }
  1861. _listPool.push(list);
  1862. };
  1863. /** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
  1864. const _setBody = (r, decls, childRules) => {
  1865. _bodyIdx[_nodeIndex(r)] = _declBodies.length + 1;
  1866. _declBodies.push(decls);
  1867. _ruleBodies.push(childRules);
  1868. };
  1869. /** @type {(r: Node) => number} */
  1870. const _nodeTypeOf = (r) => _types[_nodeIndex(r)];
  1871. /** @type {(r: Node) => number} */
  1872. const _nodeStartOf = (r) => _starts[_nodeIndex(r)];
  1873. /** @type {(r: Node) => string} */
  1874. const _nodeValueOf = (r) => _valueOf(_nodeIndex(r));
  1875. /** @type {(r: Node) => SimpleBlockToken} */
  1876. const _nodeTokenOf = (r) =>
  1877. /** @type {SimpleBlockToken} */ (_input[_starts[_nodeIndex(r)]]);
  1878. // A container's value, a rule's prelude, and a stylesheet's rules all seal into
  1879. // the one content-list writer, so `_setPrelude` / `_setRules` are named views of
  1880. // `_setValue` that keep the consume algorithms reading in spec terms.
  1881. const _setPrelude = _setValue;
  1882. const _setRules = _setValue;
  1883. /**
  1884. * Start a parse into the store: point it at this source, reset the node /
  1885. * flat cursors so nodes accumulate from id 1, and clear skip state (`grammar`
  1886. * sets its own afterwards; `parseA*` leave it off to build the full tree).
  1887. * @param {string} input source
  1888. * @param {LocConverter} lc loc converter
  1889. */
  1890. const _setupParse = (input, lc) => {
  1891. _input = input;
  1892. _openCommentStart = -1;
  1893. _locConverter = lc;
  1894. _nodeCount = 0;
  1895. _flatTop = 0;
  1896. _skipTypes = _NO_SKIP_TYPES;
  1897. _skipActive = false;
  1898. _skipSelectorPrelude = false;
  1899. _skipAtRulePrelude = false;
  1900. // Only `grammar` re-enables it (after this call); the standalone `parseA*`
  1901. // entry points never print, so they must not inherit a previous parse's flag.
  1902. _printing = false;
  1903. // A visitor throw mid-value can leave the flag set; never carry it over.
  1904. _inValue = false;
  1905. _inSupportsPrelude = false;
  1906. _inMediaConditionPrelude = false;
  1907. _inPropertyRule = false;
  1908. _inFunctionRule = false;
  1909. _inFeatureValuesRule = false;
  1910. _inCustomProperty = false;
  1911. _inSubstitutedValue = false;
  1912. _inGradient = false;
  1913. _substitutionSpanFrom = -1;
  1914. _substitutionSpanTo = -1;
  1915. _substitutionSpanHas = false;
  1916. _mathFunctionDepth = 0;
  1917. _steppedFunctionDepth = 0;
  1918. _convertLengthUnits = false;
  1919. _transforms = _DEFAULT_TRANSFORMS;
  1920. _commentsKept = "some";
  1921. _rewriteCustomProperties = false;
  1922. _unitScale = ABSOLUTE_UNIT_SCALE;
  1923. // Back to what the module loads with, so no parse inherits a target of its own.
  1924. _hexAlphaAllowed = true;
  1925. _doublePositionAllowed = true;
  1926. _insetShorthandAllowed = true;
  1927. _rangeSpellingAllowed = true;
  1928. _placeShorthandAllowed = true;
  1929. // Only a stylesheet naming one has an empty rule worth keeping, and a match
  1930. // inside a comment or a string costs those bytes rather than changing meaning.
  1931. _namespacePrologueOpen = NAMESPACE_AT_RULE_RE.test(input);
  1932. _overflowTwoValuesAllowed = true;
  1933. _valueDeclaration = null;
  1934. _keywordOnlyFor = null;
  1935. _keywordOnly = false;
  1936. };
  1937. /**
  1938. * Materialize a single non-block, non-function lexer token as its leaf AST node — the spec's "consume a token" result (§5.4.8 "anything else"), preserving stray closers / CDO / CDC.
  1939. * @param {MutableToken} t token from the lexer
  1940. * @returns {Node} the leaf token node
  1941. */
  1942. const tokenToNode = (t) => {
  1943. const tt = t.type;
  1944. // URL is the only leaf with own state (its content range); all others are a
  1945. // plain leaf whose node type comes from the map.
  1946. if (tt === TT_URL) {
  1947. const ut = /** @type {CssUrlToken} */ (t);
  1948. return _makeUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
  1949. }
  1950. return _makeLeaf(_ttToNodeType[tt], t.start, t.end);
  1951. };
  1952. /**
  1953. * Position-based view over the lexer — webpack's stand-in for the spec's
  1954. * "normalize into a token stream" (CSS Syntax §9). It unifies the lexer and the
  1955. * stream in one class: the `readToken` primitive lexes one token (the CSS
  1956. * tokenizer), and the spec token-stream operations `next` / `consume` /
  1957. * `discard` / `mark` / `restoreMark` / `discardMark` drive it from a byte
  1958. * cursor. `parse*` entry points wrap a source string in one of these and every
  1959. * `consume*` algorithm reads tokens from it.
  1960. *
  1961. * No token buffer is kept: the cursor is a byte offset and the only state is
  1962. * the next token (lazily tokenized once and cached until consumed). The
  1963. * declaration-vs-qualified-rule backtracking in `consumeABlocksContents`
  1964. * rewinds by `mark`ing / `restoreMark`ing that byte offset, which simply
  1965. * re-tokenizes the rewound span — comment tokens are filtered here and fire
  1966. * `onComment` once each, tracked by a monotonic high-water mark so a
  1967. * re-tokenized span never re-fires them.
  1968. *
  1969. * `SourceProcessor` is handed this class (not an instance) and threads it to
  1970. * the grammar, so a different language can drive the same visitor machinery by
  1971. * swapping the tokenizer — the per-token `readToken` primitive — for its own.
  1972. */
  1973. class TokenStream {
  1974. /**
  1975. * @param {string} input source
  1976. * @param {number=} pos start byte offset (default `0`)
  1977. * @param {LocConverter=} locConverter shared loc converter (default a fresh one over `input`)
  1978. * @param {((input: string, start: number, end: number) => number)=} onComment comment-token callback
  1979. */
  1980. constructor(
  1981. input,
  1982. pos = 0,
  1983. locConverter = new LocConverter(input),
  1984. onComment = undefined
  1985. ) {
  1986. /** @type {string} */
  1987. this.input = input;
  1988. /** @type {LocConverter} */
  1989. this.locConverter = locConverter;
  1990. this._onComment = onComment;
  1991. // Byte offset where the next token is tokenized from.
  1992. /** @type {number} */
  1993. this._pos = pos;
  1994. // Comments before this offset have already fired `onComment`; a
  1995. // re-tokenized (backtracked) span never re-fires them.
  1996. /** @type {number} */
  1997. this._commentHigh = pos;
  1998. // Single reused token the lexer writes into on the `next` path — see
  1999. // `MutableToken`. `_hasNext` marks it cached — a boolean instead of an
  2000. // object slot, so caching a token never pays a GC write barrier.
  2001. /** @type {MutableToken} */
  2002. this._tok = createToken();
  2003. /** @type {boolean} whether `_tok` holds the (lazily tokenized) next token */
  2004. this._hasNext = false;
  2005. /** @type {number[]} byte offsets to rewind to */
  2006. this._marks = [];
  2007. }
  2008. /**
  2009. * The next token (CSS Syntax §3 "next token") — the upcoming token without
  2010. * consuming it; the `<eof-token>` once the source is exhausted. This is the
  2011. * token the consume algorithms dispatch on (the spec's "process"). Tokenized
  2012. * from `_pos` on first use and cached until consumed; comment tokens are
  2013. * skipped here, firing `onComment` once each.
  2014. * @returns {MutableToken} the next token
  2015. */
  2016. next() {
  2017. if (!this._hasNext) {
  2018. const input = this.input;
  2019. const tok = this._tok;
  2020. let pos = this._pos;
  2021. for (;;) {
  2022. const t = readToken(input, pos, tok);
  2023. if (t === undefined) {
  2024. fill(tok, TT_EOF, input.length, input.length);
  2025. break;
  2026. }
  2027. if (t.type === TT_COMMENT) {
  2028. // A closed comment is `/**/` at the shortest; anything else running to
  2029. // the end never closed, and the printer writes its `*/` back.
  2030. if (
  2031. t.end === input.length &&
  2032. (t.end - t.start < 4 ||
  2033. input.charCodeAt(t.end - 2) !== CC_ASTERISK ||
  2034. input.charCodeAt(t.end - 1) !== CC_SOLIDUS)
  2035. ) {
  2036. _openCommentStart = t.start;
  2037. }
  2038. if (t.start >= this._commentHigh) {
  2039. if (this._onComment) this._onComment(input, t.start, t.end);
  2040. this._commentHigh = t.end;
  2041. }
  2042. pos = t.end;
  2043. continue;
  2044. }
  2045. break;
  2046. }
  2047. this._hasNext = true;
  2048. }
  2049. return this._tok;
  2050. }
  2051. /**
  2052. * Consume a token (CSS Syntax §3 "consume a token") — return the next token
  2053. * and advance the cursor past it. The returned token is valid until the next
  2054. * `next` re-tokenizes (the reused instance is not cleared by advancing).
  2055. * @returns {MutableToken} the consumed token
  2056. */
  2057. consume() {
  2058. const t = this.next();
  2059. if (t.type !== TT_EOF) {
  2060. this._pos = t.end;
  2061. this._hasNext = false;
  2062. }
  2063. return t;
  2064. }
  2065. /**
  2066. * Discard a token (CSS Syntax §3 "discard a token") — advance the cursor past
  2067. * the next token without returning it.
  2068. * @returns {void}
  2069. */
  2070. discard() {
  2071. const t = this.next();
  2072. if (t.type !== TT_EOF) {
  2073. this._pos = t.end;
  2074. this._hasNext = false;
  2075. }
  2076. }
  2077. /**
  2078. * Advance past the already-peeked next token, skipping the redundant `next()`
  2079. * re-check `consume` / `discard` pay. Precondition: the caller has just called
  2080. * `next()` (so `_tok` is the cached next token and `_hasNext` is true) and that
  2081. * token is not the `<eof-token>` — the hot "peek, decide, advance" sites where
  2082. * both always hold. Callers that can't guarantee a non-EOF cached token use
  2083. * `consume` / `discard` instead.
  2084. * @returns {void}
  2085. */
  2086. advance() {
  2087. this._pos = this._tok.end;
  2088. this._hasNext = false;
  2089. }
  2090. /**
  2091. * Mark (CSS Syntax §3 "mark") — push the current cursor position.
  2092. * @returns {void}
  2093. */
  2094. mark() {
  2095. this._marks.push(this._pos);
  2096. }
  2097. /**
  2098. * Restore a mark (CSS Syntax §3 "restore a mark") — pop the last mark and
  2099. * rewind the cursor to it. The rewound span is re-tokenized on the next read;
  2100. * already-fired comments are not re-fired (`_commentHigh`).
  2101. * @returns {void}
  2102. */
  2103. restoreMark() {
  2104. this._pos = /** @type {number} */ (this._marks.pop());
  2105. this._hasNext = false;
  2106. }
  2107. /**
  2108. * Discard a mark (CSS Syntax §3 "discard a mark") — pop without rewinding.
  2109. * @returns {void}
  2110. */
  2111. discardMark() {
  2112. this._marks.pop();
  2113. }
  2114. }
  2115. /**
  2116. * Normalize a `parse*` entry point's first argument into a `TokenStream`
  2117. * (CSS Syntax §9 "normalize into a token stream"). An existing `TokenStream`
  2118. * is returned as-is (consumed from its current position — it already carries
  2119. * the shared `LocConverter` and comment hook), so `pos` / `onComment` are
  2120. * ignored. A raw source string is tokenized from `pos` with a fresh
  2121. * `LocConverter`; pass a `TokenStream` instead to share one converter across
  2122. * sub-parses.
  2123. * @param {string | TokenStream} input source string or an existing stream
  2124. * @param {number=} pos start byte offset (string input only; default `0`)
  2125. * @param {((input: string, start: number, end: number) => number)=} onComment comment callback (string input only)
  2126. * @returns {TokenStream} the stream to consume from
  2127. */
  2128. const normalizeIntoTokenStream = (input, pos, onComment) =>
  2129. input instanceof TokenStream
  2130. ? input
  2131. : new TokenStream(input, pos || 0, new LocConverter(input), onComment);
  2132. // === Parser entry points (CSS Syntax Level 3 §5.3) ===
  2133. // Each `parseA*` is a thin public wrapper over a `consumeA*` algorithm
  2134. // (§5.4): it takes raw source + a start position (webpack's stand-in for
  2135. // the spec's "normalize into a token stream") and runs the matching
  2136. // consume algorithm. The split mirrors tabatkins/parse-css — `parse*`
  2137. // are the documented entry points, `consume*` are the internal
  2138. // algorithms that drive the tokenizer.
  2139. /**
  2140. * @typedef {object} ParseOptions
  2141. * @property {((input: string, start: number, end: number) => number)=} comment optional comment-token callback; the public `parse*` entry points use it to build the `TokenStream` so the outer parser's comment tracker still sees magic comments inside the consumed range
  2142. */
  2143. // === parseA* retained store + property-accessor readers ===
  2144. // `grammar` recycles the columns per top-level rule, but the `parseA*` entry
  2145. // points must hand back a tree that outlives the parse. Each `parseA*` run parses
  2146. // into the columns without recycling, then `_finishStore` hands them to a
  2147. // snapshot object and resets the module columns to fresh arrays so the next parse
  2148. // can't clobber it. Nodes are exposed as `parseA*` readers: plain objects sharing
  2149. // one module-level prototype whose getters index the reader's own snapshot by node
  2150. // id — no per-node class, no eager string slices, child readers built lazily on
  2151. // access. A single shared prototype (rather than one per store) keeps the readers
  2152. // monomorphic across parses, so `_readerAt` and every getter stay on the fast path.
  2153. /**
  2154. * @typedef {object} NodeReader
  2155. * @property {NodeStore} _store snapshot this reader indexes
  2156. * @property {number} _i node id into the snapshot columns
  2157. * @property {number} type node type
  2158. * @property {number} start start offset
  2159. * @property {number} end end offset
  2160. * @property {[number, number]} range start / end offsets
  2161. * @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location
  2162. * @property {() => string} toString source slice
  2163. * @property {string | ComponentValue[]} value token value (leaf) or component-value list (function / block / declaration)
  2164. * @property {string} unescaped unescaped token value
  2165. * @property {number} numericValue parsed numeric value
  2166. * @property {"integer" | "number" | "id" | "unrestricted"} typeFlag spec type flag
  2167. * @property {"+" | "-" | ""} sign spec sign
  2168. * @property {string} unit dimension unit (lower-cased)
  2169. * @property {number} contentStart url content start offset
  2170. * @property {number} contentEnd url content end offset
  2171. * @property {string} name rule / declaration / function name
  2172. * @property {number} nameStart name start offset
  2173. * @property {number} nameEnd name end offset
  2174. * @property {string} unescapedName unescaped name
  2175. * @property {ComponentValue[]} prelude rule prelude
  2176. * @property {Declaration[] | null} declarations block declarations
  2177. * @property {Rule[] | null} childRules block child rules
  2178. * @property {number} blockStart `{` start offset
  2179. * @property {number} blockEnd `}` end offset
  2180. * @property {boolean} important `!important` flag
  2181. * @property {SimpleBlockToken} token simple-block opening char
  2182. * @property {Rule[]} rules stylesheet rules
  2183. */
  2184. /**
  2185. * @typedef {object} NodeStore
  2186. * @property {string} input source
  2187. * @property {LocConverter} lc loc converter
  2188. * @property {Uint8Array} types node-type column
  2189. * @property {Int32Array} starts start-offset column
  2190. * @property {Int32Array} ends end-offset column
  2191. * @property {Int32Array} aux0 aux slot 0
  2192. * @property {Int32Array} aux1 aux slot 1
  2193. * @property {Uint8Array} flags flags column
  2194. * @property {Int32Array} listStarts content-span start column
  2195. * @property {Int32Array} listLens content-span length column
  2196. * @property {Int32Array} flat flat node-ref buffer
  2197. * @property {Int32Array} bodyIdx per-node `1 + body index` (0 = no body)
  2198. * @property {Node[][]} declBodies dense per-body declaration lists
  2199. * @property {Node[][]} ruleBodies dense per-body child-rule lists
  2200. */
  2201. // Shared frozen empty list for a block body with no declarations / child rules,
  2202. // so equal-empty reads return one reference (mirrors `_EMPTY_LIST`).
  2203. const _READER_EMPTY = /** @type {Rule[]} */ (
  2204. /** @type {unknown} */ (Object.freeze([]))
  2205. );
  2206. /**
  2207. * Raw token value over a snapshot (the lazy `Token.value` form): hash / at-keyword
  2208. * drop their one-char prefix, url uses its content range.
  2209. * @param {NodeStore} store snapshot
  2210. * @param {number} i node id
  2211. * @returns {string} raw token value
  2212. */
  2213. const _storeValueOf = (store, i) => {
  2214. const ty = store.types[i];
  2215. if (ty === T_HASH || ty === T_AT_KEYWORD) {
  2216. return store.input.slice(store.starts[i] + 1, store.ends[i]);
  2217. }
  2218. if (ty === T_URL) return store.input.slice(store.aux0[i], store.aux1[i]);
  2219. return store.input.slice(store.starts[i], store.ends[i]);
  2220. };
  2221. /**
  2222. * @param {NodeStore} store snapshot
  2223. * @param {number} i node id
  2224. * @returns {Node} reader over node `i`
  2225. */
  2226. const _readerAt = (store, i) => {
  2227. const o = Object.create(NODE_READER_PROTO);
  2228. o._store = store;
  2229. o._i = i;
  2230. return /** @type {Node} */ (o);
  2231. };
  2232. /**
  2233. * Readers over a container's flat content span (value / prelude / rules).
  2234. * @param {NodeStore} store snapshot
  2235. * @param {number} i container id
  2236. * @returns {Node[]} child readers
  2237. */
  2238. const _readList = (store, i) => {
  2239. const s = store.listStarts[i];
  2240. const len = store.listLens[i];
  2241. const flat = store.flat;
  2242. /** @type {Node[]} */
  2243. const out = [];
  2244. for (let k = 0; k < len; k++) out.push(_readerAt(store, flat[s + k]));
  2245. return out;
  2246. };
  2247. /**
  2248. * Readers over a node-id list (declarations / child rules).
  2249. * @param {NodeStore} store snapshot
  2250. * @param {Node[]} list node-id list
  2251. * @returns {Node[]} child readers
  2252. */
  2253. const _readRefList = (store, list) => {
  2254. /** @type {Node[]} */
  2255. const out = [];
  2256. for (let k = 0; k < list.length; k++) {
  2257. out.push(_readerAt(store, _nodeIndex(list[k])));
  2258. }
  2259. return out;
  2260. };
  2261. // Module-level reader prototype shared by every `parseA*` reader. Getters index
  2262. // the reader's own `_store` snapshot by its `_i` node id; because the prototype is
  2263. // created once (not per store), all readers share one hidden map and stay
  2264. // monomorphic across parses.
  2265. const NODE_READER_PROTO = /** @type {NodeReader} */ ({
  2266. _store: /** @type {NodeStore} */ (/** @type {unknown} */ (null)),
  2267. _i: 0,
  2268. get type() {
  2269. return this._store.types[this._i];
  2270. },
  2271. get start() {
  2272. return this._store.starts[this._i];
  2273. },
  2274. get end() {
  2275. return this._store.ends[this._i];
  2276. },
  2277. get range() {
  2278. const store = this._store;
  2279. const i = this._i;
  2280. return /** @type {[number, number]} */ ([store.starts[i], store.ends[i]]);
  2281. },
  2282. get loc() {
  2283. const store = this._store;
  2284. const i = this._i;
  2285. const lc = store.lc;
  2286. // `LocConverter#get` mutates and returns itself, so snapshot the first.
  2287. const s = lc.get(store.starts[i]);
  2288. const sl = s.line;
  2289. const sc = s.column;
  2290. const e = lc.get(store.ends[i]);
  2291. return {
  2292. start: { line: sl, column: sc },
  2293. end: { line: e.line, column: e.column }
  2294. };
  2295. },
  2296. toString() {
  2297. const store = this._store;
  2298. const i = this._i;
  2299. return store.input.slice(store.starts[i], store.ends[i]);
  2300. },
  2301. get value() {
  2302. const store = this._store;
  2303. const i = this._i;
  2304. const ty = store.types[i];
  2305. // function / simple-block / declaration expose their component-value
  2306. // list; every leaf token exposes its raw string value.
  2307. return ty === T_FUNCTION || ty === T_SIMPLE_BLOCK || ty === T_DECLARATION
  2308. ? /** @type {ComponentValue[]} */ (_readList(store, i))
  2309. : _storeValueOf(store, i);
  2310. },
  2311. get unescaped() {
  2312. const store = this._store;
  2313. const i = this._i;
  2314. const v = _storeValueOf(store, i);
  2315. return store.types[i] === T_STRING
  2316. ? unescapeIdentifier(v.slice(1, -1))
  2317. : unescapeIdentifier(v);
  2318. },
  2319. get numericValue() {
  2320. const store = this._store;
  2321. const i = this._i;
  2322. const v = _storeValueOf(store, i);
  2323. if (store.types[i] === T_DIMENSION) {
  2324. return Number(v.slice(0, _consumeANumber(v, 0)));
  2325. }
  2326. if (store.types[i] === T_PERCENTAGE) return Number(v.slice(0, -1));
  2327. return Number(v);
  2328. },
  2329. get typeFlag() {
  2330. const store = this._store;
  2331. const i = this._i;
  2332. if (store.types[i] === T_HASH) {
  2333. const input = store.input;
  2334. const p = store.starts[i] + 1;
  2335. return _ifThreeCodePointsWouldStartAnIdentSequence(
  2336. input,
  2337. p,
  2338. input.charCodeAt(p),
  2339. input.charCodeAt(p + 1),
  2340. input.charCodeAt(p + 2)
  2341. )
  2342. ? "id"
  2343. : "unrestricted";
  2344. }
  2345. const v = _storeValueOf(store, i);
  2346. return _typeFlagOf(
  2347. store.types[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
  2348. );
  2349. },
  2350. get sign() {
  2351. return _signOf(_storeValueOf(this._store, this._i));
  2352. },
  2353. get unit() {
  2354. const v = _storeValueOf(this._store, this._i);
  2355. return v.slice(_consumeANumber(v, 0)).toLowerCase();
  2356. },
  2357. get contentStart() {
  2358. return this._store.aux0[this._i];
  2359. },
  2360. get contentEnd() {
  2361. return this._store.aux1[this._i];
  2362. },
  2363. get name() {
  2364. const store = this._store;
  2365. const i = this._i;
  2366. // An at-rule's name skips its `@`; others start at the node.
  2367. return store.types[i] === T_AT_RULE
  2368. ? store.input.slice(store.starts[i] + 1, store.aux0[i])
  2369. : store.input.slice(store.starts[i], store.aux0[i]);
  2370. },
  2371. get nameStart() {
  2372. return this._store.starts[this._i];
  2373. },
  2374. get nameEnd() {
  2375. return this._store.aux0[this._i];
  2376. },
  2377. get unescapedName() {
  2378. return unescapeIdentifier(this.name);
  2379. },
  2380. get prelude() {
  2381. return /** @type {ComponentValue[]} */ (_readList(this._store, this._i));
  2382. },
  2383. get declarations() {
  2384. const store = this._store;
  2385. // Only rules populate the body slot (see `_makeContainer`); 0 means no
  2386. // block — same `null` contract as before.
  2387. const bi = store.bodyIdx[this._i];
  2388. if (bi === 0) return null;
  2389. const list = store.declBodies[bi - 1];
  2390. return /** @type {Declaration[]} */ (
  2391. list.length > 0 ? _readRefList(store, list) : _READER_EMPTY
  2392. );
  2393. },
  2394. get childRules() {
  2395. const store = this._store;
  2396. const bi = store.bodyIdx[this._i];
  2397. if (bi === 0) return null;
  2398. const list = store.ruleBodies[bi - 1];
  2399. return /** @type {Rule[]} */ (
  2400. list.length > 0 ? _readRefList(store, list) : _READER_EMPTY
  2401. );
  2402. },
  2403. get blockStart() {
  2404. return this._store.aux1[this._i];
  2405. },
  2406. get blockEnd() {
  2407. const store = this._store;
  2408. const i = this._i;
  2409. return store.aux1[i] !== -1 ? store.ends[i] : -1;
  2410. },
  2411. get important() {
  2412. return (this._store.flags[this._i] & 1) !== 0;
  2413. },
  2414. get token() {
  2415. const store = this._store;
  2416. return /** @type {SimpleBlockToken} */ (store.input[store.starts[this._i]]);
  2417. },
  2418. get rules() {
  2419. return /** @type {Rule[]} */ (_readList(this._store, this._i));
  2420. }
  2421. });
  2422. /**
  2423. * Hand the node columns back: each is replaced by an empty view, so what this
  2424. * parse grew is collectable and every column goes together — one place to add
  2425. * a new one to, rather than two that have to agree.
  2426. * @returns {void}
  2427. */
  2428. const _releaseColumns = () => {
  2429. _types = new Uint8Array(0);
  2430. _starts = new Int32Array(0);
  2431. _ends = new Int32Array(0);
  2432. _aux0 = new Int32Array(0);
  2433. _aux1 = new Int32Array(0);
  2434. _flags = new Uint8Array(0);
  2435. _listStarts = new Int32Array(0);
  2436. _listLens = new Int32Array(0);
  2437. _bodyIdx = new Int32Array(0);
  2438. };
  2439. /**
  2440. * Hand the module's live columns to a retained snapshot, then reset the
  2441. * module columns to fresh empty arrays so the next parse starts clean and can't
  2442. * mutate this store. Called once per `parseA*` after all consuming is done.
  2443. * @returns {NodeStore} the retained snapshot
  2444. */
  2445. const _finishStore = () => {
  2446. /** @type {NodeStore} */
  2447. const store = {
  2448. input: _input,
  2449. lc: _locConverter,
  2450. types: _types,
  2451. starts: _starts,
  2452. ends: _ends,
  2453. aux0: _aux0,
  2454. aux1: _aux1,
  2455. flags: _flags,
  2456. listStarts: _listStarts,
  2457. listLens: _listLens,
  2458. flat: _flat,
  2459. bodyIdx: _bodyIdx,
  2460. declBodies: _declBodies,
  2461. ruleBodies: _ruleBodies
  2462. };
  2463. // Carry this parse's size forward as a one-shot grow hint so the next parse
  2464. // allocates its columns once instead of re-doubling from scratch (node ids
  2465. // are 1-based, so `+1`).
  2466. _growHint = _nodeCount + 1;
  2467. _flatGrowHint = _flatTop;
  2468. // Reset module state — the columns now belong to `store`.
  2469. _capacity = 0;
  2470. _nodeCount = 0;
  2471. _flatTop = 0;
  2472. _releaseColumns();
  2473. _flat = new Int32Array(0);
  2474. _declBodies = [];
  2475. _ruleBodies = [];
  2476. _listPool.length = 0;
  2477. _input = "";
  2478. _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
  2479. return store;
  2480. };
  2481. /**
  2482. * Finish the parse and wrap one consumed node ref as a `parseA*` reader, keeping
  2483. * the caller's node type.
  2484. * @template {Node} T
  2485. * @param {T} ref consumed node ref
  2486. * @returns {T} reader over the retained snapshot
  2487. */
  2488. const _finishOne = (ref) =>
  2489. /** @type {T} */ (_readerAt(_finishStore(), _nodeIndex(ref)));
  2490. /**
  2491. * Parse a stylesheet, CSS Syntax Level 3
  2492. * [§5.3.4](https://drafts.csswg.org/css-syntax/#parse-stylesheet).
  2493. * @param {string | TokenStream} input source string or an existing token stream
  2494. * @param {number=} pos start position (string input only)
  2495. * @param {ParseOptions=} options optional comment-token callback (string input only)
  2496. * @returns {Stylesheet} the parsed stylesheet
  2497. */
  2498. const parseAStylesheet = (input, pos = 0, options = {}) => {
  2499. // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
  2500. // 2. Normalize input, and set input to the result.
  2501. const ts = normalizeIntoTokenStream(input, pos, options.comment);
  2502. _setupParse(ts.input, ts.locConverter);
  2503. // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
  2504. const start = ts.next().start;
  2505. const stylesheet = _makeStylesheet(start);
  2506. // 4. Consume a stylesheet's contents from input, and set the stylesheet's rules to the result.
  2507. _setRules(stylesheet, consumeAStylesheetsContents(ts));
  2508. _setEnd(stylesheet, ts.next().start);
  2509. // 5. Return the stylesheet.
  2510. return /** @type {Stylesheet} */ (
  2511. _readerAt(_finishStore(), _nodeIndex(stylesheet))
  2512. );
  2513. };
  2514. /**
  2515. * Parse a stylesheet's contents, CSS Syntax Level 3
  2516. * [§5.3.5](https://drafts.csswg.org/css-syntax/#parse-stylesheets-contents) —
  2517. * the top-level rule list via `consumeAStylesheetsContents` (§5.4.1): top-level
  2518. * declarations are parse errors (never produced) and top-level CDO (`<!--`) /
  2519. * CDC (`-->`) tokens are discarded.
  2520. * @param {string | TokenStream} input source string or an existing token stream
  2521. * @param {number=} pos start position (string input only)
  2522. * @param {ParseOptions=} options optional comment-token callback (string input only)
  2523. * @returns {Rule[]} top-level rules
  2524. */
  2525. const parseAStylesheetsContents = (input, pos = 0, options = {}) => {
  2526. // 1. Normalize input, and set input to the result.
  2527. const ts = normalizeIntoTokenStream(input, pos, options.comment);
  2528. _setupParse(ts.input, ts.locConverter);
  2529. // 2. Consume a stylesheet’s contents from input, and return the result.
  2530. const rules = consumeAStylesheetsContents(ts);
  2531. const store = _finishStore();
  2532. return /** @type {Rule[]} */ (_readRefList(store, rules));
  2533. };
  2534. /**
  2535. * Parse a block's contents, CSS Syntax Level 3
  2536. * [§5.3.6](https://drafts.csswg.org/css-syntax/#parse-block-contents).
  2537. * @param {string | TokenStream} input source string or an existing token stream
  2538. * @param {number=} pos start position (string input only; just past the opening `{`, or 0)
  2539. * @param {ParseOptions=} options optional comment-token callback (string input only)
  2540. * @returns {{ decls: Declaration[], rules: Rule[] }} block decls + rules
  2541. */
  2542. const parseABlocksContents = (input, pos = 0, options = {}) => {
  2543. // 1. Normalize input, and set input to the result.
  2544. const ts = normalizeIntoTokenStream(input, pos, options.comment);
  2545. _setupParse(ts.input, ts.locConverter);
  2546. // 2. Consume a block’s contents from input, and return the result.
  2547. const { decls, rules } = consumeABlocksContents(ts);
  2548. const store = _finishStore();
  2549. return {
  2550. decls: /** @type {Declaration[]} */ (_readRefList(store, decls)),
  2551. rules:
  2552. rules.length > 0
  2553. ? /** @type {Rule[]} */ (_readRefList(store, rules))
  2554. : _READER_EMPTY
  2555. };
  2556. };
  2557. /**
  2558. * Parse a rule, CSS Syntax Level 3
  2559. * [§5.3.7](https://drafts.csswg.org/css-syntax/#parse-rule) — discards leading
  2560. * whitespace, consumes one at-rule / qualified rule, and requires only trailing
  2561. * whitespace; `undefined` (syntax error) otherwise.
  2562. * @param {string | TokenStream} input source string or an existing token stream
  2563. * @param {number=} pos start position (string input only)
  2564. * @param {ParseOptions=} options optional comment-token callback (string input only)
  2565. * @returns {Rule | undefined} the parsed rule
  2566. */
  2567. const parseARule = (input, pos = 0, options = {}) => {
  2568. // 1. Normalize input, and set input to the result.
  2569. const ts = normalizeIntoTokenStream(input, pos, options.comment);
  2570. _setupParse(ts.input, ts.locConverter);
  2571. // 2. Discard whitespace from input.
  2572. while (ts.next().type === TT_WHITESPACE) ts.advance();
  2573. // 3. If the next token from input is an <EOF-token>, return a syntax error.
  2574. // Otherwise, if the next token from input is an <at-keyword-token>, consume an at-rule from input, and let rule be the return value.
  2575. // Otherwise, consume a qualified rule from input and let rule be the return value.
  2576. // If nothing or an invalid rule error was returned, return a syntax error.
  2577. const head = ts.next();
  2578. if (head.type === TT_EOF) return undefined;
  2579. const rule =
  2580. head.type === TT_AT_KEYWORD
  2581. ? consumeAnAtRule(ts)
  2582. : consumeAQualifiedRule(ts);
  2583. if (!rule) return undefined;
  2584. // 4. Discard whitespace from input.
  2585. while (ts.next().type === TT_WHITESPACE) ts.advance();
  2586. // 5. If the next token from input is an <EOF-token>, return rule. Otherwise, return a syntax error.
  2587. return ts.next().type === TT_EOF ? _finishOne(rule) : undefined;
  2588. };
  2589. /**
  2590. * Parse a declaration, CSS Syntax Level 3
  2591. * [§5.3.8](https://drafts.csswg.org/css-syntax/#parse-declaration).
  2592. * @param {string | TokenStream} input source string or an existing token stream
  2593. * @param {number=} pos start position (string input only)
  2594. * @param {ParseOptions=} options optional comment-token callback (string input only)
  2595. * @returns {Declaration | undefined} the parsed declaration, or undefined
  2596. */
  2597. const parseADeclaration = (input, pos = 0, options = {}) => {
  2598. // 1. Normalize input, and set input to the result.
  2599. const ts = normalizeIntoTokenStream(input, pos, options.comment);
  2600. _setupParse(ts.input, ts.locConverter);
  2601. // 2. Discard whitespace from input.
  2602. while (ts.next().type === TT_WHITESPACE) ts.advance();
  2603. // 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
  2604. const decl = consumeADeclaration(ts);
  2605. return decl === undefined ? undefined : _finishOne(decl);
  2606. };
  2607. /**
  2608. * Parse a component value, CSS Syntax Level 3 [§5.3.9](https://drafts.csswg.org/css-syntax/#parse-component-value) — strict entry point that consumes one value and returns `undefined` if non-whitespace input trails (use `consumeAComponentValue` for "one value, ignore the rest").
  2609. * @param {string | TokenStream} input source string or an existing token stream
  2610. * @param {number=} pos start position (string input only)
  2611. * @param {ParseOptions=} options optional comment-token callback (string input only)
  2612. * @returns {ComponentValue | undefined} the parsed component value, or `undefined` on empty / trailing-garbage input
  2613. */
  2614. const parseAComponentValue = (input, pos = 0, options = {}) => {
  2615. // 1. Normalize input, and set input to the result.
  2616. const ts = normalizeIntoTokenStream(input, pos, options.comment);
  2617. _setupParse(ts.input, ts.locConverter);
  2618. // 2. Discard whitespace from input.
  2619. while (ts.next().type === TT_WHITESPACE) ts.advance();
  2620. // 3. If input is empty, return a syntax error.
  2621. if (ts.next().type === TT_EOF) return undefined;
  2622. // 4. Consume a component value from input and let value be the return value.
  2623. const result = consumeAComponentValue(ts);
  2624. // 5. Discard whitespace from input.
  2625. while (ts.next().type === TT_WHITESPACE) ts.advance();
  2626. // 6. If input is empty, return value. Otherwise, return a syntax error.
  2627. return ts.next().type === TT_EOF ? _finishOne(result) : undefined;
  2628. };
  2629. /**
  2630. * Parse a list of component values, CSS Syntax Level 3
  2631. * [§5.3.10](https://drafts.csswg.org/css-syntax/#parse-list-of-components).
  2632. * @param {string | TokenStream} input source string or an existing token stream
  2633. * @param {number=} pos start position (string input only)
  2634. * @param {ParseOptions=} options comment callback
  2635. * @returns {ComponentValue[]} component values
  2636. */
  2637. const parseAListOfComponentValues = (input, pos = 0, options = {}) => {
  2638. // 1. Normalize input, and set input to the result.
  2639. const ts = normalizeIntoTokenStream(input, pos, options.comment);
  2640. _setupParse(ts.input, ts.locConverter);
  2641. // 2. Consume a list of component values from input, and return the result.
  2642. // (`null` needs `bailOnCurly`, which is not passed here.)
  2643. const values = /** @type {Node[]} */ (consumeAListOfComponentValues(ts));
  2644. const store = _finishStore();
  2645. return /** @type {ComponentValue[]} */ (_readRefList(store, values));
  2646. };
  2647. /**
  2648. * Parse a comma-separated list of component values, CSS Syntax Level 3 [§5.3.11](https://drafts.csswg.org/css-syntax/#parse-comma-list) — consumes one `<comma-token>`-stopped group of component values per iteration until EOF.
  2649. * @param {string | TokenStream} input source string or an existing token stream
  2650. * @param {number=} pos start position (string input only)
  2651. * @param {ParseOptions=} options comment callback
  2652. * @returns {ComponentValue[][]} comma-separated groups of component values
  2653. */
  2654. const parseACommaSeparatedListOfComponentValues = (
  2655. input,
  2656. pos = 0,
  2657. options = {}
  2658. ) => {
  2659. // 1. Normalize input, and set input to the result.
  2660. const ts = normalizeIntoTokenStream(input, pos, options.comment);
  2661. _setupParse(ts.input, ts.locConverter);
  2662. // 2. Let groups be an empty list.
  2663. /** @type {Node[][]} */
  2664. const groups = [];
  2665. // 3. While input is not empty:
  2666. while (ts.next().type !== TT_EOF) {
  2667. // 3.1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
  2668. groups.push(
  2669. /** @type {Node[]} */ (consumeAListOfComponentValues(ts, TT_COMMA))
  2670. );
  2671. // 3.2 Discard a token from input.
  2672. ts.discard();
  2673. }
  2674. // 4. Return groups — wrap each group's refs against the retained snapshot.
  2675. const store = _finishStore();
  2676. return groups.map(
  2677. (g) => /** @type {ComponentValue[]} */ (_readRefList(store, g))
  2678. );
  2679. };
  2680. // === Parser algorithms (CSS Syntax Level 3 §5.4) ===
  2681. // The mutually-recursive consume algorithms the `parse*` entry points drive:
  2682. // each reads tokens from a `TokenStream` and reuses `consumeAComponentValue`
  2683. // for nested values, mirroring tabatkins/parse-css.
  2684. /**
  2685. * Consume a stylesheet's contents, CSS Syntax Level 3 [§5.4.1](https://drafts.csswg.org/css-syntax/#consume-stylesheet-contents) — the top-level rule list: whitespace and CDO (`<!--`) / CDC (`-->`) tokens are discarded, an at-keyword starts an at-rule, and anything else starts a qualified rule (so top-level declarations are parse errors and never produced).
  2686. *
  2687. * `onRule` is a webpack extension to the algorithm's output: when given, each
  2688. * consumed rule is handed to it immediately and not collected, so the walker can
  2689. * process one top-level rule at a time without materializing the whole
  2690. * stylesheet (the returned list is then empty). When omitted the rules are
  2691. * collected and returned as the spec specifies.
  2692. * @param {TokenStream} ts token stream
  2693. * @param {((rule: Rule) => void)=} onRule optional per-rule sink (streaming); rules are not collected when given
  2694. * @returns {Rule[]} top-level rules (empty when `onRule` is given)
  2695. */
  2696. const consumeAStylesheetsContents = (ts, onRule) => {
  2697. // Let rules be an initially empty list of rules.
  2698. /** @type {Rule[]} */
  2699. const rules = [];
  2700. // Process input
  2701. for (;;) {
  2702. const t = ts.next();
  2703. // <whitespace-token> / <CDO-token> / <CDC-token>
  2704. // Discard a token from input.
  2705. if (t.type === TT_WHITESPACE || t.type === TT_CDO || t.type === TT_CDC) {
  2706. ts.discard();
  2707. }
  2708. // <EOF-token>
  2709. // Return rules.
  2710. else if (t.type === TT_EOF) {
  2711. return rules;
  2712. }
  2713. // <at-keyword-token>
  2714. // Consume an at-rule from input. If anything is returned, append it to rules.
  2715. else if (t.type === TT_AT_KEYWORD) {
  2716. const at = consumeAnAtRule(ts);
  2717. if (at) {
  2718. if (onRule) onRule(at);
  2719. else rules.push(at);
  2720. }
  2721. }
  2722. // anything else
  2723. // Consume a qualified rule from input. If a rule is returned, append it to rules.
  2724. else {
  2725. const rule = consumeAQualifiedRule(ts);
  2726. if (rule) {
  2727. if (onRule) onRule(rule);
  2728. else rules.push(rule);
  2729. }
  2730. }
  2731. }
  2732. };
  2733. /**
  2734. * Consume an at-rule, CSS Syntax Level 3 [§5.4.2](https://drafts.csswg.org/css-syntax/#consume-at-rule) — the next token must be an <at-keyword-token> (asserted); consumes the prelude up to `;` / `{` / `}` / EOF; `{` consumes the block (§5.4.4) onto `.block`, `;` / EOF is discarded, a top-level `}` (when not `nested`) is appended via `consumeAComponentValue`.
  2735. * @param {TokenStream} ts token stream
  2736. * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the at-rule (left for the caller)
  2737. * @returns {AtRule | undefined} the parsed at-rule
  2738. */
  2739. const consumeAnAtRule = (ts, nested = false) => {
  2740. // Assert (spec): the next token is an <at-keyword-token>.
  2741. // Consume a token from input, and let rule be a new at-rule with its name set to the returned token’s value, its prelude initially set to an empty list, and no declarations or child rules.
  2742. const head = ts.consume();
  2743. const rule = /** @type {AtRule} */ (
  2744. _makeContainer(T_AT_RULE, head.start, head.end)
  2745. );
  2746. _setName(rule, head.start, head.end);
  2747. // Sealed (`_setPrelude`) at each return — the store consumes the
  2748. // scratch array when sealing, so it must be complete by then.
  2749. const prelude = _takeList();
  2750. // declarations / childRules stay null (no block); the `;` / EOF / nested-`}`
  2751. // forms set blockStart / blockEnd to -1 explicitly at their return below.
  2752. // Like `consumeAQualifiedRule`: skip mode scans the prelude without
  2753. // materializing it (url tokens / functions kept so `@import url(…)` still
  2754. // resolves); the block boundary is found by scanning, not the prelude nodes.
  2755. const skip = _skipAtRulePrelude;
  2756. // Process input
  2757. for (;;) {
  2758. const t = ts.next();
  2759. // <semicolon-token>
  2760. // <EOF-token>
  2761. // Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
  2762. if (t.type === TT_SEMICOLON || t.type === TT_EOF) {
  2763. ts.discard();
  2764. _setPrelude(rule, prelude);
  2765. _setBlock(rule, -1);
  2766. _setEnd(rule, t.start);
  2767. return rule;
  2768. }
  2769. // <}-token>
  2770. // If nested is true: if rule is valid in the current context, return it; otherwise return nothing.
  2771. // Otherwise, consume a token and append the result to rule’s prelude.
  2772. else if (t.type === TT_RIGHT_CURLY_BRACKET) {
  2773. if (nested) {
  2774. _setPrelude(rule, prelude);
  2775. _setBlock(rule, -1);
  2776. _setEnd(rule, t.start);
  2777. return rule;
  2778. }
  2779. const node = consumeATokenAsNode(ts);
  2780. if (!skip) prelude.push(node);
  2781. continue;
  2782. }
  2783. // <{-token>
  2784. // Consume a block from input, and assign the result to rule's declarations and child rules.
  2785. else if (t.type === TT_LEFT_CURLY_BRACKET) {
  2786. _setPrelude(rule, prelude);
  2787. if (_streamBlocks) {
  2788. _streamConsumeBlock(ts, rule);
  2789. return rule;
  2790. }
  2791. consumeABlock(ts);
  2792. _setBody(rule, _blockDecls, _blockRules);
  2793. _setBlock(rule, _blockStart);
  2794. _setEnd(rule, _blockEnd);
  2795. return rule;
  2796. }
  2797. // anything else
  2798. // Consume a component value from input and append the returned value to rule’s prelude.
  2799. const node = consumeAComponentValue(ts, t);
  2800. if (!skip) {
  2801. prelude.push(node);
  2802. } else if (
  2803. _nodeTypeOf(node) === T_FUNCTION ||
  2804. _nodeTypeOf(node) === T_URL
  2805. ) {
  2806. prelude.push(node);
  2807. }
  2808. }
  2809. };
  2810. /**
  2811. * Consume a token (CSS Syntax §3 "consume a token"): advance past the next
  2812. * token and return it as a leaf AST node. Used directly where the spec says
  2813. * "consume a token from input" (e.g. the parse-error branches in §5.4.7 /
  2814. * §5.4.2 / §5.4.3), distinct from `consumeAComponentValue` which would recurse
  2815. * into a simple block / function.
  2816. * @param {TokenStream} ts token stream
  2817. * @returns {Token} the consumed token as a leaf node
  2818. */
  2819. const consumeATokenAsNode = (ts) => {
  2820. const t = ts.consume();
  2821. return /** @type {Token} */ (tokenToNode(t));
  2822. };
  2823. /**
  2824. * Consume a qualified rule, CSS Syntax Level 3 [§5.4.3](https://drafts.csswg.org/css-syntax/#consume-qualified-rule) — consumes the prelude (each component value via `consumeAComponentValue`) up to its `{` block; EOF, the optional `stopToken`, or a nested top-level `}` is a parse error returning nothing (the block-less prelude is dropped), while a non-nested top-level `}` is consumed as a parse error and the prelude continues. A returned rule always has a block.
  2825. * @param {TokenStream} ts token stream
  2826. * @param {number=} stopToken token type that ends the prelude (parse error → nothing)
  2827. * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the rule (left for the caller)
  2828. * @returns {QualifiedRule | undefined} parsed qualified rule, or `undefined` on a parse error
  2829. */
  2830. const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
  2831. const start = ts.next().start;
  2832. // Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
  2833. const rule = /** @type {QualifiedRule} */ (
  2834. _makeContainer(T_QUALIFIED_RULE, start, start)
  2835. );
  2836. // Sealed (`_setPrelude`) at the `{` exit — the only path that returns the
  2837. // rule; the parse-error exits abandon the scratch unsealed. Allocated lazily
  2838. // on first push: skip mode usually pushes nothing (empty selector prelude),
  2839. // and `_makeContainer` already zeroed the rule's list length, so a null
  2840. // prelude reads as empty in the walk.
  2841. /** @type {Node[] | null} */
  2842. let prelude = null;
  2843. // A returned qualified rule always has a block (only the `{` exit returns a
  2844. // rule), so `_setBlock` always runs — no blockStart / blockEnd default needed.
  2845. // Skip mode leaves `prelude` empty (selector text is recovered from the
  2846. // rule's byte range, not its nodes); `first`/`second` still track the first
  2847. // two non-whitespace tokens the `--foo: {` disambiguation below needs.
  2848. const skip = _skipSelectorPrelude;
  2849. // Non-skip: first two non-whitespace prelude nodes (computed at the `{`).
  2850. let first = /** @type {Node} */ (/** @type {unknown} */ (0));
  2851. let second = /** @type {Node} */ (/** @type {unknown} */ (0));
  2852. // Skip mode: the same two tokens tracked by token type + start, so a dropped
  2853. // leaf selector token needs no materialized node just for the disambiguation.
  2854. // `0` (no token type) = unset.
  2855. let firstTT = 0;
  2856. let firstStart = 0;
  2857. let secondTT = 0;
  2858. // Process input
  2859. for (;;) {
  2860. const t = ts.next();
  2861. // <EOF-token>
  2862. // stop token (if passed)
  2863. // This is a parse error. Return nothing.
  2864. if (t.type === TT_EOF || t.type === stopToken) {
  2865. return undefined;
  2866. }
  2867. // <}-token>
  2868. // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
  2869. else if (t.type === TT_RIGHT_CURLY_BRACKET) {
  2870. if (nested) return undefined;
  2871. if (skip) {
  2872. // Stray `}` is a non-ws token; record its type/start, drop the node.
  2873. if (firstTT === 0) {
  2874. firstTT = t.type;
  2875. firstStart = t.start;
  2876. } else if (secondTT === 0) {
  2877. secondTT = t.type;
  2878. }
  2879. ts.advance();
  2880. } else {
  2881. (prelude || (prelude = _takeList())).push(consumeATokenAsNode(ts));
  2882. }
  2883. continue;
  2884. }
  2885. // <{-token>
  2886. // If the first two non-<whitespace-token> values of rule's prelude are an <ident-token> whose value starts with "--" followed by a <colon-token>, then:
  2887. // - If nested is true, consume the remnants of a bad declaration from input, with nested set to true, and return nothing.
  2888. // - If nested is false, consume a block from input, and return nothing.
  2889. // (This disambiguates custom-property declarations from nested qualified rules — `--foo: { … }` at top level of a block is a declaration, not a rule.)
  2890. // Otherwise, consume a block from input, and let child rules be the result.
  2891. else if (t.type === TT_LEFT_CURLY_BRACKET) {
  2892. // `--foo: {` disambiguation: are the first two non-ws prelude tokens an
  2893. // ident starting with `--` followed by a colon? Skip mode reads the
  2894. // tracked token type/start; non-skip reads the materialized prelude.
  2895. let dashedDeclaration;
  2896. if (skip) {
  2897. dashedDeclaration =
  2898. firstTT === TT_IDENTIFIER &&
  2899. ts.input.startsWith("--", firstStart) &&
  2900. secondTT === TT_COLON;
  2901. } else if (prelude === null) {
  2902. dashedDeclaration = false;
  2903. } else {
  2904. let firstIdx = 0;
  2905. /* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
  2906. while (
  2907. firstIdx < prelude.length &&
  2908. _nodeTypeOf(prelude[firstIdx]) === T_WHITESPACE
  2909. ) {
  2910. firstIdx++;
  2911. }
  2912. let secondIdx = firstIdx + 1;
  2913. while (
  2914. secondIdx < prelude.length &&
  2915. _nodeTypeOf(prelude[secondIdx]) === T_WHITESPACE
  2916. ) {
  2917. secondIdx++;
  2918. }
  2919. first = prelude[firstIdx];
  2920. second = prelude[secondIdx];
  2921. dashedDeclaration =
  2922. first &&
  2923. _nodeTypeOf(first) === T_IDENT &&
  2924. // Test the source bytes directly — avoids forcing the lazy `value`
  2925. // slice just to check the `--` custom-property prefix.
  2926. ts.input.startsWith("--", _nodeStartOf(first)) &&
  2927. second &&
  2928. _nodeTypeOf(second) === T_COLON;
  2929. }
  2930. if (dashedDeclaration) {
  2931. /* istanbul ignore if -- @preserve: when nested, `declarationStartLikely` routes every `--x:` to consumeADeclaration (which accepts custom properties), so this fallthrough is unreachable */
  2932. if (nested) {
  2933. consumeTheRemnantsOfABadDeclaration(ts, true);
  2934. } else {
  2935. consumeABlock(ts);
  2936. }
  2937. return undefined;
  2938. }
  2939. if (prelude !== null) _setPrelude(rule, prelude);
  2940. if (_streamBlocks) {
  2941. _streamConsumeBlock(ts, rule);
  2942. return rule;
  2943. }
  2944. consumeABlock(ts);
  2945. _setBody(rule, _blockDecls, _blockRules);
  2946. _setBlock(rule, _blockStart);
  2947. _setEnd(rule, _blockEnd);
  2948. return rule;
  2949. }
  2950. // anything else
  2951. // Consume a component value from input and append the result to rule’s prelude.
  2952. if (skip) {
  2953. const tt = t.type;
  2954. // Track the first two non-whitespace tokens for the disambiguation above.
  2955. if (tt !== TT_WHITESPACE) {
  2956. if (firstTT === 0) {
  2957. firstTT = tt;
  2958. firstStart = t.start;
  2959. } else if (secondTT === 0) {
  2960. secondTT = tt;
  2961. }
  2962. }
  2963. // Only functions (which may hold a url like `:unknown(url(x))`) and the
  2964. // `(` / `[` blocks that must be balanced are materialized; the url
  2965. // visitor keeps url / function nodes. Every other selector leaf token has
  2966. // no non-modules consumer — drop it without building a node.
  2967. if (
  2968. tt === TT_FUNCTION ||
  2969. (tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET)
  2970. ) {
  2971. const node = consumeAComponentValue(ts, t);
  2972. const ty = _nodeTypeOf(node);
  2973. if (ty === T_FUNCTION || ty === T_URL) {
  2974. (prelude || (prelude = _takeList())).push(node);
  2975. }
  2976. } else {
  2977. ts.advance();
  2978. }
  2979. } else {
  2980. (prelude || (prelude = _takeList())).push(consumeAComponentValue(ts, t));
  2981. }
  2982. }
  2983. };
  2984. /**
  2985. * Consume a block, CSS Syntax Level 3 [§5.4.4](https://drafts.csswg.org/css-syntax/#consume-block) — the next token must be `<{-token>`; discards it, consumes the block's contents (§5.4.5), discards the closing `}`, and returns its `decls` / `rules` pair. We also return the `[start of {, end of }]` offsets so callers can record the block's source position.
  2986. *
  2987. * Results go into the `_block*` slots rather than a returned object: there is one
  2988. * block per rule, and every caller reads all four immediately, so the object was
  2989. * pure garbage. Read them before parsing anything else.
  2990. * @param {TokenStream} ts token stream
  2991. * @returns {void}
  2992. */
  2993. const consumeABlock = (ts) => {
  2994. // Capture the opening `{`'s start before advancing — the stream reuses one
  2995. // token instance, so `consumeABlocksContents` below would overwrite it.
  2996. const blockStart = ts.next().start;
  2997. // Assert (spec): the next token is <{-token>.
  2998. // Discard a token from input. Consume a block's contents from input and let result be the result. Discard a token from input.
  2999. ts.discard();
  3000. consumeABlocksContentsInto(ts);
  3001. // Read before anything else runs: a nested block would overwrite these.
  3002. const decls = _bcDecls;
  3003. const rules = _bcRules;
  3004. const close = ts.next();
  3005. const end = close.type === TT_RIGHT_CURLY_BRACKET ? close.end : close.start;
  3006. ts.discard();
  3007. _blockDecls = decls;
  3008. _blockRules = rules;
  3009. _blockStart = blockStart;
  3010. _blockEnd = end;
  3011. };
  3012. /**
  3013. * 2-token lookahead: is the next non-whitespace pair `<ident> <colon>`?
  3014. * Peeks raw code points without advancing; comments still fire `onComment` later.
  3015. * @param {TokenStream} ts token stream
  3016. * @returns {boolean} true if consume-a-declaration's step 1 + step 3 would both succeed on the current input
  3017. */
  3018. const declarationStartLikely = (ts) => {
  3019. const t = ts.next();
  3020. if (t.type !== TT_IDENTIFIER) return false;
  3021. const input = ts.input;
  3022. const len = input.length;
  3023. let pos = t.end;
  3024. for (;;) {
  3025. if (pos >= len) return false;
  3026. const cc = input.charCodeAt(pos);
  3027. if (_isWhiteSpace(cc)) {
  3028. pos++;
  3029. continue;
  3030. }
  3031. // Skip a `/* … */` comment (the tokenizer filters comments between tokens).
  3032. if (cc === CC_SOLIDUS && input.charCodeAt(pos + 1) === CC_ASTERISK) {
  3033. pos += 2;
  3034. while (
  3035. pos < len &&
  3036. !(
  3037. input.charCodeAt(pos) === CC_ASTERISK &&
  3038. input.charCodeAt(pos + 1) === CC_SOLIDUS
  3039. )
  3040. ) {
  3041. pos++;
  3042. }
  3043. pos += 2;
  3044. continue;
  3045. }
  3046. // `:` is always a standalone <colon-token>, so the next significant char
  3047. // being `:` is equivalent to the next token being a <colon-token>.
  3048. return cc === CC_COLON;
  3049. }
  3050. };
  3051. /**
  3052. * Consume a block's contents, CSS Syntax Level 3 [§5.4.5](https://drafts.csswg.org/css-syntax/#consume-block-contents). Per tabatkins/parse-css.js reference impl: returns separate `decls` and `rules` flat lists, both preserved on EOF / `}` (the spec text's "Return rules" single-list model drops trailing decls because there's no implicit flush before EOF / `}`).
  3053. *
  3054. * `onNode` is the same streaming extension `consumeAStylesheetsContents` exposes:
  3055. * when given, each consumed declaration / rule is handed to it immediately (in
  3056. * source order) instead of being collected, so the returned lists are empty.
  3057. *
  3058. * `streamDepth` / `streamRule` are the other half of block streaming (see
  3059. * `_streamConsumeBlock`): the block collects into its own arrays exactly as it
  3060. * always has, and only once it has grown past `_STREAM_MIN_NODES` does it
  3061. * activate and switch to the sink. Keeping that check here rather than behind
  3062. * `onNode` is what makes a small block cost the same as it does with nothing
  3063. * streaming — no call, no type test per child — and the block's frame is written
  3064. * only when something could still read it (see `_streamPublishFrame`), so a block
  3065. * of nothing but declarations never touches one.
  3066. * @param {TokenStream} ts token stream
  3067. * @param {((node: Declaration | Rule) => void)=} onNode optional per-node sink (streaming); nodes are not collected when given
  3068. * @param {number=} streamDepth this block's frame depth while the walk streams, else undefined
  3069. * @param {Rule=} streamRule the rule this block belongs to, needed only to stream it
  3070. * @param {boolean=} nested whether a `{` opened this — true inside a block, where a `}` closes it; false for the whole input, where one closes nothing (default true)
  3071. * Results go into `_bcDecls` / `_bcRules` rather than a returned object — one
  3072. * block's contents per rule made that object pure garbage. `consumeABlocksContents`
  3073. * below wraps this for the callers that do want an object.
  3074. * @returns {void}
  3075. */
  3076. const consumeABlocksContentsInto = (
  3077. ts,
  3078. onNode,
  3079. streamDepth,
  3080. streamRule,
  3081. nested = true
  3082. ) => {
  3083. /** @type {Declaration[]} */
  3084. const decls = [];
  3085. // Child rules are the common empty case (most rules carry only declarations),
  3086. // so `rules` is allocated lazily and returned as the shared frozen
  3087. // `_EMPTY_LIST` when nothing was appended — one fewer array per rule. `decls`
  3088. // stays eager so the hot declaration append keeps a branch-free `push`.
  3089. /** @type {Rule[] | null} */
  3090. let rules = null;
  3091. let sink = onNode;
  3092. // -1 = nothing to stream, and then the growth check below can never fire.
  3093. const d = streamDepth === undefined ? -1 : streamDepth;
  3094. let limit = _STREAM_NO_LIMIT;
  3095. // Where this block began, kept in locals until something needs the frame.
  3096. let markNode = 0;
  3097. let markFlat = 0;
  3098. let published = false;
  3099. if (d >= 0) {
  3100. markNode = _nodeCount;
  3101. markFlat = _flatTop;
  3102. limit = markNode + _STREAM_MIN_NODES;
  3103. _bcStreamed = false;
  3104. }
  3105. // Process input:
  3106. for (;;) {
  3107. const t = ts.next();
  3108. // <whitespace-token> / <semicolon-token>
  3109. // Discard a token from input (`t` was just peeked and is non-EOF).
  3110. if (t.type === TT_WHITESPACE || t.type === TT_SEMICOLON) {
  3111. ts.advance();
  3112. continue;
  3113. }
  3114. // <}-token>
  3115. // Return decls and rules — but only nested, where it closes the block. At
  3116. // top level nothing opened one, so it is a parse error whose bad
  3117. // declaration runs to the next `;`, which is what a browser reads a
  3118. // `style=""` holding one as.
  3119. if (t.type === TT_RIGHT_CURLY_BRACKET && !nested) {
  3120. consumeTheRemnantsOfABadDeclaration(ts, false);
  3121. continue;
  3122. }
  3123. // <EOF-token> / <}-token>
  3124. // Return decls and rules.
  3125. if (t.type === TT_EOF || t.type === TT_RIGHT_CURLY_BRACKET) {
  3126. _bcDecls = decls;
  3127. _bcRules = rules || _EMPTY_LIST;
  3128. // Read from the frame, not from `sink`: a descendant can activate this
  3129. // block during a child rule that then fails to parse.
  3130. if (d >= 0) _bcStreamed = published && _frameActive[d] === 1;
  3131. return;
  3132. }
  3133. /** @type {Rule | undefined} */
  3134. let childRule;
  3135. // <at-keyword-token>
  3136. // Consume an at-rule from input, with nested set to true. If a rule was returned, append it to rules.
  3137. if (t.type === TT_AT_KEYWORD) {
  3138. if (d >= 0 && !published && sink === undefined) {
  3139. published = true;
  3140. _streamPublishFrame(d, streamRule, markNode, markFlat, decls, rules);
  3141. }
  3142. childRule = consumeAnAtRule(ts, nested);
  3143. }
  3144. // anything else
  3145. // Mark input. Consume a declaration from input, with nested set to true.
  3146. // If a declaration was returned, append it to decls, and discard a mark from input.
  3147. // Otherwise, restore a mark from input, then consume a qualified rule from input, with nested set to true, and <semicolon-token> as the stop token. If a rule was returned, append it to rules.
  3148. else {
  3149. // 2-token peek: consume-a-declaration's steps 1 / 3 require `<ident> <colon>`; if absent it would call consume-the-remnants-of-a-bad-declaration (potentially the rest of the enclosing block) only for the restoreMark to undo it (O(N²) on flat blocks of qualified rules). Skip straight to consume-a-qualified-rule — same observable result.
  3150. if (declarationStartLikely(ts)) {
  3151. ts.mark();
  3152. const decl = consumeADeclaration(ts, nested);
  3153. if (decl) {
  3154. ts.discardMark();
  3155. // A declaration opens no block, so nothing can have activated this
  3156. // one behind our back — only its own growth has to be checked.
  3157. if (sink) {
  3158. sink(decl);
  3159. } else {
  3160. decls.push(decl);
  3161. if (_nodeCount > limit) {
  3162. if (_streamWriter !== undefined && rules === null) {
  3163. // The longhand merge needs every declaration at once; a child
  3164. // rule is where that is given up rather than hold the block.
  3165. limit = _nodeCount + _STREAM_MIN_NODES;
  3166. } else {
  3167. if (!published) {
  3168. published = true;
  3169. // Grew past the threshold on declarations alone, so the
  3170. // frame has to be written here instead.
  3171. _streamPublishFrame(
  3172. d,
  3173. streamRule,
  3174. markNode,
  3175. markFlat,
  3176. decls,
  3177. rules
  3178. );
  3179. }
  3180. _streamActivate(d);
  3181. sink = _streamOnNode;
  3182. limit = _STREAM_NO_LIMIT;
  3183. }
  3184. }
  3185. }
  3186. continue;
  3187. }
  3188. ts.restoreMark();
  3189. }
  3190. // A child rule opens a block, and a block is the only thing that can reach
  3191. // back up and activate this one — so this is the last moment the frame has
  3192. // to be readable, and a block of nothing but declarations never gets here.
  3193. if (d >= 0 && !published && sink === undefined) {
  3194. published = true;
  3195. _streamPublishFrame(d, streamRule, markNode, markFlat, decls, rules);
  3196. }
  3197. const rawStart = t.start;
  3198. childRule = consumeAQualifiedRule(ts, TT_SEMICOLON, true);
  3199. if (!childRule && _printing) {
  3200. // Workaround, deliberately off-spec: §5.4 drops input both productions
  3201. // reject, which would silently delete IE hacks and template
  3202. // placeholders from minified output. Keep the source verbatim instead.
  3203. // Printing only, so the spec-exact tree is what every consumer sees.
  3204. childRule = /** @type {Rule | undefined} */ (
  3205. _makeRaw(rawStart, ts.next().start)
  3206. );
  3207. }
  3208. }
  3209. // A child rule holds a block, so a descendant of it may have activated this
  3210. // block while it was being consumed — `decls` / `rules` are then already
  3211. // walked and recycled, and everything from here on belongs to the sink.
  3212. // Checked even when the rule did not parse, since the block it opened is
  3213. // what activated us and the next child must not reach a recycled list.
  3214. if (!sink && published && _frameActive[d] === 1) sink = _streamOnNode;
  3215. if (!childRule) continue;
  3216. if (sink) {
  3217. sink(childRule);
  3218. continue;
  3219. }
  3220. if (rules === null) {
  3221. rules = [];
  3222. // The frame is written before any child rule is consumed, so it is
  3223. // already pointing at this block and needs the list it did not yet have.
  3224. if (published) _frameRules[d] = rules;
  3225. }
  3226. rules.push(childRule);
  3227. if (_nodeCount > limit) {
  3228. _streamActivate(d);
  3229. sink = _streamOnNode;
  3230. limit = _STREAM_NO_LIMIT;
  3231. }
  3232. }
  3233. };
  3234. /**
  3235. * Object-returning wrapper over `consumeABlocksContentsInto` for the callers that
  3236. * are not per-rule (the `parseABlocksContents` entry point and the printer map).
  3237. * @param {TokenStream} ts token stream
  3238. * @param {((node: Declaration | Rule) => void)=} onNode optional per-node sink (streaming); nodes are not collected when given
  3239. * @returns {{ decls: Declaration[], rules: Rule[] }} consumed decls + rules
  3240. */
  3241. const consumeABlocksContents = (ts, onNode) => {
  3242. consumeABlocksContentsInto(ts, onNode);
  3243. return { decls: _bcDecls, rules: _bcRules };
  3244. };
  3245. /**
  3246. * The same production read as the whole input rather than as a block's inside —
  3247. * what an HTML `style=""` holds. Nothing opened a block, so a `}` closes none.
  3248. * @param {TokenStream} ts token stream
  3249. * @param {((node: Rule | Declaration) => void)=} onNode streaming sink
  3250. * @returns {void}
  3251. */
  3252. const consumeADeclarationList = (ts, onNode) => {
  3253. consumeABlocksContentsInto(ts, onNode, undefined, undefined, false);
  3254. };
  3255. /**
  3256. * Consume the remnants of a bad declaration, CSS Syntax Level 3 [§5.4.11](https://drafts.csswg.org/css-syntax/#consume-the-remnants-of-a-bad-declaration). Advances the stream past a malformed declaration's tail so the caller (`consumeABlocksContents`) can resume cleanly.
  3257. * @param {TokenStream} ts token stream
  3258. * @param {boolean} nested whether the call originates from inside a `{}` block
  3259. * @returns {void}
  3260. */
  3261. const consumeTheRemnantsOfABadDeclaration = (ts, nested) => {
  3262. // Process input:
  3263. for (;;) {
  3264. const t = ts.next();
  3265. // <eof-token> / <semicolon-token>
  3266. // Discard a token from input, and return.
  3267. if (t.type === TT_EOF || t.type === TT_SEMICOLON) {
  3268. ts.discard();
  3269. return;
  3270. }
  3271. // <}-token>
  3272. // If nested is true, return. Otherwise, discard a token.
  3273. if (t.type === TT_RIGHT_CURLY_BRACKET) {
  3274. if (nested) return;
  3275. ts.discard();
  3276. continue;
  3277. }
  3278. // anything else
  3279. // Consume a component value from input, and do nothing.
  3280. consumeAComponentValue(ts);
  3281. }
  3282. };
  3283. /**
  3284. * Consume a declaration, CSS Syntax Level 3 [§5.4.6](https://drafts.csswg.org/css-syntax/#consume-declaration).
  3285. * @param {TokenStream} ts token stream
  3286. * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the value
  3287. * @returns {Declaration | undefined} parsed declaration, or `undefined` on the spec's "return nothing" branches (steps 1, 3, 8)
  3288. */
  3289. const consumeADeclaration = (ts, nested = false) => {
  3290. const { input } = ts;
  3291. // Let decl be a new declaration, with an initially empty name and a value set to an empty list.
  3292. const start = ts.next().start;
  3293. // nameEnd (= start) / important (unset) keep their container defaults;
  3294. // `value` is set unconditionally at step 5 below.
  3295. const decl = /** @type {Declaration} */ (
  3296. _makeContainer(T_DECLARATION, start, start)
  3297. );
  3298. // 1. If the next token is an <ident-token>, consume a token from input and set decl's name to the returned token's value.
  3299. // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
  3300. if (ts.next().type === TT_IDENTIFIER) {
  3301. const head = ts.consume();
  3302. _setName(decl, head.start, head.end);
  3303. } else {
  3304. consumeTheRemnantsOfABadDeclaration(ts, nested);
  3305. return undefined;
  3306. }
  3307. // 2. Discard whitespace from input.
  3308. while (ts.next().type === TT_WHITESPACE) ts.advance();
  3309. // 3. If the next token is a <colon-token>, discard a token from input.
  3310. // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
  3311. if (ts.next().type === TT_COLON) {
  3312. ts.advance();
  3313. } else {
  3314. consumeTheRemnantsOfABadDeclaration(ts, nested);
  3315. return undefined;
  3316. }
  3317. // 4. Discard whitespace from input.
  3318. while (ts.next().type === TT_WHITESPACE) ts.advance();
  3319. // Step 8's custom-property test, computed early so the value parse can bail.
  3320. const isCustomProperty = input.startsWith("--", start);
  3321. // 5. Consume a list of component values from input, with nested, and with <semicolon-token> as the stop token, and set decl's value to the result.
  3322. // A nested non-custom declaration bails on a top-level `{` — step 8 would
  3323. // reject it and the caller restores its mark, so parsing the block (the
  3324. // entire nested-rule body, re-parsed as a qualified rule after the
  3325. // restore) would be pure waste.
  3326. const value = consumeAListOfComponentValues(
  3327. ts,
  3328. TT_SEMICOLON,
  3329. nested,
  3330. nested && !isCustomProperty
  3331. );
  3332. if (value === null) return undefined;
  3333. // `_setValue` waits until step 9: steps 6-8 still trim / scan the scratch,
  3334. // and the store consumes it when sealing.
  3335. _setEnd(decl, ts.next().start);
  3336. // 6. If the last two non-<whitespace-token>s in decl's value are a <delim-token> with the value "!" followed by an <ident-token> with a value that is an ASCII case-insensitive match for "important", remove them from decl's value and set decl's important flag.
  3337. {
  3338. let last = value.length - 1;
  3339. while (last >= 0 && _nodeTypeOf(value[last]) === T_WHITESPACE) last--;
  3340. let prev = last - 1;
  3341. while (prev >= 0 && _nodeTypeOf(value[prev]) === T_WHITESPACE) prev--;
  3342. // `!` delim first: it's almost always absent, and `_nodeValueOf` allocates
  3343. // a slice — this order pays it only for genuine `!important` candidates.
  3344. if (
  3345. prev >= 0 &&
  3346. _nodeTypeOf(value[prev]) === T_DELIM &&
  3347. input.charCodeAt(_nodeStartOf(value[prev])) === CC_EXCLAMATION &&
  3348. _nodeTypeOf(value[last]) === T_IDENT &&
  3349. equalsLowerCase(_nodeValueOf(value[last]), "important")
  3350. ) {
  3351. _setImportant(decl);
  3352. // Trimmed by popping so the pooled list keeps its backing store.
  3353. while (value.length > prev) value.pop();
  3354. }
  3355. }
  3356. // 7. While the last item in decl's value is a <whitespace-token>, remove that token.
  3357. while (
  3358. value.length > 0 &&
  3359. _nodeTypeOf(value[value.length - 1]) === T_WHITESPACE
  3360. ) {
  3361. value.pop();
  3362. }
  3363. // 8. If decl's name starts with "--" (a custom property), it can contain any value (including a top-level `{}` block) — accept it.
  3364. // Otherwise, if decl's value contains a top-level simple block with an associated token of <{-token>, and also contains any other non-whitespace token, return nothing.
  3365. // (That is, a top-level {}-block is the whole value of a non-custom property or nothing — for CSS Nesting, `consumeABlocksContents`'s `mark` / `restore a mark` will retry the input as a qualified rule.)
  3366. // Otherwise, accept the declaration. (The spec also checks "contains any non-whitespace-tokens at the top level" → return nothing; we keep empty-value declarations because callers — e.g. `@value name:;` — rely on them.)
  3367. if (!isCustomProperty) {
  3368. let block = false;
  3369. let beside = false;
  3370. for (let i = 0; i < value.length && !(block && beside); i++) {
  3371. const v = value[i];
  3372. const type = _nodeTypeOf(v);
  3373. if (type === T_SIMPLE_BLOCK && _nodeTokenOf(v) === "{") {
  3374. // A second one stands beside the first.
  3375. if (block) beside = true;
  3376. block = true;
  3377. } else if (type !== T_WHITESPACE) {
  3378. beside = true;
  3379. }
  3380. }
  3381. if (block && beside) return undefined;
  3382. }
  3383. // 9. Return decl.
  3384. _setValue(decl, value);
  3385. return decl;
  3386. };
  3387. /**
  3388. * Consume a list of component values, CSS Syntax Level 3 [§5.4.7](https://drafts.csswg.org/css-syntax/#consume-list-of-components) — consumes component values until EOF, the optional `stopToken`, or — when `nested` — a top-level `}` (left in the stream); a non-nested `}` is a parse error appended as a token.
  3389. * @param {TokenStream} ts token stream
  3390. * @param {number=} stopToken token type that terminates the list (left unconsumed)
  3391. * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the list (left unconsumed)
  3392. * @param {boolean=} bailOnCurly abort with `null` on a top-level `{` that something non-whitespace already stands before (left unconsumed) — for callers that would reject the list anyway (consume-a-declaration step 8) and restore a mark; a `{}` block alone is the whole value there, so that one is consumed
  3393. * @returns {ComponentValue[] | null} consumed component values, or `null` when `bailOnCurly` hit
  3394. */
  3395. const consumeAListOfComponentValues = (
  3396. ts,
  3397. stopToken,
  3398. nested = false,
  3399. bailOnCurly = false
  3400. ) => {
  3401. const values = /** @type {ComponentValue[]} */ (_takeList());
  3402. // Process input
  3403. for (;;) {
  3404. const t = ts.next();
  3405. // <eof-token>
  3406. // stop token (if passed)
  3407. // Return values.
  3408. if (t.type === TT_EOF || t.type === stopToken) {
  3409. return values;
  3410. }
  3411. // <}-token>
  3412. // If nested is true, return values.
  3413. // Otherwise, this is a parse error. Consume a token from input and append the result to values.
  3414. if (t.type === TT_RIGHT_CURLY_BRACKET) {
  3415. if (nested) return values;
  3416. const closer = consumeATokenAsNode(ts);
  3417. // Keep unless the type is explicitly marked skip (1); an out-of-range
  3418. // lookup on a short `skip.types` yields `undefined`, which must not drop.
  3419. if (!_skipActive || _skipTypes[_nodeTypeOf(closer)] !== 1) {
  3420. values.push(closer);
  3421. }
  3422. continue;
  3423. }
  3424. // A top-level `{` dooms the list for a bailing caller — stop before the
  3425. // whole block is parsed only to be thrown away on the caller's restore.
  3426. // Only once something stands before it: a `{}` block alone is a
  3427. // declaration's whole value (§5.4.6 step 8), which the caller keeps.
  3428. if (bailOnCurly && t.type === TT_LEFT_CURLY_BRACKET) {
  3429. let doomed = false;
  3430. for (let i = 0; i < values.length; i++) {
  3431. if (_nodeTypeOf(values[i]) !== T_WHITESPACE) {
  3432. doomed = true;
  3433. break;
  3434. }
  3435. }
  3436. if (doomed) return null;
  3437. }
  3438. // anything else
  3439. // Consume a component value from input, and append the result to values.
  3440. // Skipped leaf types short-circuit before materializing: no column slot is
  3441. // written and no node is built (blocks / functions never skip here).
  3442. const tt = t.type;
  3443. if (
  3444. _skipActive &&
  3445. tt !== TT_FUNCTION &&
  3446. !(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
  3447. _skipTypes[_ttToNodeType[tt]] === 1
  3448. ) {
  3449. // `t` was just peeked and is a skipped value leaf (never EOF).
  3450. ts.advance();
  3451. continue;
  3452. }
  3453. const node = consumeAComponentValue(ts, t);
  3454. if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) values.push(node);
  3455. }
  3456. };
  3457. /**
  3458. * Consume a component value, CSS Syntax Level 3 [§5.4.8](https://drafts.csswg.org/css-syntax/#consume-component-value) — consumes the next value (simple block, function, or single token); callers guard against EOF before calling.
  3459. * @param {TokenStream} ts token stream
  3460. * @param {MutableToken=} t the next token, if the caller already peeked it (defaults to `ts.next()`)
  3461. * @returns {SimpleBlock | FunctionNode | ComponentValue} the consumed component value
  3462. */
  3463. const consumeAComponentValue = (ts, t = ts.next()) => {
  3464. // `t` is the next token; hot callers already peeked it and pass it in to
  3465. // skip a redundant `ts.next()` per component value.
  3466. // <{-token> / <[-token> / <(-token> (the three contiguous opening brackets)
  3467. // Consume a simple block from input and return the result.
  3468. if (t.type >= TT_LEFT_PARENTHESIS && t.type <= TT_LEFT_CURLY_BRACKET) {
  3469. return /** @type {SimpleBlock} */ (consumeASimpleBlock(ts));
  3470. }
  3471. // <function-token>
  3472. // Consume a function from input and return the result.
  3473. if (t.type === TT_FUNCTION) {
  3474. return /** @type {FunctionNode} */ (consumeAFunction(ts));
  3475. }
  3476. // anything else
  3477. // Consume a token from input and return the result. (Asserted: not EOF.)
  3478. // Inlined `consumeATokenAsNode`: `t` is already the peeked next token (and not
  3479. // EOF), so `advance` past it and materialize it directly — no redundant
  3480. // `next()` and one fewer call per leaf component value (the bulk of the nodes
  3481. // on a large stylesheet).
  3482. ts.advance();
  3483. return /** @type {ComponentValue} */ (tokenToNode(t));
  3484. };
  3485. /**
  3486. * Consume a simple block, CSS Syntax Level 3 [§5.4.9](https://drafts.csswg.org/css-syntax/#consume-simple-block) — the next token must be `(`, `[`, or `{` (asserted); consumes component values via `consumeAComponentValue` until the mirror closing token (`)`, `]`, `}`) or EOF, returning the partial block on EOF (parse error).
  3487. * @param {TokenStream} ts token stream
  3488. * @returns {SimpleBlock | undefined} the parsed simple block
  3489. */
  3490. const consumeASimpleBlock = (ts) => {
  3491. const open = ts.next();
  3492. // Assert (spec): the next token of input is <{-token>, <[-token>, or <(-token>.
  3493. // Mirror closing token (`opener + 3`) and the associated block char.
  3494. const ending = open.type + 3;
  3495. const token = BLOCK_TOKEN_CHAR[open.type - TT_LEFT_PARENTHESIS];
  3496. // Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.
  3497. const block = /** @type {SimpleBlock} */ (
  3498. _makeContainer(T_SIMPLE_BLOCK, open.start, open.end)
  3499. );
  3500. _setToken(block, token);
  3501. // Sealed (`_setValue`) at the return, once complete.
  3502. const val = _takeList();
  3503. // Discard a token from input.
  3504. ts.discard();
  3505. // Process input
  3506. for (;;) {
  3507. const t = ts.next();
  3508. // <eof-token>
  3509. // ending token
  3510. // Discard a token from input. Return block.
  3511. if (t.type === TT_EOF || t.type === ending) {
  3512. ts.discard();
  3513. _setValue(block, val);
  3514. _setEnd(block, t.end);
  3515. return block;
  3516. }
  3517. // anything else
  3518. // Consume a component value from input and append the result to block’s value.
  3519. val.push(consumeAComponentValue(ts, t));
  3520. }
  3521. };
  3522. /**
  3523. * Consume a function, CSS Syntax Level 3 [§5.4.10](https://drafts.csswg.org/css-syntax/#consume-function) — consumes component values up to the matching `)` or EOF (the partial function on EOF is a parse error).
  3524. * @param {TokenStream} ts token stream
  3525. * @returns {FunctionNode | undefined} the consumed function node
  3526. */
  3527. const consumeAFunction = (ts) => {
  3528. // Assert (spec): the next token is a <function-token>.
  3529. // Consume a token from input, and let function be a new function with its name equal the returned token’s value, and a value set to an empty list.
  3530. const tFn = ts.consume();
  3531. const fn = /** @type {FunctionNode} */ (
  3532. _makeContainer(T_FUNCTION, tFn.start, tFn.end)
  3533. );
  3534. _setName(fn, tFn.start, tFn.end - 1);
  3535. // Sealed (`_setValue`) at the return, once complete.
  3536. const val = _takeList();
  3537. // Process input
  3538. for (;;) {
  3539. const t = ts.next();
  3540. if (t.type === TT_EOF || t.type === TT_RIGHT_PARENTHESIS) {
  3541. // <eof-token>
  3542. // <)-token>
  3543. // Discard a token from input. Return function.
  3544. ts.discard();
  3545. _setValue(fn, val);
  3546. _setEnd(fn, t.end);
  3547. return fn;
  3548. }
  3549. // anything else
  3550. // Consume a component value from input and append the result to function’s value.
  3551. // Same pre-materialization skip as `consumeAListOfComponentValues`.
  3552. const tt = t.type;
  3553. if (
  3554. _skipActive &&
  3555. tt !== TT_FUNCTION &&
  3556. !(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
  3557. _skipTypes[_ttToNodeType[tt]] === 1
  3558. ) {
  3559. // `t` was just peeked and is a skipped value leaf (never EOF).
  3560. ts.advance();
  3561. continue;
  3562. }
  3563. const node = consumeAComponentValue(ts, t);
  3564. if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) val.push(node);
  3565. }
  3566. };
  3567. // Identifier escape / unescape — operate on the raw text of an
  3568. // `<ident-token>` (or any source slice that may carry CSS escape sequences).
  3569. // `escapeIdentifier` produces a CSS-Syntax-3-conformant `<ident-token>` from
  3570. // an arbitrary string (so the result can be re-tokenized as the same name);
  3571. // `unescapeIdentifier` reverses tokenizer-time escapes per
  3572. // https://www.w3.org/TR/css-syntax-3/#consume-escaped-code-point.
  3573. // Both are pure string functions and have no dependency on the AST; they
  3574. // live here so the AST module is a one-stop shop for CSS-syntax-level
  3575. // utilities. `CssParser.js` re-exports them for back-compat with callers
  3576. // that previously reached them via `getCssParser()`.
  3577. const regexSingleEscape = /[ -,./:-@[\]^`{-~]/;
  3578. const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6}) (?![a-fA-F0-9 ])/g;
  3579. // ASCII escape class per char code: 0 = pass through, 1 = `\<char>` single
  3580. // escape, 2 = `\HEX ` (control chars). Built from the original predicates so
  3581. // behaviour is identical; replaces two regex tests per character with one load.
  3582. const ESCAPE_CLASS_HEX = 2;
  3583. const ESCAPE_CLASS_SINGLE = 1;
  3584. const _escapeClassTable = new Uint8Array(128);
  3585. for (let i = 0; i < 128; i++) {
  3586. const ch = String.fromCharCode(i);
  3587. _escapeClassTable[i] = /[\t\n\f\r\v]/.test(ch)
  3588. ? ESCAPE_CLASS_HEX
  3589. : ch === "\\" || regexSingleEscape.test(ch)
  3590. ? ESCAPE_CLASS_SINGLE
  3591. : 0;
  3592. }
  3593. /**
  3594. * Returns escaped identifier.
  3595. * @param {string} str string
  3596. * @returns {string} escaped identifier
  3597. */
  3598. const _escapeIdentifier = (str) => {
  3599. let output = "";
  3600. // Flush safe runs in bulk: only escaped chars break the run, so an
  3601. // identifier needing no escapes returns `str` unchanged (no allocation).
  3602. let lastFlush = 0;
  3603. let needSpaceFix = false;
  3604. for (let i = 0; i < str.length; i++) {
  3605. const cc = str.charCodeAt(i);
  3606. const cls = cc < 128 ? _escapeClassTable[cc] : 0;
  3607. if (cls === 0) continue;
  3608. output += str.slice(lastFlush, i);
  3609. if (cls === ESCAPE_CLASS_SINGLE) {
  3610. output += `\\${str[i]}`;
  3611. } else {
  3612. output += `\\${cc.toString(16).toUpperCase()} `;
  3613. needSpaceFix = true;
  3614. }
  3615. lastFlush = i + 1;
  3616. }
  3617. output = lastFlush === 0 ? str : output + str.slice(lastFlush);
  3618. // `-` and digits are class 0 (never escaped above), so testing `str`'s lead
  3619. // char codes is equivalent to regexes over `output` — and keeps the common
  3620. // nothing-to-do call regex-free.
  3621. const first = str.charCodeAt(0);
  3622. if (
  3623. first === CC_HYPHEN_MINUS &&
  3624. (str.charCodeAt(1) === CC_HYPHEN_MINUS || _isDigit(str.charCodeAt(1)))
  3625. ) {
  3626. output = `\\-${output.slice(1)}`;
  3627. } else if (_isDigit(first)) {
  3628. // A leading digit becomes `\3<digit> `, another `\HEX ` run to clean up.
  3629. output = `\\3${str.charAt(0)} ${output.slice(1)}`;
  3630. needSpaceFix = true;
  3631. }
  3632. // Remove spaces after `\HEX` escapes that are not followed by a hex digit,
  3633. // since they’re redundant. Only `\HEX ` runs (above) can produce them; plain
  3634. // single escapes can't, so skip the scan when none were emitted. Note this is
  3635. // only possible if the escape isn't preceded by an odd number of backslashes.
  3636. if (needSpaceFix) {
  3637. output = output.replace(regexExcessiveSpaces, ($0, $1, $2) => {
  3638. /* istanbul ignore if -- @preserve: this escaper never emits an odd run of backslashes before a `\HEX` escape (literal `\` is doubled) */
  3639. if ($1 && $1.length % 2) {
  3640. // It’s not safe to remove the space, so don’t.
  3641. return $0;
  3642. }
  3643. // Strip the space.
  3644. return ($1 || "") + $2;
  3645. });
  3646. }
  3647. return output;
  3648. };
  3649. /**
  3650. * Returns hex. Reads up to six hex digits from `str` starting at `start` —
  3651. * indexed rather than sliced, and case-folded inline, so the common
  3652. * non-hex escape (e.g. `\:` in `focus\:sr-only`) allocates nothing.
  3653. * @param {string} str string
  3654. * @param {number} start index just past the `\`
  3655. * @returns {[string, number] | undefined} hex
  3656. */
  3657. const gobbleHex = (str, start) => {
  3658. let hex = "";
  3659. for (let i = 0; i < 6; i++) {
  3660. const code = str.charCodeAt(start + i);
  3661. // valid hex char [0-9 | A-F | a-f]; out-of-range reads NaN -> invalid
  3662. const valid =
  3663. (code >= 48 && code <= 57) ||
  3664. (code >= 65 && code <= 70) ||
  3665. (code >= 97 && code <= 102);
  3666. if (!valid) break;
  3667. // parseInt below is case-insensitive, so keep the original char.
  3668. hex += str[start + i];
  3669. }
  3670. if (hex.length === 0) return undefined;
  3671. // One trailing whitespace terminates the escape, matching the tokenizer's
  3672. // `_consumeAnEscapedCodePoint` — including after a full 6-digit escape, for
  3673. // any CSS whitespace (not just space), plus the extra LF of a CRLF pair.
  3674. // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
  3675. let consumed = hex.length;
  3676. const trail = str.charCodeAt(start + hex.length);
  3677. if (_isWhiteSpace(trail)) {
  3678. consumed = consumeExtraNewline(trail, str, start + hex.length + 1) - start;
  3679. }
  3680. const codePoint = Number.parseInt(hex, 16);
  3681. const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff;
  3682. // Add special case for
  3683. // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
  3684. // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
  3685. if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) {
  3686. return ["�", consumed];
  3687. }
  3688. return [String.fromCodePoint(codePoint), consumed];
  3689. };
  3690. /**
  3691. * Unescape identifier.
  3692. * @param {string} str string
  3693. * @returns {string} unescaped string
  3694. */
  3695. const _unescapeIdentifier = (str) => {
  3696. // `indexOf` is the no-escape fast path and the start offset in one — the
  3697. // leading safe run is skipped and an unescaped ident returns as-is.
  3698. const first = str.indexOf("\\");
  3699. if (first === -1) return str;
  3700. let ret = "";
  3701. // Flush safe runs in bulk instead of appending char by char.
  3702. let lastFlush = 0;
  3703. for (let i = first; i < str.length; i++) {
  3704. if (str[i] !== "\\") continue;
  3705. ret += str.slice(lastFlush, i);
  3706. const gobbled = gobbleHex(str, i + 1);
  3707. if (gobbled !== undefined) {
  3708. ret += gobbled[0];
  3709. i += gobbled[1];
  3710. } else if (str[i + 1] === "\\") {
  3711. // Retain one `\` of an escaped `\\` pair.
  3712. // https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e
  3713. ret += "\\";
  3714. i += 1;
  3715. } else if (str.length === i + 1) {
  3716. // A trailing lone `\` is retained.
  3717. // https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb
  3718. ret += "\\";
  3719. }
  3720. // Otherwise the lone `\` is dropped; the next char flushes with its run.
  3721. lastFlush = i + 1;
  3722. }
  3723. ret += str.slice(lastFlush);
  3724. return ret;
  3725. };
  3726. // Cacheable per `compiler.root` — CssParser binds once per parse via
  3727. // `.bindCache(...)` and reuses for every identifier.
  3728. const escapeIdentifier = makeCacheable(_escapeIdentifier);
  3729. const unescapeIdentifier = makeCacheable(_unescapeIdentifier);
  3730. // A url-token / url-string value's escaped newlines (`url("im\<newline>g.png")`).
  3731. const STRING_MULTILINE = /\\[\n\r\f]/g;
  3732. // Leading / trailing CSS whitespace inside a quoted url value.
  3733. const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
  3734. // One CSS escape: `\` + up to 6 hex digits (+ optional whitespace) or any char.
  3735. const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;
  3736. /**
  3737. * Normalize a url value (a url-token's content or a url string's body) into
  3738. * the form requests are resolved from: escaped newlines removed (string form),
  3739. * edge whitespace trimmed, CSS escapes and percent-encoding decoded
  3740. * (`data:` URIs excepted).
  3741. * @param {string} str url string
  3742. * @param {boolean} isString is url wrapped in quotes
  3743. * @returns {string} normalized url
  3744. */
  3745. const normalizeUrl = (str, isString) => {
  3746. // Fast paths: skip the regex engine for the common URL with no escape and
  3747. // no edge whitespace (e.g. `./img.png`). Each guard is equivalent to the
  3748. // regex being a no-op.
  3749. // Remove escaped newlines from a string-token url like `url("im\<newline>g.png")`.
  3750. if (isString && str.includes("\\")) {
  3751. str = str.replace(STRING_MULTILINE, "");
  3752. }
  3753. // Remove unnecessary spaces from `url(" img.png ")`
  3754. if (
  3755. str.length !== 0 &&
  3756. (_isWhiteSpace(str.charCodeAt(0)) ||
  3757. _isWhiteSpace(str.charCodeAt(str.length - 1)))
  3758. ) {
  3759. str = str.replace(TRIM_WHITE_SPACES, "");
  3760. }
  3761. // Unescape
  3762. if (str.includes("\\")) {
  3763. str = str.replace(UNESCAPE, (match) => {
  3764. if (match.length > 2) {
  3765. return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
  3766. }
  3767. return match[1];
  3768. });
  3769. }
  3770. // Char-code gate so the dominant non-`data:` url skips the regex test.
  3771. if ((str.charCodeAt(0) | 0x20) === CC_LOWER_D && /^data:/i.test(str)) {
  3772. return str;
  3773. }
  3774. if (str.includes("%")) {
  3775. // Convert `url('%2E/img.png')` -> `url('./img.png')`
  3776. try {
  3777. str = decodeURIComponent(str);
  3778. } catch (_err) {
  3779. // Ignore
  3780. }
  3781. }
  3782. return str;
  3783. };
  3784. // CSS-typed views over the generic visitor machinery (`util/SourceProcessor`),
  3785. // re-exported so consumers keep importing them from this module.
  3786. /**
  3787. * @typedef {import("../util/SourceProcessor").VisitorFn<CssPath>} VisitorFn
  3788. * @typedef {import("../util/SourceProcessor").VisitorBucket<CssPath>} VisitorBucket
  3789. * @typedef {import("../util/SourceProcessor").VisitorMap<CssPath>} VisitorMap
  3790. * @typedef {import("../util/SourceProcessor").CompiledVisitorMap<CssPath>} CompiledVisitorMap
  3791. */
  3792. /**
  3793. * A CSS Syntax §5.4 top-level consumer that streams each top-level node it
  3794. * produces to `onNode` (in source order) rather than collecting it. Every entry
  3795. * in `TOP_LEVEL_CONSUMERS` shares this shape, so the walk's `grammar` drives any
  3796. * `as` mode through one call — a future mode is just another map entry.
  3797. * @typedef {(ts: TokenStream, onNode: (node: Rule | Declaration) => void) => void} TopLevelConsumer
  3798. */
  3799. /**
  3800. * `as` value → the §5.4 consumer that streams its top-level nodes. Keyed by the
  3801. * public `CssParserOptions.as` enum.
  3802. * @type {Record<string, TopLevelConsumer>}
  3803. */
  3804. const TOP_LEVEL_CONSUMERS = {
  3805. stylesheet: /** @type {TopLevelConsumer} */ (consumeAStylesheetsContents),
  3806. "block-contents": consumeADeclarationList
  3807. };
  3808. /**
  3809. * What the minifying printer may rewrite. Every entry is on unless it is
  3810. * `false`, so a document one transform breaks can still be minified by the rest.
  3811. * @typedef {object} CssTransformOptions
  3812. * @property {(boolean | "all" | "some" | string | RegExp | ((comment: string) => boolean))=} comments which comments survive: `"some"` (the default) the ones that carry something, `true` / `"all"` every one, `false` none, or the ones a pattern matches / a predicate accepts, over the comment's own text
  3813. * @property {boolean=} mergeLonghands write a family of longhands as the one shorthand that sets them
  3814. * @property {boolean=} mergeRules join rules that print the same block, at-rules that share a prelude, and a named `@layer` block a later sibling opens again
  3815. * @property {boolean=} normalizeQuotes normalize a string's, `url()`'s, font family's and attribute value's quoting
  3816. * @property {boolean=} reduceFunctions compute a call into the shorter call naming the same value (`calc()` and the math functions, transforms, gradients, easing functions, filters)
  3817. * @property {boolean=} removeDeadRules drop a rule or declaration nothing can read: an empty rule, and one an identical later one supersedes
  3818. * @property {boolean=} shortenColors write each color in the shortest spelling of the same value
  3819. * @property {boolean=} shortenMediaQueries write a media feature in its range spelling and collapse an `and` of two into the interval
  3820. * @property {boolean=} shortenNumbers write each number in its shortest equal spelling
  3821. * @property {boolean=} shortenSelectors rewrite a selector into a shorter equal one
  3822. * @property {boolean=} shortenValues write a value the shortest way its property's own grammar allows
  3823. */
  3824. /**
  3825. * @typedef {object} CssProcessOptions
  3826. * @property {LocConverter=} locConverter shared loc converter (default a fresh one over the input)
  3827. * @property {boolean=} recurseBlocks walk into block bodies' nested rules (default true)
  3828. * @property {("stylesheet" | "block-contents")=} as which top-level production to consume the source as (see `TOP_LEVEL_CONSUMERS`): `"stylesheet"` (default) or `"block-contents"` (a block's contents, e.g. an HTML `style` attribute)
  3829. * @property {SkipOptions=} skip what the grammar may leave un-materialized to go faster — safe only for parts nothing reads in the active parse; default skip nothing. Ignored while printing (`minimize`), which needs every node
  3830. * @property {boolean=} minimize print the safely-minified serialization (collapsed whitespace, dropped redundant separators, the `printer`'s value transforms) as `process` walks and return `{ code, map }` (default false = walk only, return `undefined`)
  3831. * @property {string=} source name of the input in the emitted source map (`sources[0]` / `file`); only read while printing
  3832. * @property {string=} content the input's contents for the map's `sourcesContent`; only read while printing
  3833. * @property {CssEnvironment=} environment what the target can read (the CSS entries of `output.environment`), so a spelling it would not understand is never reached for; only read while printing, and an absent entry means the modern spelling is available
  3834. * @property {boolean=} convertLengthUnits rewrite a length into a shorter unit it is exactly equal in (`16px` -> `1pc`); off by default because it earns nothing once the asset is compressed, and only read while printing. A time is always rewritten
  3835. * @property {boolean=} rewriteCustomProperties shorten a custom property's value the way any other value is shortened (`--x:#ffffff` -> `#fff`); off by default because `getPropertyValue()` hands that text back, and only read while printing. What it may rewrite is what any other value's tokens may be, a color in a substitution's fallback included — that being the property's value rather than the function's own argument
  3836. * @property {EmbeddedSourceRenderer=} renderEmbeddedSource renders source this stylesheet embeds: the payload of a `url()` `data:` URL whose media type names a language webpack knows (SVG, CSS, HTML, JSON, JavaScript). Absent, a data URL is emitted exactly as written
  3837. * @property {CssTransformOptions=} transforms which of the meaning-preserving rewrites the minifying print makes; each is on unless it is `false`
  3838. * @property {DeferredEmbeddedSource[]=} deferEmbeddedSource collects what `renderEmbeddedSource` would be offered instead of offering it, for a caller whose renderer is asynchronous: the print leaves a marker for each and `finish` puts the answers in their place, so one parse serves both. Takes precedence over `renderEmbeddedSource`
  3839. */
  3840. /**
  3841. * The environment the stylesheet is built for. Every CSS ability the printer
  3842. * reaches for is read off this selection, so nothing states one separately.
  3843. * @typedef {object} CssEnvironment
  3844. * @property {string[]=} browsers the browserslist selection (`["chrome 100", "safari 15"]`), so vendor prefixes and every spelling a target has to be able to read are decided for exactly these browsers; absent leaves prefixes untouched and assumes every ability
  3845. * @property {boolean=} vendorPrefixes whether prefixes are written at all; false leaves them alone while the selection still decides which spellings a target reads
  3846. */
  3847. /**
  3848. * `CssProcessOptions.skip`: two independent axes, so each reads unambiguously.
  3849. * @typedef {object} SkipOptions
  3850. * @property {Uint8Array=} types component-value node types to drop from declaration value / function-arg lists (indexed by `NodeType`, 1 = skip; build with `buildSkipSet`)
  3851. * @property {boolean=} selectorPrelude drop qualified-rule (selector) preludes — the rule and its block are still produced (default false)
  3852. * @property {boolean=} atRulePrelude drop at-rule preludes — the at-rule and its block are still produced (default false)
  3853. */
  3854. // Per-parse walk state in module slots (same pattern as `_skip*`) so the walk
  3855. // functions below are module-level constants: one function identity across
  3856. // parses keeps the recursive per-node call sites monomorphic and drops the
  3857. // per-parse closure allocations.
  3858. /** @typedef {import("../util/SourceProcessor").CompiledVisitorBucket<CssPath>} CompiledVisitorBucket */
  3859. /** @type {CompiledVisitorMap} */
  3860. let _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
  3861. let _recurseBlocks = true;
  3862. // Printing needs a faithful serialization, so it keeps dropped input as `T_RAW`
  3863. // nodes; a walk-only parse leaves this false and allocates none.
  3864. let _printing = false;
  3865. // The selectors / at-rules met so far in each open block, so a prefixed rule can
  3866. // be added or dropped against the sibling it needs without buffering the whole
  3867. // stylesheet (streaming holds ~one node). Keyed by the block's rule — null for
  3868. // the stylesheet itself — and dropped as that block finishes, so it holds one
  3869. // set per open ancestor and never a recycled node. Null unless minifying for a
  3870. // target. Markers: `signature` (the unprefixed rule is present) and
  3871. // `signature\0prefix`.
  3872. /** @type {Map<Node | null, PrefixScope> | null} */
  3873. let _seenPrefixRules = null;
  3874. // Whether vendor prefixes are written at all. The selection still answers which
  3875. // spellings a target reads, so `vendorPrefixes: false` turns off only this.
  3876. let _prefixingOn = false;
  3877. // The prefixed rule just printed, and the signature of the unprefixed twin that
  3878. // would make it dead weight — read by whoever writes that rule out, so it can go
  3879. // as a piece of its own. Null when the rule just printed is not one.
  3880. /** @type {{ node: Node, signature: string } | null} */
  3881. let _prefixDropCandidate = null;
  3882. // The browserslist selection this parse prefixes for, as the versions selected
  3883. // for each browser by its `SUPPORT_BROWSERS` slot; null unless minifying for
  3884. // one, which is what turns prefixing on.
  3885. /** @type {(number[] | undefined)[] | null} */
  3886. let _prefixBrowsers = null;
  3887. /** @type {CompiledVisitorBucket | undefined} */
  3888. let _commentBucket;
  3889. // Comments reach the visitor map through `NodeType.Comment` instead of a
  3890. // side callback. They fire during tokenization — in source order among
  3891. // comments, not interleaved with the node walk — on a transient store node so
  3892. // `A.start`/`end`/`loc`/`source` work. No comment visitor → no callback →
  3893. // the tokenizer skips comments with zero overhead.
  3894. /** @type {(input: string, start: number, end: number) => number} */
  3895. const _grammarOnComment = (_src, start, end) => {
  3896. const node = _makeLeaf(T_COMMENT, start, end);
  3897. _currentNode = node;
  3898. _currentParent = null;
  3899. _currentIndex = 0;
  3900. const bucket = /** @type {CompiledVisitorBucket} */ (_commentBucket);
  3901. const e = bucket.enter;
  3902. for (let i = 0; i < e.length; i++) e[i](A);
  3903. const x = bucket.exit;
  3904. for (let i = 0; i < x.length; i++) x[i](A);
  3905. return end;
  3906. };
  3907. /**
  3908. * A node's post-order tail, shared by `_walkValue` / `_walkRule`: fire its `exit`
  3909. * visitors, then — when printing — its printer, so the print step lives in one
  3910. * place instead of being repeated in each walker. Rebinds the path onto `node`
  3911. * (descending into children moved it). `writer` undefined = walk only, no print.
  3912. * @param {Node} node the finished node
  3913. * @param {Node | null} parent enclosing node
  3914. * @param {number} index node's index within its sibling list
  3915. * @param {CompiledVisitorBucket | undefined} b the node's visitor bucket
  3916. * @param {PrintContext | undefined} writer print context when printing, else undefined
  3917. */
  3918. const _exitNode = (node, parent, index, b, writer) => {
  3919. if (b === undefined && writer === undefined) return;
  3920. _currentNode = node;
  3921. _currentParent = parent;
  3922. _currentIndex = index;
  3923. if (b !== undefined) {
  3924. const x = b.exit;
  3925. for (let i = 0; i < x.length; i++) x[i](A);
  3926. }
  3927. if (writer !== undefined) writer.printNode(node, A);
  3928. };
  3929. // The letters those two sets' names start with, as a mask over `a`-`z`. Derived
  3930. // from the sets, so a name added to either is covered without touching this.
  3931. const _MATH_NAME_FIRST_LETTERS = (() => {
  3932. let mask = 0;
  3933. for (const name of MATH_FUNCTIONS) mask |= 1 << (name.charCodeAt(0) - 0x61);
  3934. for (const name of STEPPED_FUNCTIONS) {
  3935. mask |= 1 << (name.charCodeAt(0) - 0x61);
  3936. }
  3937. return mask;
  3938. })();
  3939. /**
  3940. * @param {number} start the name's first byte offset in `_input`
  3941. * @returns {boolean} whether the name could be a math or stepped function's
  3942. */
  3943. const _mayBeMathFunction = (start) => {
  3944. // ASCII-lowercased in place: only a letter lands in `a`-`z` this way, and an
  3945. // escape or a digit starts neither set's names.
  3946. const first = _input.charCodeAt(start) | 0x20;
  3947. if (first < 0x61 || first > 0x7a) return false;
  3948. return (_MATH_NAME_FIRST_LETTERS & (1 << (first - 0x61))) !== 0;
  3949. };
  3950. /**
  3951. * Walk a component-value subtree; children are already materialized. Fetches
  3952. * the node's visitor bucket once (reused for enter + exit) and uses index
  3953. * loops — `for…of` would allocate an iterator per node on this hot path. When
  3954. * `writer` is given, fires this node's printer once its children and visitors are
  3955. * done (post-order); the generic context owns everything the printer then does.
  3956. * @param {Node} node component-value root
  3957. * @param {Node | null} parent enclosing node
  3958. * @param {number} index node's index within its sibling list
  3959. * @param {PrintContext | undefined} writer print context when printing, else undefined
  3960. */
  3961. const _walkValue = (node, parent, index, writer) => {
  3962. const ty = _types[_nodeIndex(node)];
  3963. const b = _visitors[ty];
  3964. let skip = false;
  3965. if (b !== undefined && b.enter.length !== 0) {
  3966. _walkSkip = false;
  3967. _currentNode = node;
  3968. _currentParent = parent;
  3969. _currentIndex = index;
  3970. const e = b.enter;
  3971. for (let i = 0; i < e.length; i++) e[i](A);
  3972. skip = _walkSkip;
  3973. _walkSkip = false;
  3974. }
  3975. // Everything below a math function is a math expression, `(…)` groups
  3976. // included, so the depth rides the recursion the way `_inValue` does — and
  3977. // still counts this node when its own printer runs, which is what joins the
  3978. // children it applies to.
  3979. let enteredMath = false;
  3980. let enteredStepped = false;
  3981. if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
  3982. const i0 = _nodeIndex(node);
  3983. const vs = _listStarts[i0];
  3984. const ve = vs + _listLens[i0];
  3985. // Read off the source before the name is cut out of it: most calls in a
  3986. // stylesheet start with a letter neither set uses, `var()` above all.
  3987. if (ty === T_FUNCTION && _mayBeMathFunction(_starts[i0])) {
  3988. const name = toLowerCaseIfNeeded(_input.slice(_starts[i0], _aux0[i0]));
  3989. if (MATH_FUNCTIONS.has(name)) {
  3990. enteredMath = true;
  3991. _mathFunctionDepth++;
  3992. }
  3993. if (STEPPED_FUNCTIONS.has(name)) {
  3994. enteredStepped = true;
  3995. _steppedFunctionDepth++;
  3996. }
  3997. }
  3998. const previousGradient = _inGradient;
  3999. if (
  4000. ty === T_FUNCTION &&
  4001. // Every gradient name ends in `gradient`, so one code point rejects the
  4002. // rest before the name is sliced.
  4003. (_input.charCodeAt(_aux0[i0] - 1) | 0x20) === CC_LOWER_T &&
  4004. GRADIENT_FUNCTION_RE.test(_gradientName(i0))
  4005. ) {
  4006. _inGradient = true;
  4007. }
  4008. for (let i = vs; i < ve; i++) {
  4009. _walkValue(_nodeRef(_flat[i]), node, i - vs, writer);
  4010. }
  4011. _inGradient = previousGradient;
  4012. }
  4013. _exitNode(node, parent, index, b, writer);
  4014. if (enteredMath) _mathFunctionDepth--;
  4015. if (enteredStepped) _steppedFunctionDepth--;
  4016. };
  4017. // A gradient, whatever it is prefixed with.
  4018. const GRADIENT_FUNCTION_RE = /(?:^|-)(?:linear|radial|conic)-gradient$/i;
  4019. /**
  4020. * A function's name, for the gradient test.
  4021. * @param {number} i0 the function's node index
  4022. * @returns {string} the name as written
  4023. */
  4024. const _gradientName = (i0) => _input.slice(_starts[i0], _aux0[i0]);
  4025. /**
  4026. * Set the flags an at-rule's own name decides, on the way into its prelude:
  4027. * which of them is set is the same question wherever the walk asks it, and the
  4028. * collected and streaming paths would otherwise have to agree by hand. The
  4029. * caller puts them back — each rides its own recursion.
  4030. * @param {number} i0 the at-rule's node index
  4031. * @returns {void}
  4032. */
  4033. const _enterAtRulePrelude = (i0) => {
  4034. const atName = _input.slice(_starts[i0] + 1, _aux0[i0]);
  4035. if (equalsLowerCase(atName, "property")) {
  4036. _inPropertyRule = true;
  4037. } else if (equalsLowerCase(atName, "function")) {
  4038. _inFunctionRule = true;
  4039. } else if (equalsLowerCase(atName, "font-feature-values")) {
  4040. _inFeatureValuesRule = true;
  4041. }
  4042. if (equalsLowerCase(atName, "supports")) {
  4043. _inSupportsPrelude = true;
  4044. }
  4045. // `@media` and `@container` are the two preludes whose `(…)` holds a media
  4046. // feature, the only place the range spelling is a spelling of.
  4047. else if (
  4048. equalsLowerCase(atName, "media") ||
  4049. equalsLowerCase(atName, "container")
  4050. ) {
  4051. _inMediaConditionPrelude = true;
  4052. }
  4053. };
  4054. /**
  4055. * Walk a structural subtree; an at-rule / qualified-rule's block was parsed
  4056. * eagerly (§5.4.4), so its `value` holds the nested rules / declarations. When
  4057. * `writer` is given, fires this node's printer once its children and visitors are
  4058. * done (post-order); the generic context owns everything the printer then does.
  4059. * @param {Node} node structural-tree root
  4060. * @param {Node | null} parent enclosing node
  4061. * @param {number} index node's index within its sibling list (declarations and child rules index independently)
  4062. * @param {PrintContext | undefined} writer print context when printing, else undefined
  4063. */
  4064. const _walkRule = (node, parent, index, writer) => {
  4065. const i0 = _nodeIndex(node);
  4066. const ty = _types[i0];
  4067. const b = _visitors[ty];
  4068. let skip = false;
  4069. if (b !== undefined && b.enter.length !== 0) {
  4070. _walkSkip = false;
  4071. _currentNode = node;
  4072. _currentParent = parent;
  4073. _currentIndex = index;
  4074. const e = b.enter;
  4075. for (let i = 0; i < e.length; i++) e[i](A);
  4076. skip = _walkSkip;
  4077. _walkSkip = false;
  4078. }
  4079. if (!skip) {
  4080. if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
  4081. const ps = _listStarts[i0];
  4082. const pe = ps + _listLens[i0];
  4083. // A `@supports` prelude holds a declaration being *tested*, not applied,
  4084. // so rewriting its value would change what the test asks — the flag rides
  4085. // the recursion, since the conditions nest.
  4086. const prevSupports = _inSupportsPrelude;
  4087. const prevMedia = _inMediaConditionPrelude;
  4088. const prevProperty = _inPropertyRule;
  4089. const prevFunction = _inFunctionRule;
  4090. const prevFeatureValues = _inFeatureValuesRule;
  4091. if (ty === T_AT_RULE) _enterAtRulePrelude(i0);
  4092. for (let i = ps; i < pe; i++) {
  4093. _walkValue(_nodeRef(_flat[i]), node, i - ps, writer);
  4094. }
  4095. _inSupportsPrelude = prevSupports;
  4096. _inMediaConditionPrelude = prevMedia;
  4097. if (_recurseBlocks) {
  4098. // Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
  4099. const bi = _bodyIdx[i0];
  4100. if (bi !== 0) {
  4101. const decls = _declBodies[bi - 1];
  4102. for (let i = 0; i < decls.length; i++) {
  4103. _walkRule(decls[i], node, i, writer);
  4104. }
  4105. const ch = _ruleBodies[bi - 1];
  4106. for (let i = 0; i < ch.length; i++) _walkRule(ch[i], node, i, writer);
  4107. }
  4108. }
  4109. _inPropertyRule = prevProperty;
  4110. _inFunctionRule = prevFunction;
  4111. _inFeatureValuesRule = prevFeatureValues;
  4112. } else if (ty === T_DECLARATION) {
  4113. const vs = _listStarts[i0];
  4114. const ve = vs + _listLens[i0];
  4115. // A declaration's value is a value context (its hashes are colors, not
  4116. // ids); the flag rides the recursion so a hash at any depth (e.g. inside a
  4117. // gradient) knows it, while selector-prelude hashes never see it set.
  4118. const prev = _inValue;
  4119. const prevDeclaration = _valueDeclaration;
  4120. const prevCustom = _inCustomProperty;
  4121. const prevSubstituted = _inSubstitutedValue;
  4122. _inValue = true;
  4123. if (_printing) _valueDeclaration = node;
  4124. // A custom property's value is the one an engine hands back verbatim, so
  4125. // its tokens print squeezed but as written (see `_inCustomProperty`).
  4126. // `@property`'s `initial-value` is typed by its sibling `syntax`, so it
  4127. // is opaque the same way: `0px` there is not the `0` a length accepts.
  4128. // Only the custom property itself is what `rewriteCustomProperties`
  4129. // asks for; those two stay opaque whatever it says.
  4130. if (
  4131. (!_rewriteCustomProperties && _input.startsWith("--", _starts[i0])) ||
  4132. (_inPropertyRule &&
  4133. rangeEqualsLowerCase(
  4134. _input,
  4135. _starts[i0],
  4136. _aux0[i0],
  4137. "initial-value"
  4138. )) ||
  4139. (_inFunctionRule &&
  4140. rangeEqualsLowerCase(_input, _starts[i0], _aux0[i0], "result"))
  4141. ) {
  4142. _inCustomProperty = true;
  4143. }
  4144. // So is a value holding a substitution: the engine keeps it as the token
  4145. // stream it was written as until the substitution resolves, so no rewrite
  4146. // inside one prints the value it would hand back. Read once per
  4147. // declaration, before its children print — a `var()` sibling of the token
  4148. // being rewritten has not been visited yet.
  4149. // Every substitution is a function call, so a value with no `(` in it
  4150. // cannot hold one — checked over the span in place, because slicing it
  4151. // out allocates a string per declaration only to throw it away.
  4152. if (_printing && _hasSubstitutionInSpan(_starts[i0], _ends[i0])) {
  4153. _inSubstitutedValue = true;
  4154. }
  4155. for (let i = vs; i < ve; i++) {
  4156. _walkValue(_nodeRef(_flat[i]), node, i - vs, writer);
  4157. }
  4158. _inValue = prev;
  4159. _valueDeclaration = prevDeclaration;
  4160. _inCustomProperty = prevCustom;
  4161. _inSubstitutedValue = prevSubstituted;
  4162. }
  4163. }
  4164. _exitNode(node, parent, index, b, writer);
  4165. };
  4166. // === Nested-block streaming ===
  4167. // Top-level nodes already stream, but inside one big block nothing could be
  4168. // released until the whole subtree finished. Enter an open rule once its block
  4169. // has grown enough to be worth it, then walk and recycle each later child as it
  4170. // completes: peak storage becomes the open path rather than the block.
  4171. // Printing streams with it: a rule's `prelude{` is held back when the block
  4172. // opens (`_streamOpen`), each finished child is emitted straight after it
  4173. // (`_streamEmitChild`), and the `}` closes it (`_streamClose`) — so the printer
  4174. // never assembles a parent from children a streamed body has already released.
  4175. // The two things that need a whole block at once still hold: a longhand family
  4176. // only merges in a block with no child rule, which is exactly the block this
  4177. // declines to stream, and the last of a set of identical declarations is reached
  4178. // by taking the earlier ones back out of the output rather than by looking ahead.
  4179. // On for every walk, and not an option: a block under the threshold is collected
  4180. // and walked in one batch exactly as it always was, so there is nothing to trade
  4181. // off. It is still a flag because the standalone `parseA*` entry points hand back
  4182. // a materialized tree and must not stream — `_setupParse` leaves it off for them,
  4183. // and only `grammar` turns it on.
  4184. let _streamBlocks = false;
  4185. /** @type {Node | null} node already walked inline, so its sink only recycles */
  4186. let _streamWalked = null;
  4187. // One frame per open block, indexed by depth rather than pushed and popped:
  4188. // nesting is shallow and strictly LIFO, so an indexed store costs a write where
  4189. // parallel stacks cost bounds and capacity checks on every rule. Activation
  4190. // needs to reach every ancestor, which locals could not offer.
  4191. //
  4192. // A frame is written lazily, by `_streamPublishFrame`, and only where an
  4193. // ancestor could still be reached for: just before a child rule — the one thing
  4194. // that can open a block that reaches back up — and on the block's own growth
  4195. // past the threshold. Every other block, which is every leaf rule in a
  4196. // stylesheet, keeps its state in `consumeABlocksContentsInto`'s locals and never
  4197. // touches these at all. A node is an id, so every slot but the two buffers is
  4198. // numeric: typed columns keep a write off the GC's books.
  4199. const _STREAM_MAX_DEPTH = 64;
  4200. let _depth = 0;
  4201. // The deepest frame this parse wrote, so the reset clears what was used rather
  4202. // than all `_STREAM_MAX_DEPTH` slots — nesting is 1 deep in almost every sheet.
  4203. let _frameHighWater = 0;
  4204. /** @type {Int32Array} node id of the open rule */
  4205. const _frameRule = new Int32Array(_STREAM_MAX_DEPTH);
  4206. const _frameMark = new Int32Array(_STREAM_MAX_DEPTH);
  4207. const _frameFlatMark = new Int32Array(_STREAM_MAX_DEPTH);
  4208. const _frameBodyMark = new Int32Array(_STREAM_MAX_DEPTH);
  4209. const _frameActive = new Uint8Array(_STREAM_MAX_DEPTH);
  4210. // Sibling counters, kept apart because the walk indexes declarations and child
  4211. // rules independently (see `_walkRule`) and a streamed block must agree with it.
  4212. const _frameDeclIndex = new Int32Array(_STREAM_MAX_DEPTH);
  4213. const _frameRuleIndex = new Int32Array(_STREAM_MAX_DEPTH);
  4214. // Whether a finished child of this frame is walked at all: `skipChildren()` and
  4215. // `recurseBlocks: false` both decline them, folded into one test per child.
  4216. const _frameWalk = new Uint8Array(_STREAM_MAX_DEPTH);
  4217. // What the block buffered before it activated. `consumeABlocksContentsInto`
  4218. // collects into these very arrays, so a descendant that crosses the threshold
  4219. // first can still drain what its ancestors hold.
  4220. /** @type {(Declaration[] | null)[]} */
  4221. const _frameDecls = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
  4222. /** @type {(Rule[] | null)[]} */
  4223. const _frameRules = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
  4224. const _frameParentIndex = new Int32Array(_STREAM_MAX_DEPTH);
  4225. // The `@property` / `@function` / `@font-feature-values` state this frame was
  4226. // entered under. A streamed block outlives the call that set it, so it is
  4227. // restored when the frame closes.
  4228. const _framePrevProperty = new Uint8Array(_STREAM_MAX_DEPTH);
  4229. const _framePrevFunction = new Uint8Array(_STREAM_MAX_DEPTH);
  4230. const _framePrevFeatureValues = new Uint8Array(_STREAM_MAX_DEPTH);
  4231. // Printing a streamed block: where its first item landed in the output, how deep
  4232. // its opener sits in the writer's pending stack, and the direct declarations it
  4233. // has emitted so far — the last of a set of identical ones is the only one that
  4234. // can be read, and the earlier are taken back as each later one arrives.
  4235. const _frameFirstChunk = new Int32Array(_STREAM_MAX_DEPTH);
  4236. const _framePendingDepth = new Int32Array(_STREAM_MAX_DEPTH);
  4237. /** @type {(Map<string, number> | null)[]} */
  4238. const _frameSeenDeclarations = Array.from(
  4239. { length: _STREAM_MAX_DEPTH },
  4240. () => null
  4241. );
  4242. // The child rules a streamed block has emitted, by their printed text: the last
  4243. // of a set of identical ones is the only one read, and the earlier are taken
  4244. // back as each later one arrives.
  4245. /** @type {(Map<string, number> | null)[]} */
  4246. const _frameSeenRules = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
  4247. // The openers enclosing each streamed depth, joined: what a rule there is read
  4248. // under, so one elsewhere under the same conditions keys the same.
  4249. /** @type {(string | null)[]} */
  4250. const _frameChainKey = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
  4251. /**
  4252. * A rule the printer has written, at `[at, at + len)` of the piece holding it.
  4253. * `key` is the text it printed to and `scope` what encloses it; a later rule
  4254. * with both the same is this one written again. Neither is rewritten as the span
  4255. * travels outward — only `scope` is replaced, by one standing for more — so the
  4256. * collected and the streamed path split a rule the same way, which is what makes
  4257. * the two agree on what a duplicate is.
  4258. * @typedef {{ scope: RuleScope, key: string, at: number, len: number }} RuleSpan
  4259. * @typedef {{ prefix: string, rules: Map<string, { taken: TakenPiece, span: RuleSpan }>, inner: Map<string, RuleScope> }} RuleScope
  4260. * @typedef {{ bodyAt: number, prelude: string, keyPrelude: string, qualified: boolean, spans: RuleSpan[] }} BlockSpans
  4261. */
  4262. // Where each finished block's rules landed in it, youngest last — by print
  4263. // order, since a node id is a slot the next top-level node reuses.
  4264. /** @type {BlockSpans[]} */
  4265. const _blockSpans = [];
  4266. const _NO_RULE_SPANS = /** @type {RuleSpan[]} */ (
  4267. /** @type {unknown} */ (Object.freeze([]))
  4268. );
  4269. const _NO_BLOCK_SPANS = /** @type {BlockSpans[]} */ (
  4270. /** @type {unknown} */ (Object.freeze([]))
  4271. );
  4272. // Stands in for a block node that carries no rules of its own, so a parent
  4273. // still takes exactly one entry off for each of its non-declaration children.
  4274. /** @type {BlockSpans} */
  4275. const _NO_BLOCK_ENTRY = Object.freeze({
  4276. bodyAt: -1,
  4277. prelude: "",
  4278. keyPrelude: "",
  4279. qualified: false,
  4280. spans: _NO_RULE_SPANS
  4281. });
  4282. // The same for a qualified rule, which is still one rule of its own however
  4283. // little it can say about what it nests.
  4284. /** @type {BlockSpans} */
  4285. const _NO_BLOCK_ENTRY_QUALIFIED = Object.freeze({
  4286. bodyAt: -1,
  4287. prelude: "",
  4288. keyPrelude: "",
  4289. qualified: true,
  4290. spans: _NO_RULE_SPANS
  4291. });
  4292. /**
  4293. * @typedef {{ piece: number, text: string, spans: RuleSpan[], empties: number }} TakenPiece
  4294. */
  4295. // Every rule taken so far, sheet-wide, by what encloses it and then by the text
  4296. // it printed to — neither of which changes once written (see `RuleSpan`).
  4297. /** @type {Map<string, RuleScope> | null} */
  4298. let _ruleScopes = null;
  4299. /** @type {RuleScope | null} */
  4300. let _rootRuleScope = null;
  4301. /**
  4302. * The scope for `prefix`, made once and shared — so a chain reached a level at a
  4303. * time and the same one reached in a single step are the one scope.
  4304. * @param {string} prefix what encloses the rules in it, "" at the top level
  4305. * @returns {RuleScope} the scope
  4306. */
  4307. const _ruleScopeFor = (prefix) => {
  4308. let scopes = _ruleScopes;
  4309. if (scopes === null) {
  4310. scopes = new Map();
  4311. _ruleScopes = scopes;
  4312. }
  4313. let scope = scopes.get(prefix);
  4314. if (scope === undefined) {
  4315. scope = { prefix, rules: new Map(), inner: new Map() };
  4316. scopes.set(prefix, scope);
  4317. }
  4318. return scope;
  4319. };
  4320. /**
  4321. * The top-level scope, where a rule nothing encloses lands.
  4322. * @returns {RuleScope} the root scope
  4323. */
  4324. const _rootScope = () => {
  4325. let root = _rootRuleScope;
  4326. if (root === null) {
  4327. root = _ruleScopeFor("");
  4328. _rootRuleScope = root;
  4329. }
  4330. return root;
  4331. };
  4332. /**
  4333. * The scope a scope's rules land in once `head` encloses them. Memoized on the
  4334. * scope, so the join runs once per block rather than once per rule in it.
  4335. * @param {RuleScope} scope the scope the rules are in now
  4336. * @param {string} head what now encloses them
  4337. * @returns {RuleScope} the enclosing scope
  4338. */
  4339. const _enclosingRuleScope = (scope, head) => {
  4340. if (head.length === 0) return scope;
  4341. let inner = scope.inner.get(head);
  4342. if (inner === undefined) {
  4343. inner = _ruleScopeFor(`${head}${scope.prefix}`);
  4344. scope.inner.set(head, inner);
  4345. }
  4346. return inner;
  4347. };
  4348. // CSS Cascade 5 §6.4.1: an `!important` declaration is read from the earliest
  4349. // layer, so a copy in a later `@layer {` makes nothing dead.
  4350. let _anonymousLayers = 0;
  4351. const BARE_AT_RULE_RE = /^@([^\s({;]+)\s*\{$/;
  4352. /**
  4353. * What an opener contributes to the key the rules under it are read by. The
  4354. * name is compared unescaped, since `@l\61yer {` opens one of these too.
  4355. * @param {string} opener the block's prelude, its `{` included
  4356. * @returns {string} the opener, or a token no other occurrence shares
  4357. */
  4358. const _openerKey = (opener) => {
  4359. const bare = BARE_AT_RULE_RE.exec(opener);
  4360. return bare !== null &&
  4361. toLowerCaseIfNeeded(unescapeIdentifier(bare[1])) === "layer"
  4362. ? `\u0000${++_anonymousLayers}@layer{`
  4363. : opener;
  4364. };
  4365. // The named layer blocks a streamed block has emitted, by their opener: a later
  4366. // one of the same name is folded into the piece the first went out as.
  4367. /** @type {(Map<string, SeenLayer> | null)[]} */
  4368. const _frameSeenLayers = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
  4369. /** @type {PrintContext | undefined} the print context while streaming, else undefined */
  4370. let _streamWriter;
  4371. // A block earns the streaming machinery only once it holds enough to be worth
  4372. // releasing. `_nodeCount` restarts at each top-level node, so
  4373. // `_nodeCount - mark` is exactly how far this block has grown; under the
  4374. // threshold `consumeABlocksContentsInto` collects into its own arrays exactly as
  4375. // it does when nothing streams, and the rule is walked once, in one batch.
  4376. // Tuned: a block that crosses only just, near its own `}`, activates for almost
  4377. // nothing back, so the threshold sits above the few-hundred-rule `@media` a
  4378. // stylesheet actually tends to hold. It bounds what is ever buffered, and even
  4379. // so that is a fraction of the block it replaces.
  4380. const _STREAM_MIN_NODES = 16384;
  4381. // `_nodeCount` never reaches this, so a non-streaming block's per-child growth
  4382. // check is one compare that can never fire — no second code path to maintain.
  4383. const _STREAM_NO_LIMIT = 0x7fffffff;
  4384. // How many finished bodies a streamed block lets pile up before handing them
  4385. // back in one go (see `_streamOnNode`).
  4386. const _STREAM_BODY_SLACK = 64;
  4387. /**
  4388. * Make an open block's frame readable, so a descendant that crosses the
  4389. * threshold can enter and drain it on the way in. Called at most once per block,
  4390. * and only by blocks that could still need it.
  4391. * @param {number} d the block's depth
  4392. * @param {Rule | undefined} rule the rule whose block this is
  4393. * @param {number} markNode `_nodeCount` when the block opened
  4394. * @param {number} markFlat `_flatTop` when the block opened
  4395. * @param {Declaration[]} decls the block's declarations so far
  4396. * @param {Rule[] | null} rules the block's child rules so far
  4397. */
  4398. const _streamPublishFrame = (d, rule, markNode, markFlat, decls, rules) => {
  4399. _frameRule[d] = _nodeIndex(/** @type {Rule} */ (rule));
  4400. _frameMark[d] = markNode;
  4401. _frameFlatMark[d] = markFlat;
  4402. _frameActive[d] = 0;
  4403. // The very arrays the block is collecting into, so a descendant can drain what
  4404. // it buffered without it being on the stack.
  4405. _frameDecls[d] = decls;
  4406. _frameRules[d] = rules;
  4407. };
  4408. /**
  4409. * Hold back the opener of a streamed rule — `prelude{`, which its prelude
  4410. * already determines. Held rather than emitted so a block that prints to nothing
  4411. * can still be dropped whole at its `}`.
  4412. * @param {number} d the block's depth
  4413. * @param {Rule} rule the open rule
  4414. */
  4415. const _streamOpen = (d, rule) => {
  4416. const writer = /** @type {PrintContext} */ (_streamWriter);
  4417. const minify = writer.options.mode === "minify";
  4418. // Bind the whole path, not just the node: a prelude can be read in terms of
  4419. // what encloses it — a keyframe selector is `from` only inside `@keyframes` —
  4420. // and `_streamEnterRule` binds the parent only when the rule has an `enter`
  4421. // visitor to run, which printing on its own does not.
  4422. _currentNode = rule;
  4423. _currentParent = d === 0 ? null : _nodeRef(_frameRule[d - 1]);
  4424. _currentIndex = _frameParentIndex[d];
  4425. const opener = `${_rulePrelude(A, writer, minify)}${minify ? "" : " "}{`;
  4426. // A streamed rule writes straight through, so the rule held back for a join
  4427. // lands before its opener — after the opener is read, since taking one clears
  4428. // the store the prelude's own children were printed into.
  4429. _flushTopLevel(writer);
  4430. _framePendingDepth[d] = writer.pushPending(opener);
  4431. _frameSeenDeclarations[d] = null;
  4432. _frameSeenRules[d] = null;
  4433. _frameSeenLayers[d] = null;
  4434. _frameFirstChunk[d] = writer.markCut();
  4435. const above = d === 0 ? "" : _frameChainKey[d - 1];
  4436. _frameChainKey[d] =
  4437. !minify || above === null ? null : `${above}${_openerKey(opener)}`;
  4438. // The outermost opener carries the whole run's source anchor: it is the one
  4439. // the kept comments precede and the mapping points at.
  4440. if (d === 0) {
  4441. const i0 = _nodeIndex(rule);
  4442. const start = _starts[i0];
  4443. const loc = _locConverter.get(start);
  4444. writer.anchorPending(start, loc.line - 1, loc.column);
  4445. }
  4446. };
  4447. /**
  4448. * Emit one finished child of a streamed block, and with it every opener still
  4449. * held back — something inside them printed, so they are not empty after all.
  4450. * @param {number} d the block's depth
  4451. * @param {Node} child the finished child
  4452. */
  4453. const _streamEmitChild = (d, child) => {
  4454. const writer = /** @type {PrintContext} */ (_streamWriter);
  4455. const text = writer.get(child);
  4456. // A declaration the minifier dropped prints to nothing, and an empty rule to
  4457. // nothing as well: neither makes the block it sits in non-empty.
  4458. if (text.length === 0) return;
  4459. writer.flushPending();
  4460. const minify = writer.options.mode === "minify";
  4461. // Taking a dead rule or declaration back and folding two named layer blocks
  4462. // into one are two options, so each gather below runs only where its own
  4463. // is on.
  4464. const dropping = minify && _transforms.removeDeadRules;
  4465. const merging = minify && _transforms.mergeRules;
  4466. if (!minify || _nodeTypeOf(child) !== T_DECLARATION) {
  4467. const body = minify ? text : `\n${text}`;
  4468. // Drained here and nowhere else: every path out of this branch has to take
  4469. // the child's entry with it, or the next child reads it as its own.
  4470. const own = _drainTopLevelSpans(child);
  4471. // The one child rule that can be taken back: a prefixed one an unprefixed
  4472. // twin later in this block would make dead weight. Its parent never
  4473. // assembles a body here, so the piece is what the twin drops.
  4474. const candidate = _prefixDropCandidate;
  4475. if (candidate !== null && candidate.node === child) {
  4476. _prefixDropCandidate = null;
  4477. const scope = _prefixScope(_nodeRef(_frameRule[d]));
  4478. const pending = scope.pending;
  4479. if (pending !== null && pending.delete(candidate.signature)) {
  4480. if (scope.retractable === null) scope.retractable = new Map();
  4481. scope.retractable.set(
  4482. candidate.signature,
  4483. writer.emitRetractable(body)
  4484. );
  4485. return;
  4486. }
  4487. }
  4488. // A named layer block is one layer however far its siblings stand apart, so
  4489. // a repeat of one folds into the first — `_mergeNamedLayerBlocks` is the
  4490. // same gather for a block whose body is assembled in one go. A sibling
  4491. // naming a layer any other way writes into one of these, and the order
  4492. // within a layer is the cascade's, so nothing folds back over it.
  4493. const positional = merging && POSITIONAL_AT_RULE_RE.test(text);
  4494. if (positional) {
  4495. const opener = _namedLayerOpener(text);
  4496. if (opener === null) {
  4497. if (_frameSeenLayers[d] !== null) {
  4498. /** @type {Map<string, SeenLayer>} */ (_frameSeenLayers[d]).clear();
  4499. }
  4500. } else {
  4501. let layers = _frameSeenLayers[d];
  4502. if (layers === null) {
  4503. layers = new Map();
  4504. _frameSeenLayers[d] = layers;
  4505. }
  4506. const first = layers.get(opener);
  4507. const deep = _opensNestedLayer(text, opener);
  4508. _noteLayerBlock(
  4509. layers,
  4510. opener.slice(NAMED_LAYER_OPENER_HEAD, -1).trim(),
  4511. deep
  4512. );
  4513. const chain = _frameChainKey[d];
  4514. const inner = chain === null ? null : _topLevelSpans(own, text);
  4515. if (first !== undefined && !(deep && first.subtree)) {
  4516. const grown = first.taken;
  4517. if (grown === undefined || inner === null || chain === null) {
  4518. writer.foldIntoRetractable(first.at, text.slice(opener.length));
  4519. return;
  4520. }
  4521. // The body lands in front of the piece's own `}`, past everything
  4522. // already keyed in it, so only what arrives now needs moving.
  4523. const base = grown.text.length - 1 - opener.length;
  4524. grown.text = `${grown.text.slice(0, -1)}${text.slice(opener.length)}`;
  4525. writer.rewriteRetractable(grown.piece, grown.text);
  4526. for (const span of inner) span.at += base;
  4527. _noteSpans(writer, grown, inner, chain);
  4528. return;
  4529. }
  4530. const at = writer.emitRetractable(body);
  4531. layers.set(opener, {
  4532. at,
  4533. subtree: false,
  4534. taken:
  4535. inner === null || chain === null
  4536. ? undefined
  4537. : _takeDeduped(writer, at, body, inner, own, chain)
  4538. });
  4539. return;
  4540. }
  4541. }
  4542. // The same rule twice in a block is read once: the later writes the same
  4543. // declarations to the same elements and wins the tie, so it takes the
  4544. // earlier back — as each identical declaration does below.
  4545. if (dropping && !positional) {
  4546. const chain = _frameChainKey[d];
  4547. const piece = writer.emitRetractable(body);
  4548. if (chain !== null) {
  4549. // Sheet-wide, keyed on what the rule is read under: the same map the
  4550. // collected path registers into, so the two agree across a sheet that
  4551. // takes both.
  4552. const spans = _topLevelSpans(own, text);
  4553. if (spans !== null) {
  4554. _takeDeduped(writer, piece, text, spans, own, chain);
  4555. return;
  4556. }
  4557. }
  4558. let rules = _frameSeenRules[d];
  4559. if (rules === null) {
  4560. rules = new Map();
  4561. _frameSeenRules[d] = rules;
  4562. }
  4563. const previous = rules.get(text);
  4564. if (previous !== undefined) writer.retract(previous);
  4565. rules.set(text, piece);
  4566. return;
  4567. }
  4568. writer._emit(body);
  4569. return;
  4570. }
  4571. // Only the last of a set of identical declarations can be read, so each one
  4572. // takes back the one it repeats — which is the only thing here that needs a
  4573. // piece of its own. Keyed on the printed text, as the collected printer keys
  4574. // its own pass.
  4575. if (!dropping) {
  4576. writer._emit(text);
  4577. return;
  4578. }
  4579. const at = writer.emitRetractable(text);
  4580. let seen = _frameSeenDeclarations[d];
  4581. if (seen === null) {
  4582. seen = new Map();
  4583. _frameSeenDeclarations[d] = seen;
  4584. }
  4585. const previous = seen.get(text);
  4586. if (previous !== undefined) writer.retract(previous);
  4587. seen.set(text, at);
  4588. };
  4589. /**
  4590. * Close a streamed rule: emit its `}`, or drop the whole rule when its block
  4591. * printed to nothing and the block itself carries no meaning.
  4592. * @param {number} d the block's depth
  4593. * @param {Rule} rule the rule being closed
  4594. */
  4595. const _streamClose = (d, rule) => {
  4596. const writer = /** @type {PrintContext} */ (_streamWriter);
  4597. const minify = writer.options.mode === "minify";
  4598. _frameSeenDeclarations[d] = null;
  4599. _frameSeenRules[d] = null;
  4600. _frameSeenLayers[d] = null;
  4601. if (writer.isPending(_framePendingDepth[d])) {
  4602. // Nothing inside printed. An empty rule paints nothing, so dropping it
  4603. // leaves the cascade as it was — but only where the block itself carries no
  4604. // meaning (see `DROPPABLE_WHEN_EMPTY_AT_RULES`).
  4605. const i0 = _nodeIndex(rule);
  4606. if (
  4607. minify &&
  4608. _transforms.removeDeadRules &&
  4609. // Not while a `@namespace` after it could still be read: taking this
  4610. // rule out would move one up to where the engine honours it.
  4611. !(d === 0 && _namespacePrologueOpen) &&
  4612. (_types[i0] === T_QUALIFIED_RULE ||
  4613. DROPPABLE_WHEN_EMPTY_AT_RULES.has(
  4614. _input.slice(_starts[i0] + 1, _aux0[i0]).toLowerCase()
  4615. ))
  4616. ) {
  4617. writer.dropPending();
  4618. return;
  4619. }
  4620. writer.flushPending();
  4621. } else if (minify) {
  4622. // The `;` the last declaration carries is the one a `}` makes redundant.
  4623. writer.dropTrailing(_frameFirstChunk[d], CC_SEMICOLON);
  4624. }
  4625. writer._emit(minify ? "}" : "\n}");
  4626. };
  4627. /**
  4628. * Fire an open rule's `enter` and walk its prelude — both are final at `{`.
  4629. * @param {Rule} rule the open rule
  4630. * @param {Node | null} parent enclosing rule
  4631. * @param {number} index the rule's index among its siblings
  4632. * @param {number} d the block's frame depth
  4633. * @returns {boolean} whether `skipChildren()` declined its children
  4634. */
  4635. const _streamEnterRule = (rule, parent, index, d) => {
  4636. const i0 = _nodeIndex(rule);
  4637. const ty = _types[i0];
  4638. const b = _visitors[ty];
  4639. if (b !== undefined && b.enter.length !== 0) {
  4640. _walkSkip = false;
  4641. _currentNode = rule;
  4642. _currentParent = parent;
  4643. _currentIndex = index;
  4644. const e = b.enter;
  4645. for (let i = 0; i < e.length; i++) e[i](A);
  4646. const skip = _walkSkip;
  4647. _walkSkip = false;
  4648. if (skip) return true;
  4649. }
  4650. const ps = _listStarts[i0];
  4651. const pe = ps + _listLens[i0];
  4652. const prevSupports = _inSupportsPrelude;
  4653. const prevMedia = _inMediaConditionPrelude;
  4654. // Body-scoped, unlike the two prelude flags: the block streams past this call,
  4655. // so `_streamConsumeBlock` puts them back once its `}` is reached.
  4656. _framePrevProperty[d] = _inPropertyRule ? 1 : 0;
  4657. _framePrevFunction[d] = _inFunctionRule ? 1 : 0;
  4658. _framePrevFeatureValues[d] = _inFeatureValuesRule ? 1 : 0;
  4659. if (ty === T_AT_RULE) _enterAtRulePrelude(i0);
  4660. for (let i = ps; i < pe; i++) {
  4661. _walkValue(_nodeRef(_flat[i]), rule, i - ps, _streamWriter);
  4662. }
  4663. _inSupportsPrelude = prevSupports;
  4664. _inMediaConditionPrelude = prevMedia;
  4665. return false;
  4666. };
  4667. /**
  4668. * A block at `d` has grown past the threshold. Every ancestor still buffering
  4669. * must be entered first, outermost in, so `enter` stays in source order; each
  4670. * one then walks what it buffered (its two lists are each source-ordered, so
  4671. * merging on start offset restores source order) before the deeper level does.
  4672. * @param {number} d depth of the block that crossed the threshold
  4673. */
  4674. const _streamActivate = (d) => {
  4675. for (let k = 0; k <= d; k++) {
  4676. if (_frameActive[k] === 1) continue;
  4677. _frameActive[k] = 1;
  4678. const rule = /** @type {Rule} */ (_nodeRef(_frameRule[k]));
  4679. // Claim the body slot before `enter` can read it: a streamed rule hands its
  4680. // children to the visitors, so its own block has to report as empty rather
  4681. // than as `null`, which is how a rule with no block at all reads. Everything
  4682. // a later child claims sits above this, so the slots come back per child.
  4683. _setBody(rule, _EMPTY_LIST, _EMPTY_LIST);
  4684. _frameBodyMark[k] = _declBodies.length;
  4685. // The enclosing block has just been activated and drained, so its rule
  4686. // counter is exactly how many siblings precede this one.
  4687. const parentIndex = k === 0 ? 0 : _frameRuleIndex[k - 1]++;
  4688. _frameParentIndex[k] = parentIndex;
  4689. const skipped = _streamEnterRule(
  4690. rule,
  4691. k === 0 ? null : _nodeRef(_frameRule[k - 1]),
  4692. parentIndex,
  4693. k
  4694. );
  4695. if (_streamWriter !== undefined) _streamOpen(k, rule);
  4696. const walk = !skipped && _recurseBlocks;
  4697. _frameWalk[k] = walk ? 1 : 0;
  4698. const decls = _frameDecls[k];
  4699. const rules = _frameRules[k];
  4700. _frameDecls[k] = null;
  4701. _frameRules[k] = null;
  4702. const dn = decls === null ? 0 : decls.length;
  4703. const rn = rules === null ? 0 : rules.length;
  4704. // Both counters end at what was buffered, so the children still to come
  4705. // carry on from there whether or not this block's body was walked.
  4706. _frameDeclIndex[k] = dn;
  4707. _frameRuleIndex[k] = rn;
  4708. if (!walk) continue;
  4709. // Merging the two source-ordered lists on start offset restores source
  4710. // order, while each child keeps the sibling index the batch walk gives it.
  4711. let di = 0;
  4712. let ri = 0;
  4713. while (di < dn || ri < rn) {
  4714. /** @type {Node} */
  4715. let child;
  4716. if (
  4717. ri >= rn ||
  4718. (di < dn &&
  4719. _starts[_nodeIndex(/** @type {Declaration[]} */ (decls)[di])] <
  4720. _starts[_nodeIndex(/** @type {Rule[]} */ (rules)[ri])])
  4721. ) {
  4722. child = /** @type {Declaration[]} */ (decls)[di];
  4723. _walkRule(child, rule, di, _streamWriter);
  4724. di++;
  4725. } else {
  4726. child = /** @type {Rule[]} */ (rules)[ri];
  4727. _walkRule(child, rule, ri, _streamWriter);
  4728. ri++;
  4729. }
  4730. if (_streamWriter !== undefined) {
  4731. _streamEmitChild(k, child);
  4732. _streamWriter.dropStore();
  4733. }
  4734. }
  4735. }
  4736. // Only the deepest block may release: an ancestor's mark sits below the ids
  4737. // its still-open descendants are using.
  4738. if (_nodeCount > _peak) _peak = _nodeCount;
  4739. if (_flatTop > _flatPeak) _flatPeak = _flatTop;
  4740. _nodeCount = _frameMark[d];
  4741. _flatTop = _frameFlatMark[d];
  4742. };
  4743. /**
  4744. * Per-child sink of an *activated* block: walk the finished child, then release
  4745. * every id it used. Module-level (no per-rule closure) — the depth-indexed frame
  4746. * carries the open rule, its sibling counters and its mark. A block only reaches
  4747. * for this once it has activated, so a small block never calls it.
  4748. * @param {Rule | Declaration} node the finished child
  4749. */
  4750. const _streamOnNode = (node) => {
  4751. const d = _depth - 1;
  4752. // A child rule that streamed on its own already entered, walked and exited
  4753. // inline and released its ids; it reaches the sink already finished.
  4754. if (node === _streamWalked) {
  4755. // Already entered, printed and closed itself inline, straight into the
  4756. // output — there is nothing left of it to emit here.
  4757. _streamWalked = null;
  4758. } else if (_frameWalk[d] === 1) {
  4759. const index =
  4760. _nodeTypeOf(node) === T_DECLARATION
  4761. ? _frameDeclIndex[d]++
  4762. : _frameRuleIndex[d]++;
  4763. _walkRule(node, _nodeRef(_frameRule[d]), index, _streamWriter);
  4764. if (_streamWriter !== undefined) {
  4765. _streamEmitChild(d, node);
  4766. _streamWriter.dropStore();
  4767. }
  4768. }
  4769. if (_nodeCount > _peak) _peak = _nodeCount;
  4770. if (_flatTop > _flatPeak) _flatPeak = _flatTop;
  4771. _nodeCount = _frameMark[d];
  4772. _flatTop = _frameFlatMark[d];
  4773. // The child is walked, so the body slots it and its subtree claimed go back
  4774. // too — otherwise the one thing a streamed block still grew per child. Handed
  4775. // back in batches: shortening the two arrays costs more than the compare, and
  4776. // what a batch holds onto is a few dozen finished bodies.
  4777. const bodyMark = _frameBodyMark[d];
  4778. if (_declBodies.length >= bodyMark + _STREAM_BODY_SLACK) {
  4779. _declBodies.length = bodyMark;
  4780. _ruleBodies.length = bodyMark;
  4781. }
  4782. };
  4783. /**
  4784. * Consume a rule's block, streaming its children once the block grows past
  4785. * `_STREAM_MIN_NODES`. A block that stays small is handed back materialized for
  4786. * the ordinary walk, so it costs a collect and nothing else.
  4787. * @param {TokenStream} ts token stream
  4788. * @param {Rule} rule the rule whose block follows
  4789. */
  4790. const _streamConsumeBlock = (ts, rule) => {
  4791. const d = _depth;
  4792. const blockStart = ts.next().start;
  4793. ts.discard();
  4794. if (d >= _STREAM_MAX_DEPTH) {
  4795. // Deeper than the frame table: fall back to the materializing path.
  4796. consumeABlocksContentsInto(ts);
  4797. const decls = _bcDecls;
  4798. const rules = _bcRules;
  4799. const c = ts.next();
  4800. ts.discard();
  4801. _setBody(rule, decls, rules);
  4802. _setBlock(rule, blockStart);
  4803. _setEnd(rule, c.type === TT_RIGHT_CURLY_BRACKET ? c.end : c.start);
  4804. return;
  4805. }
  4806. // Nothing is written to the frame here: the block keeps its state in locals
  4807. // and writes one only if it could still be read (see `_streamPublishFrame`),
  4808. // so a rule whose block is all declarations costs what it always did.
  4809. _depth = d + 1;
  4810. if (_depth > _frameHighWater) _frameHighWater = _depth;
  4811. consumeABlocksContentsInto(ts, undefined, d, rule);
  4812. _depth = d;
  4813. const streamed = _bcStreamed;
  4814. const decls = _bcDecls;
  4815. const rules = _bcRules;
  4816. const close = ts.next();
  4817. const end = close.type === TT_RIGHT_CURLY_BRACKET ? close.end : close.start;
  4818. ts.discard();
  4819. _setBlock(rule, blockStart);
  4820. _setEnd(rule, end);
  4821. if (!streamed) {
  4822. // Never grew: hand the body back, so the ordinary walk visits it and
  4823. // `A.declarations` / `A.childRules` still read it.
  4824. _setBody(rule, decls, rules);
  4825. return;
  4826. }
  4827. // Streamed: `_streamActivate` already claimed the body slot, empty. The exit
  4828. // visitors still run here; the rule's own `}` follows them, where the printer
  4829. // would have run for a rule printed in one piece.
  4830. _exitNode(
  4831. rule,
  4832. d === 0 ? null : _nodeRef(_frameRule[d - 1]),
  4833. _frameParentIndex[d],
  4834. _visitors[_types[_nodeIndex(rule)]],
  4835. undefined
  4836. );
  4837. if (_streamWriter !== undefined) _streamClose(d, rule);
  4838. _inPropertyRule = _framePrevProperty[d] === 1;
  4839. _inFunctionRule = _framePrevFunction[d] === 1;
  4840. _inFeatureValuesRule = _framePrevFeatureValues[d] === 1;
  4841. _streamWalked = rule;
  4842. };
  4843. /** @typedef {{ node: Node, offset: number, line: number | undefined, column: number | undefined, entry: RuleEntry, owned: boolean, own: BlockSpans | undefined }} HeldTopLevel one top-level rule held back for a join, with where it stood and what it recorded */
  4844. // The one top-level rule held back, so the next can be joined onto it (see
  4845. // `_mergeAdjacentRules` — the same merge, across nodes the writer takes one at a
  4846. // time). Null when nothing is held.
  4847. /** @type {HeldTopLevel | null} */
  4848. let _heldTopLevel = null;
  4849. // The named layer blocks the stylesheet's own children have emitted, by their
  4850. // opener: a later one of the same name is folded into the piece the first went
  4851. // out as, as `_frameSeenLayers` does inside a block.
  4852. /** @type {Map<string, SeenLayer> | null} */
  4853. let _seenTopLevelLayers = null;
  4854. const AT_RULE_NAME_RE = /^@([^\s({;]+)/;
  4855. /**
  4856. * The name an at-rule prelude opens with, lowercased.
  4857. * @param {string} prelude the prelude, `@` included
  4858. * @returns {string} the name, or "" when it names none
  4859. */
  4860. const _atRuleName = (prelude) => {
  4861. const match = AT_RULE_NAME_RE.exec(prelude);
  4862. return match === null ? "" : match[1].toLowerCase();
  4863. };
  4864. // Stands for a top-level node that is one rule whatever its text ends up being,
  4865. // a qualified rule joined onto the one beside it included.
  4866. /** @type {BlockSpans} */
  4867. const _WHOLE_TEXT_SPANS = Object.freeze({
  4868. bodyAt: 0,
  4869. prelude: "",
  4870. keyPrelude: "",
  4871. qualified: true,
  4872. spans: _NO_RULE_SPANS
  4873. });
  4874. /**
  4875. * What a finished top-level node recorded while it printed. A node id is a slot
  4876. * the next top-level node reuses, so this reads at the one moment the node still
  4877. * names itself — the writer holds a rule back a node, and by then it does not.
  4878. * @param {Node} node the top-level node, freshly printed
  4879. * @returns {BlockSpans | undefined} its entry, or undefined when it records none
  4880. */
  4881. const _drainTopLevelSpans = (node) => {
  4882. // Its children's entries were spliced off as their parent assembled its body,
  4883. // so what is left is this node's own.
  4884. // A block a streamed parent emitted leaves its entry behind — nothing spliced
  4885. // it off — so only a lone entry is this node's own.
  4886. const own = _blockSpans.length === 1 ? _blockSpans[0] : undefined;
  4887. _blockSpans.length = 0;
  4888. if (own !== undefined) return own;
  4889. return _nodeTypeOf(node) === T_QUALIFIED_RULE ? _WHOLE_TEXT_SPANS : undefined;
  4890. };
  4891. /**
  4892. * The rules a finished top-level node carries, where they land in its text. A
  4893. * qualified rule is one rule however it nests; an at-rule states conditions its
  4894. * body is read under, so its spans come out with its prelude on their keys.
  4895. * @param {BlockSpans | undefined} own what it recorded, from {@link _drainTopLevelSpans}
  4896. * @param {string} text its printed text
  4897. * @returns {RuleSpan[] | null} the spans, or null when it carries no rule
  4898. */
  4899. const _topLevelSpans = (own, text) => {
  4900. if (own === undefined) return null;
  4901. if (own === _WHOLE_TEXT_SPANS) {
  4902. return [{ scope: _rootScope(), key: text, at: 0, len: text.length }];
  4903. }
  4904. /** @type {RuleSpan[]} */
  4905. const out = own.qualified
  4906. ? [{ scope: _rootScope(), key: text, at: 0, len: text.length }]
  4907. : [];
  4908. for (const span of own.spans) {
  4909. out.push({
  4910. scope: _enclosingRuleScope(span.scope, own.keyPrelude),
  4911. key: span.key,
  4912. at: own.bodyAt + span.at,
  4913. len: span.len
  4914. });
  4915. }
  4916. return out;
  4917. };
  4918. /**
  4919. * Take a top-level node, and with it every rule an identical later one has yet
  4920. * to make dead. A rule already taken whose key comes round again is read for
  4921. * nothing, so the piece holding it is written again without it — pieces are
  4922. * joined only when the sheet ends, so one taken long ago is still reachable.
  4923. * @param {PrintContext} writer the print context
  4924. * @param {number} piece the piece the node was taken as
  4925. * @param {string} text the node's printed text
  4926. * @param {RuleSpan[]} spans where its rules land in it
  4927. * @param {BlockSpans | undefined} own what the node recorded while printing
  4928. * @param {string} chain what encloses it, when it is not a top-level node
  4929. * @returns {TakenPiece} the piece, for a fold that grows it
  4930. */
  4931. const _takeDeduped = (writer, piece, text, spans, own, chain) => {
  4932. // A block the cuts empty is one written empty here, which the printer drops —
  4933. // but only where the block itself carries no meaning.
  4934. const empties =
  4935. own !== undefined &&
  4936. own !== _WHOLE_TEXT_SPANS &&
  4937. _transforms.removeDeadRules &&
  4938. (own.qualified ||
  4939. (own.prelude.charCodeAt(0) === CC_AT_SIGN &&
  4940. DROPPABLE_WHEN_EMPTY_AT_RULES.has(_atRuleName(own.prelude))))
  4941. ? own.bodyAt
  4942. : -1;
  4943. /** @type {TakenPiece} */
  4944. const taken = { piece, text, spans: [], empties };
  4945. _noteSpans(writer, taken, spans, chain);
  4946. return taken;
  4947. };
  4948. /**
  4949. * Note the rules a piece carries, taking back the piece each identical earlier
  4950. * one went out as. Called again for the same piece as a fold grows it.
  4951. * @param {PrintContext} writer the print context
  4952. * @param {TakenPiece} taken the piece they landed in
  4953. * @param {RuleSpan[]} spans where they land in it
  4954. * @param {string} chain what encloses them, "" at the top level
  4955. * @returns {void}
  4956. */
  4957. const _noteSpans = (writer, taken, spans, chain) => {
  4958. // Every span joins the piece before any cutting: a cut here moves what
  4959. // `taken.spans` holds, and one not yet pushed would keep its old offset.
  4960. for (const span of spans) {
  4961. if (chain.length !== 0) span.scope = _enclosingRuleScope(span.scope, chain);
  4962. taken.spans.push(span);
  4963. }
  4964. for (const span of spans) {
  4965. const rules = span.scope.rules;
  4966. const before = rules.get(span.key);
  4967. if (before !== undefined) _cutSpan(writer, before.taken, before.span);
  4968. rules.set(span.key, { taken, span });
  4969. }
  4970. };
  4971. /**
  4972. * Write a piece again without one rule, and move the spans after it back by
  4973. * what went, so a later cut in the same piece still names its own text.
  4974. * @param {PrintContext} writer the print context
  4975. * @param {TakenPiece} taken the piece the rule was taken in
  4976. * @param {RuleSpan} span the rule to cut
  4977. * @returns {void}
  4978. */
  4979. const _cutSpan = (writer, taken, span) => {
  4980. if (span.len === 0) return;
  4981. let from = span.at;
  4982. let len = span.len;
  4983. // The rule stood last in its block, so the `;` in front of it is one the
  4984. // printer drops itself — it goes with the rule rather than being left behind.
  4985. if (
  4986. taken.text.charCodeAt(from + len) === CC_RIGHT_CURLY &&
  4987. taken.text.charCodeAt(from - 1) === CC_SEMICOLON
  4988. ) {
  4989. from -= 1;
  4990. len += 1;
  4991. }
  4992. taken.text = `${taken.text.slice(0, from)}${taken.text.slice(from + len)}`;
  4993. if (taken.empties !== -1 && taken.text.length === taken.empties + 1) {
  4994. taken.text = "";
  4995. }
  4996. writer.rewriteRetractable(taken.piece, taken.text);
  4997. const end = from + len;
  4998. for (const other of taken.spans) {
  4999. if (other === span) continue;
  5000. // One inside what went is gone with it. One that held it still describes
  5001. // what is left of it, which a later copy of it restates in full, so it
  5002. // only shrinks by what went.
  5003. if (other.at >= from && other.at + other.len <= end) other.len = 0;
  5004. else if (other.at <= from && other.at + other.len >= end) other.len -= len;
  5005. else if (other.at > from) other.at -= len;
  5006. }
  5007. span.len = 0;
  5008. };
  5009. /**
  5010. * Hand one finished top-level node to the writer, gathering a named `@layer`
  5011. * block into the first sibling of its name — they are one layer however far
  5012. * apart they stand, and what separates them is in another layer or in none,
  5013. * ordered against these by the cascade rather than by where it sits, so moving
  5014. * the later body up is not a move the cascade can see. `_mergeNamedLayerBlocks`
  5015. * is the same gather one block down. The node goes out with whatever text it
  5016. * ended up carrying, so a block a join already grew is the one gathered into.
  5017. * @param {PrintContext} writer the print context
  5018. * @param {Node} node the top-level node
  5019. * @param {number} offset the node's source offset
  5020. * @param {number | undefined} line 0-based source line of the node's start
  5021. * @param {number | undefined} column 0-based source column of the node's start
  5022. * @param {string} text the text the node goes out as
  5023. * @param {BlockSpans=} own what it recorded while printing
  5024. * @returns {void}
  5025. */
  5026. const _emitTopLevel = (writer, node, offset, line, column, text, own) => {
  5027. const printing = writer.options.mode === "minify";
  5028. // Folding two named layer blocks into one, and taking back a rule an
  5029. // identical later one makes dead, each answer to their own option.
  5030. const minify = printing && _transforms.removeDeadRules;
  5031. const opener =
  5032. printing && _transforms.mergeRules ? _namedLayerOpener(text) : null;
  5033. // Nothing folds back over a kept comment — it was written above what follows
  5034. // it — nor over a sibling naming a layer any other way, since it writes into
  5035. // one of these and the order within a layer is the cascade's.
  5036. if (
  5037. _seenTopLevelLayers !== null &&
  5038. (writer.hasInsertBefore(offset) ||
  5039. (opener === null && minify && POSITIONAL_AT_RULE_RE.test(text)))
  5040. ) {
  5041. _seenTopLevelLayers.clear();
  5042. }
  5043. if (opener === null) {
  5044. // Taking back a rule an identical later one makes dead is a rewrite, which
  5045. // is minifying's to make: beautifying hands back every rule it was given.
  5046. const spans = minify ? _topLevelSpans(own, text) : null;
  5047. if (spans === null) {
  5048. writer.take(node, offset, line, column, text);
  5049. return;
  5050. }
  5051. _takeDeduped(
  5052. writer,
  5053. writer.takeRetractable(node, offset, line, column, text),
  5054. text,
  5055. spans,
  5056. own,
  5057. ""
  5058. );
  5059. return;
  5060. }
  5061. if (_seenTopLevelLayers === null) _seenTopLevelLayers = new Map();
  5062. const first = _seenTopLevelLayers.get(opener);
  5063. const deep = _opensNestedLayer(text, opener);
  5064. _noteLayerBlock(
  5065. _seenTopLevelLayers,
  5066. opener.slice(NAMED_LAYER_OPENER_HEAD, -1).trim(),
  5067. deep
  5068. );
  5069. if (first === undefined || (deep && first.subtree)) {
  5070. _seenTopLevelLayers.set(opener, {
  5071. at: writer.takeRetractable(node, offset, line, column, text),
  5072. subtree: false
  5073. });
  5074. return;
  5075. }
  5076. // Both bodies keep their order, so the layer reads as it was written.
  5077. writer.foldIntoRetractable(first.at, text.slice(opener.length));
  5078. };
  5079. /**
  5080. * Emit the rule held back for a join, as it stands. Its own text is what a
  5081. * grown join left it with, and what it recorded while printing goes with it.
  5082. * @param {PrintContext} writer the print context
  5083. * @param {HeldTopLevel} held the held rule
  5084. * @returns {void}
  5085. */
  5086. const _emitHeld = (writer, held) => {
  5087. _emitTopLevel(
  5088. writer,
  5089. held.node,
  5090. held.offset,
  5091. held.line,
  5092. held.column,
  5093. held.entry.text,
  5094. held.own
  5095. );
  5096. };
  5097. /**
  5098. * Hand a finished top-level node to the writer, holding a qualified rule back
  5099. * one node so an adjacent one printing the same block can join its selectors.
  5100. * @param {Node} node the top-level node
  5101. * @param {PrintContext} writer the print context
  5102. * @param {number} offset the node's source offset
  5103. * @param {number=} line 0-based source line of the node's start, when mapping
  5104. * @param {number=} column 0-based source column of the node's start, when mapping
  5105. * @returns {void}
  5106. */
  5107. const _takeTopLevel = (node, writer, offset, line, column) => {
  5108. const held = _heldTopLevel;
  5109. // The node's own text first — taking the held one clears the store it sits in.
  5110. const own = writer.get(node);
  5111. // Read where the node stands, not where it is emitted: the writer holds a
  5112. // rule back one node, by when the prologue has closed behind it. A rule taken
  5113. // back from in front of a `@namespace` would move one up into a live
  5114. // position, so one standing there is never offered.
  5115. const drained = _drainTopLevelSpans(node);
  5116. const spans = _namespacePrologueOpen ? undefined : drained;
  5117. const entry = _ruleEntryOf(node, own);
  5118. // A prefixed rule an unprefixed twin would make dead weight goes out as a piece
  5119. // of its own, so the twin can take it back from wherever it stands rather than
  5120. // only from the next rule. It is not offered for joining first: a rule that may
  5121. // still be taken back must be one piece and one rule.
  5122. const candidate = _prefixDropCandidate;
  5123. _prefixDropCandidate = null;
  5124. if (candidate !== null && candidate.node === node) {
  5125. if (held !== null) {
  5126. _heldTopLevel = null;
  5127. _emitHeld(writer, held);
  5128. }
  5129. const at = writer.takeRetractable(node, offset, line, column, own);
  5130. const scope = _prefixScope(null);
  5131. if (scope.retractable === null) scope.retractable = new Map();
  5132. scope.retractable.set(candidate.signature, at);
  5133. return;
  5134. }
  5135. if (entry.prelude === -1) {
  5136. // Not a rule this can join: flush what is held, then take it as it stands.
  5137. // A node printing nothing leaves the two around it adjacent in the output.
  5138. // Its kept comments stay queued: they land where it stood, which is after
  5139. // the held rule, and until then they block a join across that gap.
  5140. if (own.length === 0) return;
  5141. if (held !== null) {
  5142. _heldTopLevel = null;
  5143. _emitHeld(writer, held);
  5144. }
  5145. _emitTopLevel(writer, node, offset, line, column, own, spans);
  5146. return;
  5147. }
  5148. // A kept comment between the two was written above the second rule.
  5149. if (held !== null && !writer.hasInsertBefore(offset)) {
  5150. const merged = _joinRuleEntries(held.entry, entry, held.owned);
  5151. if (merged !== null) {
  5152. held.entry = merged;
  5153. held.owned = true;
  5154. // The join rewrote its text, so what it recorded no longer names its
  5155. // own offsets — it is one rule, and only that.
  5156. held.own = _WHOLE_TEXT_SPANS;
  5157. return;
  5158. }
  5159. }
  5160. if (held !== null) {
  5161. _emitHeld(writer, held);
  5162. }
  5163. // What was written above this rule is emitted above it now, so the next
  5164. // node's check sees only the gap between the two — and nothing folds back
  5165. // over it, so the layer blocks above it stop being ones to gather into.
  5166. if (_seenTopLevelLayers !== null && writer.hasInsertBefore(offset)) {
  5167. _seenTopLevelLayers.clear();
  5168. }
  5169. writer.flushInsertsBefore(offset);
  5170. _heldTopLevel = {
  5171. node,
  5172. offset,
  5173. line,
  5174. column,
  5175. entry,
  5176. owned: false,
  5177. own: spans
  5178. };
  5179. };
  5180. /**
  5181. * Emit the held top-level rule, if any. Called once the stylesheet is consumed.
  5182. * @param {PrintContext=} writer the print context
  5183. * @returns {void}
  5184. */
  5185. const _flushTopLevel = (writer) => {
  5186. const held = _heldTopLevel;
  5187. if (held === null) return;
  5188. _heldTopLevel = null;
  5189. if (writer !== undefined) {
  5190. _emitHeld(writer, held);
  5191. }
  5192. };
  5193. /**
  5194. * Emit a whole `block-contents` print: the held nodes are one declaration list,
  5195. * so they go through the same composition a rule's block does and land as one
  5196. * piece. The list is what a `style=""` attribute holds, so it is small.
  5197. * @param {(Rule | Declaration)[]} nodes the top-level nodes, in source order
  5198. * @param {PrintContext} writer the print context holding each one's text
  5199. * @returns {void}
  5200. */
  5201. const _emitBlockContents = (nodes, writer) => {
  5202. /** @type {Declaration[]} */
  5203. const decls = [];
  5204. /** @type {Rule[]} */
  5205. const rules = [];
  5206. for (const node of nodes) {
  5207. if (_types[_nodeIndex(node)] === T_DECLARATION) {
  5208. decls.push(/** @type {Declaration} */ (node));
  5209. } else {
  5210. rules.push(/** @type {Rule} */ (node));
  5211. }
  5212. }
  5213. const minify = writer.options.mode === "minify";
  5214. const { body } = _composeBlockBody(
  5215. A,
  5216. decls,
  5217. rules.length === 0 ? null : rules,
  5218. writer,
  5219. minify,
  5220. minify ? "" : "\n",
  5221. null
  5222. );
  5223. // The list prints as one piece, so it anchors as one: at where it starts. A
  5224. // declaration list is what an attribute holds, so that is the whole of it.
  5225. const offset = nodes.length === 0 ? 0 : A.start(nodes[0]);
  5226. const at = writer.mapWanted ? _locConverter.get(offset) : undefined;
  5227. // A leading separator only ever parts one item from the one before it, and at
  5228. // top level there is nothing before the first.
  5229. writer.take(
  5230. undefined,
  5231. offset,
  5232. at === undefined ? undefined : at.line - 1,
  5233. at === undefined ? undefined : at.column,
  5234. minify ? body : body.slice(1)
  5235. );
  5236. };
  5237. /**
  5238. * Note that a top-level node stood here. Called once per top-level node, after
  5239. * it has printed and before the next one does, so the empty-rule drop knows
  5240. * whether taking a rule out could still move a `@namespace` up.
  5241. * @param {Rule | Declaration} node the finished top-level node
  5242. * @returns {void}
  5243. */
  5244. const _closeNamespacePrologue = (node) => {
  5245. if (_namespacePrologueOpen && _types[_nodeIndex(node)] === T_QUALIFIED_RULE) {
  5246. _namespacePrologueOpen = false;
  5247. }
  5248. };
  5249. /**
  5250. * The `grammar` streaming sink: walk one top-level node, then recycle the
  5251. * buffers for the next.
  5252. * @param {Rule | Declaration} node top-level node
  5253. * @param {PrintContext=} writer print context when printing, else undefined
  5254. */
  5255. const _walkTopLevel = (node, writer) => {
  5256. if (node === _streamWalked) {
  5257. _streamWalked = null;
  5258. // A streamed rule's text is already out, so nothing can take it back — and
  5259. // the node is about to be recycled, which its candidacy must not outlive.
  5260. _prefixDropCandidate = null;
  5261. _closeNamespacePrologue(node);
  5262. _recycleTopLevel(node);
  5263. return;
  5264. }
  5265. _walkRule(node, null, 0, writer);
  5266. // A declaration list is composed as a whole, so its nodes are kept — their
  5267. // printed text stays reachable through the writer, which recycling would lose.
  5268. if (_blockContentsNodes !== null) {
  5269. _blockContentsNodes.push(node);
  5270. return;
  5271. }
  5272. // The walk fired each node's printer into the context; hand this finished
  5273. // top-level node to the writer with its source position — it flushes any kept
  5274. // comments before it, records the source-map anchor and appends its text (the
  5275. // loc converter is 1-based line / 0-based column; source maps are 0-based).
  5276. if (writer !== undefined) {
  5277. // Only the start is wanted, and only for a map: `loc` would walk to the end
  5278. // as well and box both, per top-level node, for a line nobody reads.
  5279. if (writer.mapWanted) {
  5280. const start = _locConverter.get(_starts[_nodeIndex(node)]);
  5281. _takeTopLevel(node, writer, A.start(node), start.line - 1, start.column);
  5282. } else {
  5283. _takeTopLevel(node, writer, A.start(node));
  5284. }
  5285. }
  5286. _closeNamespacePrologue(node);
  5287. // The held rule keeps its own entry, so the map is done with.
  5288. _ruleEntry.clear();
  5289. if (_nodeCount > _peak) _peak = _nodeCount;
  5290. if (_flatTop > _flatPeak) _flatPeak = _flatTop;
  5291. _nodeCount = 0;
  5292. _flatTop = 0;
  5293. // Body indices recycle with node ids.
  5294. _declBodies.length = 0;
  5295. _ruleBodies.length = 0;
  5296. };
  5297. /**
  5298. * `_walkTopLevel` without the walk, for a visitor-less `process` call: the
  5299. * walk's only effect is visitor dispatch, so just recycle the buffers.
  5300. * @param {Rule | Declaration} node top-level node
  5301. */
  5302. const _recycleTopLevel = (node) => {
  5303. if (_nodeCount > _peak) _peak = _nodeCount;
  5304. if (_flatTop > _flatPeak) _flatPeak = _flatTop;
  5305. _nodeCount = 0;
  5306. _flatTop = 0;
  5307. _declBodies.length = 0;
  5308. _ruleBodies.length = 0;
  5309. };
  5310. // The store buffers grow to the largest single top-level rule ever parsed and
  5311. // live at module level; above this capacity they are re-shrunk after a parse
  5312. // so one pathological rule can't pin megabytes for the process lifetime.
  5313. const _SHRINK_CAPACITY = 65536;
  5314. // `@license` / `@preserve` mark a comment for preservation (terser keeps the
  5315. // same annotations for JS); the JS-only `@cc_on` is not meaningful in CSS.
  5316. const _KEEP_COMMENT_RE = /@(?:license|preserve)/i;
  5317. /**
  5318. * A comment worth carrying through minification, matching terser's default set:
  5319. * a `/*!` banner (fast char-code check, no allocation) or a `@license` /
  5320. * `@preserve` annotated comment. `/*#` covers the `sourceMappingURL` /
  5321. * `sourceURL` pragmas, which are a link to drop, not a comment.
  5322. * @param {string} src source text
  5323. * @param {number} start comment start offset (at `/`)
  5324. * @param {number} end comment end offset (past the closing `/`)
  5325. * @returns {boolean} whether the comment survives minification
  5326. */
  5327. const _isKeptComment = (src, start, end) => {
  5328. const marker = src.charCodeAt(start + 2);
  5329. const kept = _commentsKept;
  5330. // A `/*#` pragma is a link to a source map rather than a comment, so the
  5331. // banner level keeps it; a selector of the author's still decides it.
  5332. if (marker === CC_NUMBER_SIGN && kept === "some") return true;
  5333. if (typeof kept === "boolean") return kept;
  5334. if (kept === "some") {
  5335. return (
  5336. marker === CC_EXCLAMATION || _KEEP_COMMENT_RE.test(src.slice(start, end))
  5337. );
  5338. }
  5339. // A pattern or a predicate of the author's, over the comment's own text —
  5340. // which stands in for the banner rule rather than beside it, as terser's
  5341. // `format.comments` does.
  5342. return kept(src.slice(start + 2, end - 2)) === true;
  5343. };
  5344. // Every rewrite on, which is what a print with no `transforms` option makes and
  5345. // what the walk-only passes hold. Frozen, since it is shared rather than copied.
  5346. /** @type {Required<CssTransformOptions>} */
  5347. const _ALL_TRANSFORMS = Object.freeze({
  5348. comments: "some",
  5349. mergeLonghands: true,
  5350. mergeRules: true,
  5351. normalizeQuotes: true,
  5352. reduceFunctions: true,
  5353. removeDeadRules: true,
  5354. shortenColors: true,
  5355. shortenMediaQueries: true,
  5356. shortenNumbers: true,
  5357. shortenSelectors: true,
  5358. shortenValues: true
  5359. });
  5360. /**
  5361. * The rewrites this print makes, resolved once: each name is what the options
  5362. * set it to, and what `_ALL_TRANSFORMS` gives it where they set nothing — so no
  5363. * read of the result asks whether the option was given. `comments` keeps
  5364. * whatever it was given, which is still falsy only when it is `false`.
  5365. *
  5366. * Always a copy, and always made the one way: spreading the table and then
  5367. * writing over a name it already has leaves the object's shape alone, so every
  5368. * read of the result — and there is one per token — sees a single hidden class.
  5369. * Handing the frozen table itself back where nothing is set would save this
  5370. * object and cost each of those reads a second shape, `Object.freeze` giving a
  5371. * frozen object a map of its own.
  5372. * @param {CssTransformOptions | undefined} options the `transforms` option
  5373. * @returns {Required<CssTransformOptions>} every rewrite resolved
  5374. */
  5375. const _transformsFrom = (options) => {
  5376. /** @type {EXPECTED_ANY} */
  5377. const out = { ..._ALL_TRANSFORMS };
  5378. if (options !== undefined) {
  5379. for (const name of Object.keys(_ALL_TRANSFORMS)) {
  5380. const value = /** @type {EXPECTED_ANY} */ (options)[name];
  5381. if (value !== undefined) out[name] = value;
  5382. }
  5383. }
  5384. return out;
  5385. };
  5386. // The same table, as a copy, for the reason `_transformsFrom` always makes one.
  5387. /** @type {Required<CssTransformOptions>} */
  5388. const _DEFAULT_TRANSFORMS = _transformsFrom(undefined);
  5389. /**
  5390. * The `comments` option resolved to what it means per comment: `true` every
  5391. * one, `false` none, `"some"` the ones that carry something, or a predicate
  5392. * over the comment's own text — which is what a pattern compiles to here, so no
  5393. * print re-reads one per comment. A pattern is matched from the start each
  5394. * time, since a `g` flag would otherwise carry an index between comments.
  5395. * @param {Required<CssTransformOptions>["comments"]} comments the option
  5396. * @returns {boolean | "some" | ((comment: string) => boolean)} what it keeps
  5397. */
  5398. const _keptComments = (comments) => {
  5399. if (comments === "all") return true;
  5400. if (typeof comments === "boolean" || comments === "some") return comments;
  5401. if (typeof comments === "function") return comments;
  5402. const pattern =
  5403. typeof comments === "string" ? new RegExp(comments) : comments;
  5404. return (comment) => {
  5405. pattern.lastIndex = 0;
  5406. return pattern.test(comment);
  5407. };
  5408. };
  5409. /**
  5410. * The per-transform switches out of a wider options object — what
  5411. * `optimization.minimize.css` names beside `environment` and the two rewrites
  5412. * that are off until asked for. Exported so the minify functions the minimizer
  5413. * plugin ships to its workers can pick them without repeating the names.
  5414. * @param {EXPECTED_OBJECT} options an options object that may carry them
  5415. * @returns {CssTransformOptions | undefined} the switches, or undefined when none is set
  5416. */
  5417. const pickTransforms = (options) => {
  5418. /** @type {EXPECTED_ANY} */
  5419. let out;
  5420. for (const name of Object.keys(_ALL_TRANSFORMS)) {
  5421. const value = /** @type {EXPECTED_ANY} */ (options)[name];
  5422. if (value === undefined) continue;
  5423. if (out === undefined) out = {};
  5424. out[name] = value;
  5425. }
  5426. return out;
  5427. };
  5428. /**
  5429. * The CSS `SourceProcessor` grammar: consume top-level rules one at a time
  5430. * (§5.4.1) and walk each immediately, firing `enter` / `exit` in source order
  5431. * without building a whole-stylesheet array first. `recurseBlocks: false` skips
  5432. * walking block bodies' (eagerly parsed) nested rules (caller drives nested
  5433. * traversal itself). When `writer` is given the same walk also prints: it is
  5434. * threaded down the walk and each node's printer builds its text into it as the
  5435. * node finishes — one parse, no re-tokenization. `skip` is ignored while printing
  5436. * (it needs every node).
  5437. * @param {string} input source text
  5438. * @param {CompiledVisitorMap} visitors compiled visitor map
  5439. * @param {PrintContext | undefined} writer the print context to build output into, or undefined (walk only)
  5440. * @param {CssProcessOptions} options process options
  5441. */
  5442. const grammar = (input, visitors, writer, options) => {
  5443. const locConverter = options.locConverter || new LocConverter(input);
  5444. _setupParse(input, locConverter);
  5445. // Printing (a writer) needs every node for a faithful serialization, so `skip`
  5446. // is ignored while printing — it only applies to walk-only visitor passes.
  5447. const skip = writer !== undefined ? undefined : options.skip;
  5448. _skipTypes = (skip && skip.types) || _NO_SKIP_TYPES;
  5449. _skipActive = _skipTypes !== _NO_SKIP_TYPES;
  5450. _skipSelectorPrelude = skip !== undefined && skip.selectorPrelude === true;
  5451. _skipAtRulePrelude = skip !== undefined && skip.atRulePrelude === true;
  5452. _recurseBlocks = options.recurseBlocks !== false;
  5453. // The walk always streams: there is nothing to trade off, since a block under
  5454. // the threshold is collected and walked in one batch exactly as it always was.
  5455. // Printing streams with it — the rule's opener goes out when its block opens
  5456. // and each child straight after it — so the printer never assembles a parent
  5457. // from text a streamed body has already released.
  5458. _streamBlocks = true;
  5459. _streamWriter = writer;
  5460. _printing = writer !== undefined;
  5461. _convertLengthUnits =
  5462. writer !== undefined && writer.options.convertLengthUnits === true;
  5463. _rewriteCustomProperties =
  5464. writer !== undefined && writer.options.rewriteCustomProperties === true;
  5465. _transforms = _transformsFrom(
  5466. writer === undefined ? undefined : writer.options.transforms
  5467. );
  5468. _commentsKept = _keptComments(_transforms.comments);
  5469. _unitScale = _unitScaleFor(_convertLengthUnits);
  5470. _renderEmbeddedSource =
  5471. writer === undefined ? undefined : writer.options.renderEmbeddedSource;
  5472. _deferEmbeddedSource =
  5473. writer === undefined ? undefined : writer.options.deferEmbeddedSource;
  5474. const environment =
  5475. writer !== undefined ? writer.options.environment : undefined;
  5476. // Vendor prefixing is on for a minifying print with a browserslist selection,
  5477. // and off for everything else — an empty selection names no browser to
  5478. // prefix for, so it leaves prefixes alone rather than dropping every one.
  5479. const browsers =
  5480. writer !== undefined &&
  5481. writer.options.mode === "minify" &&
  5482. environment !== undefined &&
  5483. environment.browsers !== undefined &&
  5484. environment.browsers.length !== 0
  5485. ? environment.browsers
  5486. : undefined;
  5487. if (browsers === undefined) {
  5488. _seenPrefixRules = null;
  5489. _prefixBrowsers = null;
  5490. _prefixingOn = false;
  5491. } else {
  5492. _useBrowsers(browsers);
  5493. // The selection answers two questions, and `vendorPrefixes` turns off only
  5494. // the first: which prefixes to write, and which spellings a target reads.
  5495. _prefixingOn =
  5496. _prefixBrowsers !== null &&
  5497. /** @type {CssEnvironment} */ (environment).vendorPrefixes !== false;
  5498. _seenPrefixRules = _prefixingOn ? new Map() : null;
  5499. }
  5500. // Read once per print: the selection cannot change under a single stylesheet,
  5501. // and each of these is asked per declaration.
  5502. _hexAlphaAllowed = _targetSupports("colorHexAlpha");
  5503. _doublePositionAllowed = _targetSupports("gradientDoublePosition");
  5504. _insetShorthandAllowed = _targetSupports("insetShorthand");
  5505. _rangeSpellingAllowed = _targetSupports("mediaQueryRange");
  5506. _placeShorthandAllowed = _targetSupports("placeShorthand");
  5507. _overflowTwoValuesAllowed = _targetSupports("overflowTwoValues");
  5508. _visitors = visitors;
  5509. _commentBucket = visitors[T_COMMENT];
  5510. // Comment sink: fire the `Comment` visitor bucket (if registered) and, when
  5511. // printing, keep license/important comments (`/*!`, `@license`, `@preserve`) —
  5512. // the ecosystem default (cssnano / clean-css / csso keep `/*!`; terser adds the
  5513. // annotations). A kept comment is handed to the writer, which re-emits it before
  5514. // the next top-level node. Both print modes keep the same ones, so beautifying
  5515. // and minifying the same stylesheet carry the same banners. No comment visitor
  5516. // and no printing => no callback (comments are skipped at zero cost).
  5517. /** @type {((input: string, start: number, end: number) => number) | undefined} */
  5518. let onComment;
  5519. if (writer !== undefined) {
  5520. const w = writer;
  5521. onComment = (src, start, end) => {
  5522. if (_commentBucket !== undefined) _grammarOnComment(src, start, end);
  5523. if (_isKeptComment(src, start, end)) {
  5524. w.insert(start, src.slice(start, end));
  5525. }
  5526. return end;
  5527. };
  5528. } else if (_commentBucket !== undefined) {
  5529. onComment = _grammarOnComment;
  5530. }
  5531. // Stream each top-level node (selected by `as`) to the walker the moment it's
  5532. // consumed, rather than collecting them first — so the whole AST is never
  5533. // held at once; peak heap is ~one top-level node's subtree.
  5534. const ts = new TokenStream(input, 0, locConverter, onComment);
  5535. const as = options.as || "stylesheet";
  5536. const consume = TOP_LEVEL_CONSUMERS[as] || consumeAStylesheetsContents;
  5537. // A block's contents are one declaration list, so printing them is composing
  5538. // that list — the same production a rule's block is, and the only top-level
  5539. // one that is held rather than streamed.
  5540. if (as === "block-contents" && writer !== undefined) {
  5541. _blockContentsNodes = [];
  5542. }
  5543. // With zero registered buckets the walk is a pure no-op traversal — hand the
  5544. // consumer a recycle-only sink instead.
  5545. let anyVisitor = false;
  5546. for (let i = 0; i < visitors.length; i++) {
  5547. if (visitors[i] !== undefined) {
  5548. anyVisitor = true;
  5549. break;
  5550. }
  5551. }
  5552. try {
  5553. // Bind the writer into the per-node sink only when printing, so the
  5554. // walk-only path keeps passing the module function with no per-parse closure.
  5555. // A printer consumes the walk, so only a writer-less, visitor-less process
  5556. // call can take the recycle-only sink.
  5557. consume(
  5558. ts,
  5559. writer === undefined
  5560. ? anyVisitor
  5561. ? _walkTopLevel
  5562. : _recycleTopLevel
  5563. : (node) => _walkTopLevel(node, writer)
  5564. );
  5565. if (_blockContentsNodes === null) {
  5566. _flushTopLevel(writer);
  5567. } else {
  5568. _emitBlockContents(
  5569. _blockContentsNodes,
  5570. /** @type {PrintContext} */ (writer)
  5571. );
  5572. }
  5573. } finally {
  5574. _blockContentsNodes = null;
  5575. // Each held write keeps its offered source — a decoded `data:` payload can
  5576. // be a whole document — and a closure over it, so both go with the parse.
  5577. _renderEmbeddedSource = undefined;
  5578. _deferEmbeddedSource = undefined;
  5579. _heldTopLevel = null;
  5580. _seenTopLevelLayers = null;
  5581. _ruleEntry.clear();
  5582. _seenPrefixRules = null;
  5583. // The parsed selection itself stays in `_parsedBrowsersMemo`, which the next
  5584. // asset of the same build reuses; only this parse's pointer is dropped.
  5585. _prefixBrowsers = null;
  5586. _prefixDropCandidate = null;
  5587. // Drop the module-level column references so the last parsed source (and
  5588. // its LocConverter / child lists / visitors) don't stay alive between
  5589. // parses.
  5590. _streamBlocks = false;
  5591. _streamWalked = null;
  5592. _streamWriter = undefined;
  5593. _depth = 0;
  5594. // A frame is left as the block closed it (nothing reads a closed one), so
  5595. // the last open path's buffers are dropped here rather than per block.
  5596. const used = _frameHighWater + 1;
  5597. _frameDecls.fill(null, 0, used);
  5598. _frameRules.fill(null, 0, used);
  5599. _frameSeenDeclarations.fill(null, 0, used);
  5600. _frameSeenRules.fill(null, 0, used);
  5601. _frameChainKey.fill(null, 0, used);
  5602. _frameSeenLayers.fill(null, 0, used);
  5603. _frameHighWater = 0;
  5604. _blockSpans.length = 0;
  5605. _ruleScopes = null;
  5606. _rootRuleScope = null;
  5607. _anonymousLayers = 0;
  5608. _input = "";
  5609. _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
  5610. _declBodies.length = 0;
  5611. _ruleBodies.length = 0;
  5612. _flatTop = 0;
  5613. _listPool.length = 0;
  5614. if (_flat.length > _SHRINK_CAPACITY) {
  5615. _flatGrowHint = _flatPeak;
  5616. _flat = new Int32Array(0);
  5617. }
  5618. _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
  5619. _commentBucket = undefined;
  5620. if (_capacity > _SHRINK_CAPACITY) {
  5621. // +1: node ids are 1-based and grow fires at `id >= capacity`.
  5622. _growHint = _peak + 1;
  5623. _capacity = 0;
  5624. _releaseColumns();
  5625. }
  5626. _peak = 0;
  5627. _flatPeak = 0;
  5628. }
  5629. };
  5630. /**
  5631. * Whether a pending token separator is safe to drop *before* `cc`: next to
  5632. * `{ } ; , )` it never changes meaning (`)` only ever closes a group).
  5633. * @param {number} cc first code point of the text about to be written
  5634. * @returns {boolean} true when the separator can be dropped
  5635. */
  5636. const _dropSeparatorBefore = (cc) =>
  5637. cc === CC_LEFT_CURLY ||
  5638. cc === CC_RIGHT_CURLY ||
  5639. cc === CC_SEMICOLON ||
  5640. cc === CC_COMMA ||
  5641. cc === CC_RIGHT_PARENTHESIS;
  5642. /**
  5643. * Whether a pending token separator is safe to drop *after* `cc`: next to
  5644. * `{ } ; , (` it never changes meaning. The parens are asymmetric on purpose —
  5645. * dropping a space *before* `(` could turn `x (y)` into the function `x(y)`, and
  5646. * one *after* `)` could turn `:not(a) b` into the compound `:not(a)b`, so `(` is
  5647. * only safe on this (after) side and `)` only on the before side.
  5648. * @param {number} cc last emitted code point
  5649. * @returns {boolean} true when the separator can be dropped
  5650. */
  5651. const _dropSeparatorAfter = (cc) =>
  5652. cc === CC_LEFT_CURLY ||
  5653. cc === CC_RIGHT_CURLY ||
  5654. cc === CC_SEMICOLON ||
  5655. cc === CC_COMMA ||
  5656. cc === CC_LEFT_PARENTHESIS;
  5657. /**
  5658. * What the CSS printer may be told, on top of the `mode` every language has —
  5659. * the printing slice of `CssProcessOptions`, named once so nothing outside CSS
  5660. * has to enumerate it.
  5661. * @typedef {Pick<CssProcessOptions, "environment" | "convertLengthUnits" | "rewriteCustomProperties" | "renderEmbeddedSource" | "deferEmbeddedSource" | "transforms">} CssPrintOptions
  5662. */
  5663. /** @typedef {import("../util/SourceProcessor").PrintContext<CssPath, Node, CssPrintOptions>} PrintContext */
  5664. // === Spacing: the whole safe-spacing discipline is CSS-specific (a token
  5665. // separator is safe to drop next to `{ } ; , ( )` — the asymmetric paren rule in
  5666. // `_dropSeparator*`), so it lives with `printer`, not the generic context.
  5667. // A lone space marker a whitespace token prints; `_join` resolves it. Real tokens
  5668. // print with their delimiters (a string keeps its quotes), so it never collides.
  5669. const _SEP = " ";
  5670. /**
  5671. * Whether a token separator between two code points must be kept.
  5672. * @param {number} lastCode last emitted code point
  5673. * @param {number} nextCode first code point of the next fragment
  5674. * @returns {boolean} true when the separator must be kept
  5675. */
  5676. const _keepSeparator = (lastCode, nextCode) =>
  5677. !_dropSeparatorAfter(lastCode) && !_dropSeparatorBefore(nextCode);
  5678. // A child / adjacent-sibling / general-sibling combinator. Whitespace around
  5679. // one is insignificant, but only at the top level of a selector — inside `[…]`
  5680. // (`~=`) or `(…)` (`nth-child(2n+1)`) these are matchers/`An+B`, so combinator
  5681. // trimming is applied to a qualified rule's prelude join alone (`isSelector`).
  5682. /**
  5683. * @param {number} cc a code point
  5684. * @returns {boolean} true for `>` / `+` / `~`
  5685. */
  5686. const _isCombinator = (cc) =>
  5687. cc === CC_GREATER_THAN_SIGN || cc === CC_PLUS_SIGN || cc === CC_TILDE;
  5688. // What separates the parts of a query condition: a media-feature range
  5689. // comparison (`<`, `>`, `=`, and the `<=` / `>=` pairs they build) and the `:`
  5690. // of a plain feature test. Whitespace around one is insignificant there, but the
  5691. // same code points mean something else in a selector (combinators, and `:`
  5692. // starting a pseudo-class) and in a declaration value (`calc()` operators) — so
  5693. // this trimming is applied to a `(…)` condition outside a value alone.
  5694. /**
  5695. * @param {number} cc a code point
  5696. * @returns {boolean} true for `<` / `>` / `=` / `:`
  5697. */
  5698. const _isConditionSeparator = (cc) =>
  5699. cc === CC_LESS_THAN_SIGN ||
  5700. cc === CC_GREATER_THAN_SIGN ||
  5701. cc === CC_EQUALS_SIGN ||
  5702. cc === CC_COLON;
  5703. // What a `_join` may drop a separator next to.
  5704. const _TRIM_NOTHING = 0;
  5705. const _TRIM_COMBINATORS = 1; // a qualified rule's selector prelude
  5706. const _TRIM_CONDITIONS = 2; // a query condition's `(…)` block
  5707. const _TRIM_MATH = 3; // a math function's `*` / `/`, which need no whitespace
  5708. // A declaration value's whitespace only separates tokens, so it is kept exactly
  5709. // where joining would fuse two — unlike a selector, where it is a combinator.
  5710. const _TRIM_SEPARATORS = 4;
  5711. /**
  5712. * @param {number} trim one of the `_TRIM_*` modes
  5713. * @param {number} cc a code point
  5714. * @returns {boolean} whether a separator next to `cc` may be dropped
  5715. */
  5716. const _isTrimmable = (trim, cc) => {
  5717. if (trim === _TRIM_COMBINATORS) return _isCombinator(cc);
  5718. if (trim === _TRIM_CONDITIONS) return _isConditionSeparator(cc);
  5719. // CSS Values 4 §10.1: `+` and `-` require whitespace on both sides — without
  5720. // it the sign would read as part of the next number — but `*` and `/` do not.
  5721. if (trim === _TRIM_MATH) {
  5722. return cc === CC_ASTERISK || cc === CC_SOLIDUS;
  5723. }
  5724. return false;
  5725. };
  5726. /**
  5727. * Whether a code point can continue an identifier. The lookup table only spans
  5728. * ASCII, so non-ASCII — always an ident code point (§4.2) — is folded in here.
  5729. * @param {number} cc a code point
  5730. * @returns {boolean} true when `cc` is an ident code point
  5731. */
  5732. const _isIdentLike = (cc) => cc >= 128 || _isIdentCodePoint(cc);
  5733. /**
  5734. * Whether what precedes `at` lets a number start there: an ident running into
  5735. * the position carries the digits instead, and `#` and `@` carry their own.
  5736. * @param {string} out everything emitted so far
  5737. * @param {number} at index a number would start at
  5738. * @returns {boolean} true when a number can start at `at`
  5739. */
  5740. const _startsNumber = (out, at) => {
  5741. if (at === 0) return true;
  5742. const before = out.charCodeAt(at - 1);
  5743. return !(
  5744. _isIdentLike(before) ||
  5745. before === CC_REVERSE_SOLIDUS ||
  5746. before === CC_NUMBER_SIGN ||
  5747. before === CC_AT_SIGN
  5748. );
  5749. };
  5750. /**
  5751. * Whether the run of digits `out` ends with is a number rather than the tail of
  5752. * an ident: `1` is, the `1` of `.p1` is not, and only a number can take a `.` on.
  5753. * @param {string} out everything emitted so far
  5754. * @returns {boolean} true when `out` ends in a number
  5755. */
  5756. const _endsWithNumber = (out) => {
  5757. let at = out.length;
  5758. while (at > 0 && _isDigit(out.charCodeAt(at - 1))) at--;
  5759. if (at === out.length) return false;
  5760. // A sign signs a number only where a number could start: the `-1` of
  5761. // `margin:-1` is signed, the `-1` of `.p-1` and the `-5` of `1e-5` are ident.
  5762. const sign = at === 0 ? 0 : out.charCodeAt(at - 1);
  5763. if (
  5764. (sign === CC_HYPHEN_MINUS || sign === CC_PLUS_SIGN) &&
  5765. _startsNumber(out, at - 1)
  5766. ) {
  5767. return true;
  5768. }
  5769. return _startsNumber(out, at);
  5770. };
  5771. /**
  5772. * Whether emitting `fragment` directly after `out` would re-tokenize as one
  5773. * token. Two printed siblings normally concatenate to exactly their source, but
  5774. * not when a dropped comment was all that separated them, or when one was
  5775. * rewritten (`1.0.5` is two numbers, printed as `1` and `.5`) — then the junction
  5776. * can fuse and change the declaration. The test is deliberately exact rather than
  5777. * conservative: a space inserted where none is needed would turn a compound
  5778. * selector (`.a.b`) into a descendant one (`.a .b`), so over-separating is as
  5779. * unsafe as under-separating.
  5780. * Takes the preceding character rather than the text before it: what it is
  5781. * appended to is a rope, and reading a character off one flattens the whole of
  5782. * it — once per fragment, over text that grows with every fragment.
  5783. * @param {number} last character code before the fragment
  5784. * @param {string} fragment the fragment about to be emitted
  5785. * @param {string} out everything emitted so far, read only where a digit meets a `.`
  5786. * @returns {boolean} true when a separator has to be inserted between them
  5787. */
  5788. const _wouldFuseTokens = (last, fragment, out) => {
  5789. const next = fragment.charCodeAt(0);
  5790. // An escape swallows whatever follows it.
  5791. if (last === CC_REVERSE_SOLIDUS) return true;
  5792. // `/` + `*` opens a comment.
  5793. if (last === CC_SOLIDUS && next === CC_ASTERISK) return true;
  5794. if (_isIdentLike(last)) {
  5795. // One ident / dimension / hash / at-keyword continues; `(` after an ident
  5796. // makes it a function token instead.
  5797. if (
  5798. _isIdentLike(next) ||
  5799. next === CC_LEFT_PARENTHESIS ||
  5800. next === CC_REVERSE_SOLIDUS
  5801. ) {
  5802. return true;
  5803. }
  5804. // `1` + `.5` reads back as the single number `1.5`. Only a number takes
  5805. // the `.` on, and only when a digit follows it: the `1` ending the ident of
  5806. // `.p1` does not, so `.p1` + `.c1` stays the one compound selector it is.
  5807. if (_isDigit(last) && next === CC_FULL_STOP) {
  5808. return _isDigit(fragment.charCodeAt(1)) && _endsWithNumber(out);
  5809. }
  5810. // `123` + `%` reads back as the one percentage token `123%`, and only a
  5811. // number takes the `%` on — the `1` ending an ident does not.
  5812. if (_isDigit(last) && next === CC_PERCENTAGE) return _endsWithNumber(out);
  5813. // A trailing `--` plus `>` would close a CDC.
  5814. return last === CC_HYPHEN_MINUS && next === CC_GREATER_THAN_SIGN;
  5815. }
  5816. // A number may start right after `.` or `+` (`-` is an ident code point, so it
  5817. // is already covered above).
  5818. if (last === CC_FULL_STOP) return _isDigit(next);
  5819. // CSS Syntax 3 §4.3.10: a `+` starts a number only before a digit, or before a
  5820. // `.` that itself has one. `.a+.m` is the sibling combinator and a class.
  5821. if (last === CC_PLUS_SIGN) {
  5822. return (
  5823. _isDigit(next) ||
  5824. (next === CC_FULL_STOP && _isDigit(fragment.charCodeAt(1)))
  5825. );
  5826. }
  5827. // `#` / `@` + ident starts a hash / at-keyword token.
  5828. if (last === CC_NUMBER_SIGN || last === CC_AT_SIGN) {
  5829. return _isIdentLike(next) || next === CC_REVERSE_SOLIDUS;
  5830. }
  5831. // `<` + `!` opens a CDO.
  5832. return last === CC_LESS_THAN_SIGN && next === CC_EXCLAMATION;
  5833. };
  5834. /**
  5835. * Join sibling fragments (a prelude / value / args / block body), resolving each
  5836. * `_SEP` marker into a single space kept only where dropping it would merge two
  5837. * tokens (minifying) or unconditionally (beautifying). `trim` additionally drops
  5838. * whitespace next to the code points that carry their own meaning in this
  5839. * context — a selector's combinators, a query condition's comparisons.
  5840. * Fragments that had no separator at all still get one where concatenating them
  5841. * would fuse two tokens into one (see {@link _wouldFuseTokens}).
  5842. * @param {string[]} parts printed fragments in source order
  5843. * @param {boolean} beautify whether every separator is kept
  5844. * @param {number=} trim one of the `_TRIM_*` modes (default `_TRIM_NOTHING`)
  5845. * @returns {string} the joined text
  5846. */
  5847. const _join = (parts, beautify, trim = _TRIM_NOTHING) => {
  5848. let out = "";
  5849. let last = -1;
  5850. let pending = false;
  5851. for (let i = 0; i < parts.length; i++) {
  5852. const f = parts[i];
  5853. if (f.length === 0) continue;
  5854. if (f === _SEP) {
  5855. pending = true;
  5856. continue;
  5857. }
  5858. const first = f.charCodeAt(0);
  5859. if (pending) {
  5860. const keep =
  5861. beautify ||
  5862. (trim === _TRIM_SEPARATORS
  5863. ? _wouldFuseTokens(last, f, out)
  5864. : _keepSeparator(last, first) &&
  5865. !_isTrimmable(trim, last) &&
  5866. !_isTrimmable(trim, first));
  5867. if (out.length !== 0 && keep) {
  5868. out += " ";
  5869. }
  5870. pending = false;
  5871. } else if (out.length !== 0 && _wouldFuseTokens(last, f, out)) {
  5872. // Nothing separated these in source (a comment stood here, or a token was
  5873. // rewritten), yet joining them would read back as one token. Whitespace
  5874. // is a descendant combinator in a selector, so there only an empty
  5875. // comment parts them without saying anything the source did not.
  5876. out += trim === _TRIM_COMBINATORS ? "/**/" : " ";
  5877. }
  5878. out += f;
  5879. last = f.charCodeAt(f.length - 1);
  5880. }
  5881. return out;
  5882. };
  5883. // === Safe (meaning-preserving) value transforms, applied by `printer`
  5884. // when minifying. Each is value-identical — the same computed style — so they
  5885. // never change what the stylesheet means.
  5886. /**
  5887. * The byte offset where `s`'s leading number ends (before its unit / `%`).
  5888. * @param {string} s a number / dimension / percentage token's text
  5889. * @returns {number} the numeric part's length
  5890. */
  5891. const _numberEnd = (s) => {
  5892. const n = s.length;
  5893. let i = 0;
  5894. const c = s.charCodeAt(0);
  5895. if (c === CC_PLUS_SIGN || c === CC_HYPHEN_MINUS) i++;
  5896. while (i < n && _isDigit(s.charCodeAt(i))) i++;
  5897. if (i < n && s.charCodeAt(i) === CC_FULL_STOP) {
  5898. i++;
  5899. while (i < n && _isDigit(s.charCodeAt(i))) i++;
  5900. }
  5901. // exponent (`e` / `E`)
  5902. const e = s.charCodeAt(i);
  5903. if (e === 101 || e === 69) {
  5904. let j = i + 1;
  5905. const sign = s.charCodeAt(j);
  5906. if (sign === CC_PLUS_SIGN || sign === CC_HYPHEN_MINUS) j++;
  5907. let k = j;
  5908. while (k < n && _isDigit(s.charCodeAt(k))) k++;
  5909. if (k > j) i = k;
  5910. }
  5911. return i;
  5912. };
  5913. /**
  5914. * Normalize a numeric string (no unit): drop a leading zero (`0.5`→`.5`) and
  5915. * trailing fractional zeros (`1.50`→`1.5`, `1.0`→`1`). Value-preserving;
  5916. * scientific notation is left untouched.
  5917. * @param {string} num numeric text (may carry a sign)
  5918. * @returns {string} the normalized number
  5919. */
  5920. const _normalizeNumber = (num) => {
  5921. if (num.includes("e") || num.includes("E")) return num;
  5922. let sign = "";
  5923. let s = num;
  5924. const c0 = s.charCodeAt(0);
  5925. if (c0 === CC_PLUS_SIGN || c0 === CC_HYPHEN_MINUS) {
  5926. if (c0 === CC_HYPHEN_MINUS) sign = "-";
  5927. s = s.slice(1);
  5928. }
  5929. const dot = s.indexOf(".");
  5930. if (dot !== -1) {
  5931. let end = s.length;
  5932. while (end > dot + 1 && s.charCodeAt(end - 1) === CC_0) end--;
  5933. if (end === dot + 1) end = dot; // fraction emptied → drop the dot too
  5934. s = s.slice(0, end);
  5935. }
  5936. while (
  5937. s.length > 1 &&
  5938. s.charCodeAt(0) === CC_0 &&
  5939. s.charCodeAt(1) !== CC_FULL_STOP
  5940. ) {
  5941. s = s.slice(1); // redundant integer leading zeros
  5942. }
  5943. if (
  5944. s.length > 1 &&
  5945. s.charCodeAt(0) === CC_0 &&
  5946. s.charCodeAt(1) === CC_FULL_STOP
  5947. ) {
  5948. s = s.slice(1); // "0.5" → ".5"
  5949. }
  5950. if (s === "" || s === ".") s = "0";
  5951. return sign + s;
  5952. };
  5953. // Chromium serializes a computed number at 6 significant digits, and lays a
  5954. // length out in 1/64px — so 6 digits is below what a stylesheet can observe until
  5955. // the value reaches ~23000px, which no real one does. Measured across `width`
  5956. // (px and %), `opacity`, `scale()`, `letter-spacing`, `transition-duration` and
  5957. // `flex-grow`, at container widths from 100px to 1000000px.
  5958. const _SIGNIFICANT_DIGITS = 6;
  5959. // Above this the rounded absolute error would pass 1/64px, so the digits stay.
  5960. const _ROUNDING_LIMIT = 1e4;
  5961. /**
  5962. * Round a numeric string to `_SIGNIFICANT_DIGITS`, or return it unchanged when
  5963. * rounding would not shorten it (or would leave the range the measurement
  5964. * covers). Scientific notation is left alone — `toPrecision` may produce it, and
  5965. * an exponent is not shorter here anyway.
  5966. * @param {string} num the normalized numeric text
  5967. * @returns {string} the rounded number, or `num`
  5968. */
  5969. const _roundSignificant = (num) => {
  5970. // A number written in at most `_SIGNIFICANT_DIGITS` characters carries at most
  5971. // that many significant digits — a sign, a dot and leading zeros only take
  5972. // away from them — so rounding to that precision hands back what it was given.
  5973. // Almost every number in a stylesheet is this short.
  5974. if (num.length <= _SIGNIFICANT_DIGITS) return num;
  5975. if (num.includes("e") || num.includes("E")) return num;
  5976. const value = Number(num);
  5977. if (!Number.isFinite(value) || Math.abs(value) >= _ROUNDING_LIMIT) return num;
  5978. const rounded = value.toPrecision(_SIGNIFICANT_DIGITS);
  5979. if (rounded.includes("e") || rounded.includes("E")) return num;
  5980. if (Number(rounded) === value) return num;
  5981. const text = _normalizeNumber(rounded);
  5982. return text.length < num.length ? text : num;
  5983. };
  5984. // The absolute units that still convert once `convertLengthUnits` is off: every
  5985. // group but `length`, which is the one that option gates. Built on first use and
  5986. // kept, so a parse points at one table or the other rather than testing per token.
  5987. /** @type {Map<string, [string, number]> | null} */
  5988. let _nonLengthUnitScale = null;
  5989. /**
  5990. * The unit table a parse converts through, by what its options allow.
  5991. * @param {boolean} lengths whether a length may be rewritten into another unit
  5992. * @returns {Map<string, [string, number]>} the table to convert through
  5993. */
  5994. const _unitScaleFor = (lengths) => {
  5995. if (lengths) return ABSOLUTE_UNIT_SCALE;
  5996. if (_nonLengthUnitScale === null) {
  5997. _nonLengthUnitScale = new Map();
  5998. for (const [unit, scale] of ABSOLUTE_UNIT_SCALE) {
  5999. if (scale[0] !== "length") _nonLengthUnitScale.set(unit, scale);
  6000. }
  6001. }
  6002. return _nonLengthUnitScale;
  6003. };
  6004. /**
  6005. * Rewrite a dimension into the shortest unit it is exactly equal in. Only the
  6006. * units CSS Values 4 fixes against each other, and only when the conversion
  6007. * round-trips exactly in doubles — which is what keeps `cm` / `mm` / `q`, none
  6008. * of them binary-exact in `px`, mostly where they were.
  6009. * @param {string} num the normalized numeric text
  6010. * @param {string} unit the token's unit, as written
  6011. * @returns {string} the shortest equal dimension
  6012. */
  6013. const _convertUnit = (num, unit) => {
  6014. // The table this parse converts through: without `convertLengthUnits` the
  6015. // length units are not in it, so a `px` — the commonest dimension a
  6016. // stylesheet writes — costs a lookup that misses rather than a parse thrown
  6017. // away.
  6018. const from = _unitScale.get(toLowerCaseIfNeeded(unit));
  6019. if (from === undefined) return num + unit;
  6020. const value = Number(num);
  6021. if (!Number.isFinite(value)) return num + unit;
  6022. // A zero length drops its unit outright, so rewriting it says nothing — but a
  6023. // zero time keeps one, and `s` is the shorter of the two it can carry.
  6024. if (value === 0 && from[0] !== "time") return num + unit;
  6025. const base = value * from[1];
  6026. let best = num + unit;
  6027. for (const [candidate, to] of _unitScale) {
  6028. if (to[0] !== from[0] || to === from) continue;
  6029. if (!UNIT_CONVERSION_TARGETS.has(candidate)) continue;
  6030. const converted = base / to[1];
  6031. if (converted * to[1] !== base) continue;
  6032. const text = String(converted);
  6033. if (text.includes("e") || text.includes("E")) continue;
  6034. const dimension = _normalizeNumber(text) + candidate;
  6035. if (dimension.length < best.length) best = dimension;
  6036. }
  6037. return best;
  6038. };
  6039. /**
  6040. * Whether the declaration being printed has an `<integer>` anywhere in its
  6041. * grammar, so a number in it may be one.
  6042. * @returns {boolean} true inside such a declaration
  6043. */
  6044. const _inIntegerProperty = () =>
  6045. _valueDeclaration !== null &&
  6046. INTEGER_PROPERTIES.has(toLowerCaseIfNeeded(A.name(_valueDeclaration)));
  6047. /**
  6048. * Whether the declaration being printed is one an engine takes no `calc()` in,
  6049. * so a folded term keeps the `calc()` it was written with.
  6050. * @returns {boolean} true inside such a declaration
  6051. */
  6052. const _inCalcRejectingProperty = () =>
  6053. _valueDeclaration !== null &&
  6054. CALC_REJECTING_PROPERTIES.has(toLowerCaseIfNeeded(A.name(_valueDeclaration)));
  6055. // A folded term, as a number and its unit.
  6056. const _TERM_VALUE_RE = /^(-?(?:\d+\.?\d*|\.\d+))([a-z%]*)$/i;
  6057. /**
  6058. * Whether writing a value bare would lose the clamp the spec puts on a `calc()`
  6059. * in this property — the literal is thrown out where the `calc()` computes the
  6060. * bound. A unit the range is not stated in loses it too: Chrome takes
  6061. * `oblique 100grad` off and `oblique 2rad` at face value, while clamping either
  6062. * inside a `calc()`.
  6063. * @param {string} property the lower-cased property name
  6064. * @param {string} number the value
  6065. * @param {string} unit its unit, "" when it carries none
  6066. * @returns {boolean} true when the `calc()` has to stay
  6067. */
  6068. const _losesClamp = (property, number, unit) => {
  6069. const clamped = CLAMPED_VALUE_RANGES.get(property);
  6070. if (clamped === undefined) return false;
  6071. if (!equalsLowerCase(unit, clamped[0])) return true;
  6072. const value = Number(number);
  6073. return value < clamped[1] || value > clamped[2];
  6074. };
  6075. /**
  6076. * The same, for a term the math fold is about to write in place of its
  6077. * `calc()`.
  6078. * @param {string} term the folded term
  6079. * @returns {boolean} true when writing it bare would change the declaration
  6080. */
  6081. const _foldLosesClamp = (term) => {
  6082. if (_valueDeclaration === null) return false;
  6083. const property = toLowerCaseIfNeeded(A.name(_valueDeclaration));
  6084. if (!CLAMPED_VALUE_RANGES.has(property)) return false;
  6085. const match = _TERM_VALUE_RE.exec(term);
  6086. return match === null || _losesClamp(property, match[1], match[2]);
  6087. };
  6088. /**
  6089. * Put back the fraction that keeps a number a `<number>` token, in the spelling
  6090. * the rest of the printer uses — one fractional digit, and no leading zero.
  6091. * @param {string} num a normalized number with no fraction left
  6092. * @returns {string} the same value, still spelled as a `<number>`
  6093. */
  6094. const _keepFraction = (num) => {
  6095. const out = `${num}.0`;
  6096. if (out.charCodeAt(0) === CC_0) return out.slice(1);
  6097. return out.charCodeAt(0) === CC_HYPHEN_MINUS && out.charCodeAt(1) === CC_0
  6098. ? `-${out.slice(2)}`
  6099. : out;
  6100. };
  6101. /**
  6102. * Normalize a number / dimension / percentage token: normalize the numeric part,
  6103. * then round it and reach for a shorter equal unit. Neither is done inside a
  6104. * `@supports` prelude, where the declaration is being tested rather than applied
  6105. * — an engine may read `px` and not `pc`. Angles keep every digit: `rotate()`
  6106. * runs its argument through trig, which amplifies a truncated one.
  6107. * @param {string} text the token's source text
  6108. * @returns {string} the normalized token
  6109. */
  6110. const _normalizeNumericToken = (text) => {
  6111. const end = _numberEnd(text);
  6112. // A unit identifier matches ASCII case-insensitively, so `1PX` is `1px` — and
  6113. // the three units spelled with a capital keep the spelling everything writes.
  6114. let unit = text.slice(end);
  6115. // One fold of the unit for both jobs below — how it is printed, and whether
  6116. // it is an angle. `toLowerCase` hands back the string it was given where
  6117. // nothing folds, so a unit already lowercase is the same object.
  6118. const lowered = unit.toLowerCase();
  6119. // A substituted value is handed back as the tokens it was written as, so the
  6120. // spelling there is the author's; `lowered` still answers the angle question.
  6121. if (lowered !== unit && !_inSubstitutedValue) {
  6122. // Only a unit that carried a capital can have a canonical spelling to look
  6123. // up, so the table is read for the shouted units alone.
  6124. const folded = asciiLowerCaseName(unit);
  6125. const canonical = CANONICAL_NAMES.get(folded);
  6126. unit = canonical === undefined ? folded : canonical;
  6127. }
  6128. if (!_transforms.shortenNumbers) return text.slice(0, end) + unit;
  6129. const num = _normalizeNumber(text.slice(0, end));
  6130. // An all-zero fraction still makes this a `<number>`, not an `<integer>`
  6131. // (`grid-row:1.0` computes `auto`). Property read last, it is the costlier one.
  6132. const dot = text.indexOf(".");
  6133. if (
  6134. unit === "" &&
  6135. dot !== -1 &&
  6136. dot < end &&
  6137. !num.includes(".") &&
  6138. _inIntegerProperty()
  6139. ) {
  6140. return _keepFraction(num);
  6141. }
  6142. // `round()`, `mod()` and `rem()` are step functions of their arguments, so a
  6143. // rewrite that holds everywhere else does not hold in one: `4.5cm` and `45mm`
  6144. // are the same length, and headless Chromium reads `round(down,4.5cm,1.5cm)`
  6145. // as `3cm` but `round(down,45mm,15mm)` as `4.5cm`.
  6146. if (_inSupportsPrelude || _inCustomProperty || _steppedFunctionDepth !== 0) {
  6147. return num + unit;
  6148. }
  6149. if (ANGLE_UNITS.has(lowered)) return num + unit;
  6150. return _convertUnit(_roundSignificant(num), unit);
  6151. };
  6152. // One operand as the value printer leaves it: a number, a dimension or a
  6153. // percentage. Sticky, so the tokenizer walks the expression without slicing it.
  6154. const _CALC_OPERAND = /(?:\d*\.\d+|\d+)(?:e[+-]?\d+)?(%|[a-z]+)?/iy;
  6155. // `calc(` opening a nested expression. The inner one already printed, so it
  6156. // arrives here as text and is treated as the parentheses it is.
  6157. const _CALC_OPEN = /calc\(/iy;
  6158. /**
  6159. * The key a unit accumulates under. Units fixed against each other share their
  6160. * group's key and are counted in its base unit, so `1in + 1px` is one term;
  6161. * everything else keys on the unit itself, so `1em + 1px` stays two and is
  6162. * declined below.
  6163. * @param {string} unit the operand's unit, `""` for a plain number
  6164. * @returns {[string, number]} the key and how many base units one of it is
  6165. */
  6166. const _calcUnitKey = (unit) => {
  6167. if (unit === "") return ["", 1];
  6168. const lower = unit.toLowerCase();
  6169. const absolute = ABSOLUTE_UNIT_SCALE.get(lower);
  6170. return absolute === undefined ? [lower, 1] : [absolute[0], absolute[1]];
  6171. };
  6172. /**
  6173. * The units an expression was written with, which a gated length collapse may
  6174. * still print back into.
  6175. * @param {{ type: string, value: number, unit: string }[]} tokens the tokens
  6176. * @returns {Set<string>} the lowercased units
  6177. */
  6178. const _writtenUnits = (tokens) => {
  6179. /** @type {Set<string>} */
  6180. const units = new Set();
  6181. for (const token of tokens) {
  6182. if (token.unit !== "") units.add(toLowerCaseIfNeeded(token.unit));
  6183. }
  6184. return units;
  6185. };
  6186. /**
  6187. * Tokenize one printed `calc()` body.
  6188. * @param {string} text the body
  6189. * @returns {{ type: string, value: number, unit: string }[] | null} the tokens, or `null` when something here is not arithmetic
  6190. */
  6191. const _tokenizeCalc = (text) => {
  6192. /** @type {{ type: string, value: number, unit: string }[]} */
  6193. const tokens = [];
  6194. let i = 0;
  6195. let spaced = false;
  6196. while (i < text.length) {
  6197. const c = text[i];
  6198. if (c === " ") {
  6199. spaced = true;
  6200. i++;
  6201. continue;
  6202. }
  6203. if (c === "(" || c === ")") {
  6204. tokens.push({ type: c, value: 0, unit: "" });
  6205. spaced = false;
  6206. i++;
  6207. continue;
  6208. }
  6209. if (c === "*" || c === "/" || c === ",") {
  6210. tokens.push({ type: c, value: 0, unit: "" });
  6211. spaced = false;
  6212. i++;
  6213. continue;
  6214. }
  6215. // CSS Values 4 §10.1: `+` and `-` are operators only with whitespace on
  6216. // both sides. Without it the sign belongs to the number, and two operands
  6217. // with no operator between them is not an expression at all.
  6218. if ((c === "+" || c === "-") && spaced && text[i + 1] === " ") {
  6219. tokens.push({ type: c, value: 0, unit: "" });
  6220. spaced = false;
  6221. i++;
  6222. continue;
  6223. }
  6224. _CALC_OPEN.lastIndex = i;
  6225. if (_CALC_OPEN.test(text)) {
  6226. tokens.push({ type: "(", value: 0, unit: "" });
  6227. spaced = false;
  6228. i = _CALC_OPEN.lastIndex;
  6229. continue;
  6230. }
  6231. let sign = 1;
  6232. if (c === "+" || c === "-") {
  6233. if (c === "-") sign = -1;
  6234. i++;
  6235. }
  6236. _CALC_OPERAND.lastIndex = i;
  6237. const match = _CALC_OPERAND.exec(text);
  6238. if (match === null || match.index !== i) return null;
  6239. const unit = match[1] === undefined ? "" : match[1];
  6240. const value = Number(match[0].slice(0, match[0].length - unit.length));
  6241. if (!Number.isFinite(value)) return null;
  6242. tokens.push({ type: "value", value: sign * value, unit });
  6243. spaced = false;
  6244. i = _CALC_OPERAND.lastIndex;
  6245. }
  6246. return tokens;
  6247. };
  6248. /**
  6249. * Evaluate a tokenized expression into `key -> coefficient`, the sum CSS Values
  6250. * 4 §10.11 reduces a calculation to.
  6251. * @param {{ type: string, value: number, unit: string }[]} tokens the tokens
  6252. * @param {{ at: number }} cursor the read position, carried through the recursion
  6253. * @returns {Map<string, number> | null} the sum, or `null` when it cannot be evaluated exactly
  6254. */
  6255. const _evaluateCalcSum = (tokens, cursor) => {
  6256. const sum = _evaluateCalcProduct(tokens, cursor);
  6257. if (sum === null) return null;
  6258. for (;;) {
  6259. const token = tokens[cursor.at];
  6260. if (token === undefined || (token.type !== "+" && token.type !== "-")) {
  6261. return sum;
  6262. }
  6263. cursor.at++;
  6264. const right = _evaluateCalcProduct(tokens, cursor);
  6265. if (right === null) return null;
  6266. const sign = token.type === "+" ? 1 : -1;
  6267. for (const [key, coefficient] of right) {
  6268. const scaled = exactMultiply(coefficient, sign);
  6269. if (scaled === null) return null;
  6270. const previous = sum.get(key);
  6271. if (previous === undefined) {
  6272. sum.set(key, scaled);
  6273. continue;
  6274. }
  6275. const added = exactAdd(previous, scaled);
  6276. if (added === null) return null;
  6277. sum.set(key, added);
  6278. }
  6279. }
  6280. };
  6281. /**
  6282. * `<calc-product>`: a chain of `*` and `/`. The grammar takes only a `<number>`
  6283. * on the right of a `/`, and a product needs one side to be a plain number for
  6284. * the result to stay a sum of the units already there.
  6285. * @param {{ type: string, value: number, unit: string }[]} tokens the tokens
  6286. * @param {{ at: number }} cursor the read position
  6287. * @returns {Map<string, number> | null} the sum, or `null`
  6288. */
  6289. const _evaluateCalcProduct = (tokens, cursor) => {
  6290. let sum = _evaluateCalcValue(tokens, cursor);
  6291. if (sum === null) return null;
  6292. for (;;) {
  6293. const token = tokens[cursor.at];
  6294. if (token === undefined || (token.type !== "*" && token.type !== "/")) {
  6295. return sum;
  6296. }
  6297. cursor.at++;
  6298. const right = _evaluateCalcValue(tokens, cursor);
  6299. if (right === null) return null;
  6300. if (token.type === "/") {
  6301. const divisor = right.get("");
  6302. if (divisor === undefined || right.size !== 1) return null;
  6303. const out = new Map();
  6304. for (const [key, coefficient] of sum) {
  6305. const quotient = exactDivide(coefficient, divisor);
  6306. if (quotient === null) return null;
  6307. out.set(key, quotient);
  6308. }
  6309. sum = out;
  6310. continue;
  6311. }
  6312. const leftNumber = sum.size === 1 ? sum.get("") : undefined;
  6313. const rightNumber = right.size === 1 ? right.get("") : undefined;
  6314. // One side has to be a plain number; two dimensions multiply into a type no
  6315. // property here accepts.
  6316. const factor = rightNumber !== undefined ? rightNumber : leftNumber;
  6317. const other = rightNumber !== undefined ? sum : right;
  6318. if (factor === undefined) return null;
  6319. const out = new Map();
  6320. for (const [key, coefficient] of other) {
  6321. const product = exactMultiply(coefficient, factor);
  6322. if (product === null) return null;
  6323. out.set(key, product);
  6324. }
  6325. sum = out;
  6326. }
  6327. };
  6328. /**
  6329. * `<calc-value>`: an operand, or a parenthesized sum.
  6330. * @param {{ type: string, value: number, unit: string }[]} tokens the tokens
  6331. * @param {{ at: number }} cursor the read position
  6332. * @returns {Map<string, number> | null} the sum, or `null`
  6333. */
  6334. const _evaluateCalcValue = (tokens, cursor) => {
  6335. const token = tokens[cursor.at];
  6336. if (token === undefined) return null;
  6337. if (token.type === "(") {
  6338. cursor.at++;
  6339. const inner = _evaluateCalcSum(tokens, cursor);
  6340. if (inner === null) return null;
  6341. const close = tokens[cursor.at];
  6342. if (close === undefined || close.type !== ")") return null;
  6343. cursor.at++;
  6344. return inner;
  6345. }
  6346. if (token.type !== "value") return null;
  6347. cursor.at++;
  6348. const [key, scale] = _calcUnitKey(token.unit);
  6349. const value = exactMultiply(token.value, scale);
  6350. return value === null ? null : new Map([[key, value]]);
  6351. };
  6352. /**
  6353. * Round a folded result the way an authored number is rounded — the fold prints
  6354. * a double back in full, and `6 / 10 - 0.375` is `.22499999999999998`. The
  6355. * exclusions are the token printer's own: a `@supports` prelude and a custom
  6356. * property keep what was written, a stepped function is a step of its argument,
  6357. * and an angle keeps every digit because `rotate()` runs it through trig.
  6358. * @param {string} text the printed numeric text
  6359. * @param {string} unit the unit it carries, empty or `%` for none
  6360. * @returns {string} the rounded text, or `text`
  6361. */
  6362. const _roundCalcResult = (text, unit) => {
  6363. if (_inSupportsPrelude || _inCustomProperty || _steppedFunctionDepth !== 0) {
  6364. return text;
  6365. }
  6366. return ANGLE_UNITS.has(toLowerCaseIfNeeded(unit))
  6367. ? text
  6368. : _roundSignificant(text);
  6369. };
  6370. /**
  6371. * Print one collapsed term back.
  6372. * @param {string} key the sum's one key
  6373. * @param {number} coefficient its value, in the key's base unit
  6374. * @param {Set<string>} written the units the expression was written with
  6375. * @returns {string | null} the printed value, or `null` when it does not print back exactly
  6376. */
  6377. const _printCalcTerm = (key, coefficient, written) => {
  6378. if (key === "" || key === "%") {
  6379. const text = _normalizeNumber(String(coefficient));
  6380. return Number(text) === coefficient
  6381. ? _roundCalcResult(text, key) + key
  6382. : null;
  6383. }
  6384. const base = UNIT_GROUP_BASE.get(key);
  6385. // A unit outside the conversion table counts in itself, so the coefficient is
  6386. // already what it prints as.
  6387. if (base === undefined) {
  6388. const text = _normalizeNumber(String(coefficient));
  6389. return Number(text) === coefficient
  6390. ? _roundCalcResult(text, key) + key
  6391. : null;
  6392. }
  6393. // Counted in the group's base unit, so every unit of the group is a candidate
  6394. // and each is divided into directly: `1cm + 1mm` is exactly `11mm`, and
  6395. // reaching it through `px` first would lose it — no `px` count equals it.
  6396. let best = null;
  6397. const lengthGated = key === "length" && !_convertLengthUnits;
  6398. for (const [candidate, to] of ABSOLUTE_UNIT_SCALE) {
  6399. if (to[0] !== key) continue;
  6400. // Gated, a sum may still collapse into a unit it was written with — that
  6401. // introduces none — but not into one reached only to save bytes.
  6402. if (lengthGated && candidate !== base[0] && !written.has(candidate)) {
  6403. continue;
  6404. }
  6405. if (candidate !== base[0] && !UNIT_CONVERSION_TARGETS.has(candidate)) {
  6406. continue;
  6407. }
  6408. const value = exactDivide(coefficient, to[1]);
  6409. if (value === null) continue;
  6410. const text = _normalizeNumber(String(value));
  6411. if (Number(text) !== value || text.includes("e") || text.includes("E")) {
  6412. continue;
  6413. }
  6414. const dimension = _roundCalcResult(text, candidate) + candidate;
  6415. if (best === null || dimension.length < best.length) best = dimension;
  6416. }
  6417. return best;
  6418. };
  6419. /**
  6420. * Print a whole reduced sum back as a `calc()` body. A sum still holding two
  6421. * keys is one an engine resolves against layout (a percentage against a length,
  6422. * an `em` against a `px`), and the terms of it are printed in the order they
  6423. * were first written. A zero term is kept rather than dropped: which keys may be
  6424. * added to which is a type rule, and dropping one can make an expression an
  6425. * engine rejects into one it accepts (`calc(1px + 1deg - 1deg)`).
  6426. * @param {Map<string, number>} sum the reduced sum
  6427. * @param {Set<string>} written the units the expression was written with
  6428. * @returns {string | null} the body, or `null` when a term does not print exactly
  6429. */
  6430. const _printCalcSum = (sum, written) => {
  6431. let text = "";
  6432. for (const [key, coefficient] of sum) {
  6433. const term = _printCalcTerm(
  6434. key,
  6435. text === "" ? coefficient : Math.abs(coefficient),
  6436. written
  6437. );
  6438. if (term === null) return null;
  6439. text += text === "" ? term : `${coefficient < 0 ? " - " : " + "}${term}`;
  6440. }
  6441. return text === "" ? null : text;
  6442. };
  6443. /**
  6444. * Split a tokenized argument list on its top-level commas.
  6445. * @param {{ type: string, value: number, unit: string }[]} tokens the tokens
  6446. * @returns {{ type: string, value: number, unit: string }[][]} one list per argument
  6447. */
  6448. const _splitMathArguments = (tokens) => {
  6449. /** @type {{ type: string, value: number, unit: string }[][]} */
  6450. const args = [[]];
  6451. let depth = 0;
  6452. for (const token of tokens) {
  6453. if (token.type === "(") {
  6454. depth++;
  6455. } else if (token.type === ")") {
  6456. depth--;
  6457. } else if (token.type === "," && depth === 0) {
  6458. args.push([]);
  6459. continue;
  6460. }
  6461. args[args.length - 1].push(token);
  6462. }
  6463. return args;
  6464. };
  6465. /**
  6466. * Split on the commas at the top of a function body, so a nested call's own
  6467. * arguments stay in the piece that holds it.
  6468. * @param {string} text the body between one call's parentheses
  6469. * @returns {string[]} its arguments, still as written
  6470. */
  6471. const _splitTopLevelArguments = (text) => {
  6472. /** @type {string[]} */
  6473. const parts = [];
  6474. let depth = 0;
  6475. let start = 0;
  6476. for (let i = 0; i < text.length; i++) {
  6477. const code = text.charCodeAt(i);
  6478. if (code === 0x28) {
  6479. depth++;
  6480. } else if (code === 0x29) {
  6481. depth--;
  6482. } else if (code === 0x2c && depth === 0) {
  6483. parts.push(text.slice(start, i));
  6484. start = i + 1;
  6485. }
  6486. }
  6487. parts.push(text.slice(start));
  6488. return parts;
  6489. };
  6490. /**
  6491. * Reduce the `<calc-sum>` arguments of a call the whole-call fold cannot read,
  6492. * leaving every other argument as written. `calc-size(auto, 10px + 5px)` is
  6493. * `calc-size(auto,15px)`: the basis is not an expression, so only the size is
  6494. * touched.
  6495. * @param {string} fn the lowercased function name
  6496. * @param {string} inner the text between its parentheses
  6497. * @returns {string | null} the rewritten body, or `null`
  6498. */
  6499. const _reduceMathArguments = (fn, inner) => {
  6500. if (!_transforms.reduceFunctions) return null;
  6501. const positions = MATH_FUNCTION_SUM_ARGUMENTS.get(fn);
  6502. // The same rewrite a stepped function's arguments refuse.
  6503. if (positions === undefined || _steppedFunctionDepth > 0) return null;
  6504. const parts = _splitTopLevelArguments(inner);
  6505. let changed = false;
  6506. for (const position of positions) {
  6507. if (position >= parts.length) return null;
  6508. const tokens = _tokenizeCalc(parts[position]);
  6509. if (tokens === null) return null;
  6510. const cursor = { at: 0 };
  6511. const sum = _evaluateCalcSum(tokens, cursor);
  6512. if (sum === null || cursor.at !== tokens.length) return null;
  6513. const text = _printCalcSum(sum, _writtenUnits(tokens));
  6514. if (text === null) return null;
  6515. if (text !== parts[position].trim()) changed = true;
  6516. parts[position] = text;
  6517. }
  6518. return changed ? parts.map((part) => part.trim()).join(",") : null;
  6519. };
  6520. /**
  6521. * Fold a printed math function body to the one value it is equal to. Only a
  6522. * fully collapsed result is returned: an expression still holding two units (a
  6523. * percentage against a length, an `em` against a `px`) resolves against layout
  6524. * and has to stay written out.
  6525. * @param {string} fn the lowercased function name
  6526. * @param {string} inner the printed body
  6527. * @returns {string | null} the value, or `null` to leave the expression as it is
  6528. */
  6529. const _foldMathFunction = (fn, inner) => {
  6530. if (!_transforms.reduceFunctions) return null;
  6531. const arity = MATH_FUNCTION_ARITY.get(fn);
  6532. if (arity === undefined) return null;
  6533. // A fold standing in a stepped function's argument prints in whichever unit
  6534. // is shortest, which is the rewrite that function's own arguments refuse:
  6535. // Chromium reads `round(down,4.5cm,1.5cm)` and `round(down,45mm,15mm)` as
  6536. // different lengths. The stepped function's own result is not an argument of
  6537. // one, so it is the depth above this call that decides.
  6538. if (_steppedFunctionDepth - (STEPPED_FUNCTIONS.has(fn) ? 1 : 0) > 0) {
  6539. return null;
  6540. }
  6541. // A leading keyword the grammar offers (`round(down, …)`) is not an
  6542. // expression, so it comes off before the arguments are read.
  6543. let keyword = "";
  6544. let body = inner;
  6545. const choices = MATH_FUNCTION_KEYWORDS.get(fn);
  6546. if (choices !== undefined) {
  6547. const comma = inner.indexOf(",");
  6548. if (comma !== -1) {
  6549. const head = toLowerCaseIfNeeded(inner.slice(0, comma).trim());
  6550. if (choices.includes(head)) {
  6551. keyword = head;
  6552. body = inner.slice(comma + 1);
  6553. }
  6554. }
  6555. }
  6556. const tokens = _tokenizeCalc(body);
  6557. if (tokens === null) return null;
  6558. const args = _splitMathArguments(tokens);
  6559. if (args.length < arity[0] || args.length > arity[1]) return null;
  6560. /** @type {Map<string, number>[]} */
  6561. const sums = [];
  6562. for (const argument of args) {
  6563. const cursor = { at: 0 };
  6564. const sum = _evaluateCalcSum(argument, cursor);
  6565. if (sum === null || cursor.at !== argument.length) return null;
  6566. sums.push(sum);
  6567. }
  6568. // `calc()` is the one that is not a single value: it is whatever sum its
  6569. // argument reduced to, which may still hold two units.
  6570. const written = _writtenUnits(tokens);
  6571. if (fn === "calc") return _printCalcSum(sums[0], written);
  6572. const fold = MATH_FUNCTION_FOLD.get(fn);
  6573. if (fold === undefined) return null;
  6574. const argument = fold.read(sums);
  6575. if (argument === null) return null;
  6576. const value = fold.apply(argument[1], keyword, fold.table);
  6577. if (value === null) return null;
  6578. return _printCalcTerm(
  6579. fold.result === "same" ? argument[0] : fold.result,
  6580. value,
  6581. written
  6582. );
  6583. };
  6584. const _HEX = "0123456789abcdef";
  6585. /**
  6586. * @param {number} n byte value 0..255
  6587. * @returns {string} its two lowercase hex digits
  6588. */
  6589. const _hex2 = (n) => _HEX[(n >> 4) & 15] + _HEX[n & 15];
  6590. /**
  6591. * The shortest opaque color text for (r, g, b): a named color where one is
  6592. * shorter than the hex (`RGB_TO_NAME` holds only those), else a collapsed 3-hex
  6593. * or a 6-hex.
  6594. * @param {number} r red 0..255
  6595. * @param {number} g green 0..255
  6596. * @param {number} b blue 0..255
  6597. * @returns {string} the shortest form
  6598. */
  6599. const _shortestColor = (r, g, b) => {
  6600. const name = RGB_TO_NAME.get((r << 16) | (g << 8) | b);
  6601. if (name !== undefined) return name;
  6602. const rr = _hex2(r);
  6603. const gg = _hex2(g);
  6604. const bb = _hex2(b);
  6605. return rr[0] === rr[1] && gg[0] === gg[1] && bb[0] === bb[1]
  6606. ? `#${rr[0]}${gg[0]}${bb[0]}`
  6607. : `#${rr}${gg}${bb}`;
  6608. };
  6609. /**
  6610. * The shortest text for (r, g, b, a) with an alpha below 1 — a 4- or 8-digit hex,
  6611. * always shorter than the `rgba()` it came from. No named color has an alpha.
  6612. * @param {number} r red 0..255
  6613. * @param {number} g green 0..255
  6614. * @param {number} b blue 0..255
  6615. * @param {number} a alpha 0..255
  6616. * @returns {string} the shortest form
  6617. */
  6618. const _shortestAlphaColor = (r, g, b, a) => {
  6619. const rr = _hex2(r);
  6620. const gg = _hex2(g);
  6621. const bb = _hex2(b);
  6622. const aa = _hex2(a);
  6623. return rr[0] === rr[1] &&
  6624. gg[0] === gg[1] &&
  6625. bb[0] === bb[1] &&
  6626. aa[0] === aa[1]
  6627. ? `#${rr[0]}${gg[0]}${bb[0]}${aa[0]}`
  6628. : `#${rr}${gg}${bb}${aa}`;
  6629. };
  6630. /**
  6631. * Minify a hash token (`#…`) when it is a hex color: opaque (3/6 digits, and
  6632. * 4/8 whose alpha is opaque) → the shortest color; with a real alpha (4/8
  6633. * digits) → lowercase + collapse pairs (kept hex). Returns null when it is not a
  6634. * hex color — e.g. a selector id — so it is
  6635. * left verbatim.
  6636. * @param {string} text the hash token text
  6637. * @returns {string | null} the minified hex/name, or null
  6638. */
  6639. const _minifyHash = (text) => {
  6640. if (!_transforms.shortenColors) return null;
  6641. const body = text.slice(1);
  6642. const n = body.length;
  6643. if (n !== 3 && n !== 4 && n !== 6 && n !== 8) return null;
  6644. for (let i = 0; i < n; i++) {
  6645. if (!_isHexDigit(body.charCodeAt(i))) return null;
  6646. }
  6647. const low = body.toLowerCase();
  6648. if (n === 3) {
  6649. return _shortestColor(
  6650. Number.parseInt(low[0] + low[0], 16),
  6651. Number.parseInt(low[1] + low[1], 16),
  6652. Number.parseInt(low[2] + low[2], 16)
  6653. );
  6654. }
  6655. if (n === 6) {
  6656. return _shortestColor(
  6657. Number.parseInt(low.slice(0, 2), 16),
  6658. Number.parseInt(low.slice(2, 4), 16),
  6659. Number.parseInt(low.slice(4, 6), 16)
  6660. );
  6661. }
  6662. // A fully opaque alpha says nothing, and the form without it asks nothing of
  6663. // the target's hex-alpha support either.
  6664. if (n === 4) {
  6665. if (low[3] !== "f") return `#${low}`;
  6666. return _shortestColor(
  6667. Number.parseInt(low[0] + low[0], 16),
  6668. Number.parseInt(low[1] + low[1], 16),
  6669. Number.parseInt(low[2] + low[2], 16)
  6670. );
  6671. }
  6672. if (low[6] === "f" && low[7] === "f") {
  6673. return _shortestColor(
  6674. Number.parseInt(low.slice(0, 2), 16),
  6675. Number.parseInt(low.slice(2, 4), 16),
  6676. Number.parseInt(low.slice(4, 6), 16)
  6677. );
  6678. }
  6679. return low[0] === low[1] &&
  6680. low[2] === low[3] &&
  6681. low[4] === low[5] &&
  6682. low[6] === low[7]
  6683. ? `#${low[0]}${low[2]}${low[4]}${low[6]}`
  6684. : `#${low}`;
  6685. };
  6686. /**
  6687. * @param {number} x a number
  6688. * @returns {number} `x` rounded and clamped to a 0..255 byte
  6689. */
  6690. const _clamp255 = (x) => (x < 0 ? 0 : x > 255 ? 255 : Math.round(x));
  6691. /**
  6692. * Minify an `rgb()` / `rgba()` color function to the shortest opaque form. Only
  6693. * rgb/rgba (exact integer / percent math); returns null for anything else —
  6694. * a different function, non-numeric args, or a partly-transparent alpha —
  6695. * leaving it as its normalized function form. Transparent black collapses to the
  6696. * `transparent` keyword (identical value, universally supported, shorter). hsl is
  6697. * intentionally not converted: its hue math can round to a different byte, which
  6698. * would change the color.
  6699. * @param {string} fn the lowercased function name
  6700. * @param {string} inner the already-joined argument text
  6701. * @param {boolean} hexAlpha whether the target reads a 4-/8-digit hex (see `output.environment`)
  6702. * @returns {string | null} the shortest color, or null to keep the function
  6703. */
  6704. const _minifyColorFunction = (fn, inner, hexAlpha) => {
  6705. if ((fn !== "rgb" && fn !== "rgba") || !_transforms.shortenColors) {
  6706. return null;
  6707. }
  6708. // Cut on the separators `rgb()` allows — whitespace, `,`, and the `/` before
  6709. // the alpha — in one pass. Every color in the sheet reaches this, and the
  6710. // regex form built a replaced copy of the arguments, a split array and a
  6711. // filtered copy of that to reach the same few strings.
  6712. /** @type {string[]} */
  6713. const args = [];
  6714. let from = -1;
  6715. for (let i = 0; i <= inner.length; i++) {
  6716. // Spelled out rather than asked for: a call per character loses to the
  6717. // regex engine outright, and anything looser than the five whitespace
  6718. // code points parts a token CSS does not — a control character between
  6719. // two numbers leaves an invalid declaration, which must stay invalid.
  6720. const c = inner.charCodeAt(i);
  6721. if (
  6722. i === inner.length ||
  6723. c === CC_SPACE ||
  6724. c === CC_TAB ||
  6725. c === CC_LINE_FEED ||
  6726. c === CC_CARRIAGE_RETURN ||
  6727. c === CC_FORM_FEED ||
  6728. c === CC_COMMA ||
  6729. c === CC_SOLIDUS
  6730. ) {
  6731. if (from === -1) continue;
  6732. // A fifth argument is not a color this rewrite is defined for.
  6733. if (args.length === 4) return null;
  6734. args.push(inner.slice(from, i));
  6735. from = -1;
  6736. } else if (from === -1) {
  6737. from = i;
  6738. }
  6739. }
  6740. if (args.length !== 3 && args.length !== 4) return null;
  6741. /** @type {number[]} */
  6742. const channels = [];
  6743. let alpha = 1;
  6744. let percentChannelCount = 0;
  6745. for (let i = 0; i < args.length; i++) {
  6746. const s = args[i];
  6747. if (!/^[+-]?(?:\d+\.?\d*|\.\d+)%?$/.test(s)) return null;
  6748. const pct = s.endsWith("%");
  6749. const v = Number.parseFloat(pct ? s.slice(0, -1) : s);
  6750. if (Number.isNaN(v)) return null;
  6751. if (i === 3) {
  6752. alpha = pct ? v / 100 : v;
  6753. } else {
  6754. if (pct) percentChannelCount++;
  6755. channels.push(_clamp255(pct ? (v * 255) / 100 : v));
  6756. }
  6757. }
  6758. // Mixed number/percentage channels are invalid CSS (ignored by browsers);
  6759. // rewriting would activate a dead declaration.
  6760. if (percentChannelCount !== 0 && percentChannelCount !== 3) return null;
  6761. // Fully transparent *black* only — `transparent` is `rgba(0,0,0,0)`, so a
  6762. // non-black transparent (e.g. `rgba(255,0,0,0)`) is a different value.
  6763. if (
  6764. alpha === 0 &&
  6765. channels[0] === 0 &&
  6766. channels[1] === 0 &&
  6767. channels[2] === 0
  6768. ) {
  6769. return "transparent";
  6770. }
  6771. // A hex alpha is the same color: the engine quantizes the channel to 8 bits,
  6772. // so the byte a decimal alpha lands on is the one it already stores —
  6773. // `.5`, `.501`, `.502` and `.5019` all compute to `rgba(0,0,0,0.5)` in
  6774. // headless Chromium, and every one of the 256 bytes round-trips.
  6775. if (alpha < 1) {
  6776. return hexAlpha
  6777. ? _shortestAlphaColor(
  6778. channels[0],
  6779. channels[1],
  6780. channels[2],
  6781. _clamp255(alpha * 255)
  6782. )
  6783. : null;
  6784. }
  6785. return _shortestColor(channels[0], channels[1], channels[2]);
  6786. };
  6787. // A browser list's parse, and each list's per-property prefix decision, memoized
  6788. // on the list identity: one process minifies for one target, so the work is done
  6789. // once and every later lookup is a `Map.get`.
  6790. // A build minifies every asset against one browserslist selection, but in a
  6791. // worker each asset arrives with a freshly deserialized array — so the memo is
  6792. // keyed on the joined text, and one selection at a time is all it ever holds.
  6793. /** @type {string[] | null} */
  6794. let _browsersArray = null;
  6795. /** @type {string | null} */
  6796. let _browsersKey = null;
  6797. /** @type {(number[] | undefined)[] | null} */
  6798. let _parsedBrowsersMemo = null;
  6799. // Each axis' table -> its per-construct decision for the selection in hand. Kept
  6800. // per table rather than under one tagged key, so a lookup allocates no string.
  6801. /** @type {Map<Map<string, [string, number][]>, Map<string, Set<string> | null>>} */
  6802. let _neededPrefixMemo = new Map();
  6803. // Where a browser's version sits in a `SUPPORT_PROFILES` row, which is also the
  6804. // slot a prefix window names it by and the one the parsed selection holds it in.
  6805. // Built from the order the rows are stated in, so the three cannot drift apart.
  6806. /** @type {Map<string, number>} */
  6807. const SUPPORT_BROWSER_SLOT = new Map(
  6808. SUPPORT_BROWSERS.map((browser, at) => [browser, at])
  6809. );
  6810. /**
  6811. * Parse a browserslist selection into the versions selected for each browser
  6812. * slot, memoized on the selection itself, and make it the one this parse
  6813. * prefixes for. Null where it resolves to no browser at all, which is not a
  6814. * target to prefix for.
  6815. * @param {string[]} browsers the browserslist selection
  6816. * @returns {void}
  6817. */
  6818. const _useBrowsers = (browsers) => {
  6819. // The same selection usually arrives as the same array — one resolution per
  6820. // target serves the whole build — so the text it joins to is read only where
  6821. // it does not, which is a worker handed each asset's own copy.
  6822. if (browsers === _browsersArray) {
  6823. _prefixBrowsers = _parsedBrowsersMemo;
  6824. return;
  6825. }
  6826. _browsersArray = browsers;
  6827. const key = browsers.join(",");
  6828. if (key !== _browsersKey) {
  6829. _browsersKey = key;
  6830. _neededPrefixMemo = new Map();
  6831. // By the slot the tables name a browser with, so neither the profile rows
  6832. // nor the prefix windows carry a name to look one up by.
  6833. /** @type {(number[] | undefined)[]} */
  6834. const parsed = Array.from({ length: SUPPORT_BROWSERS.length });
  6835. let named = 0;
  6836. for (const entry of browsers) {
  6837. const space = entry.indexOf(" ");
  6838. const name = space === -1 ? entry : entry.slice(0, space);
  6839. const version =
  6840. space === -1 ? null : _encodeBrowserVersion(entry.slice(space + 1));
  6841. // A browserslist name no compat dataset covers (`op_mini`, `and_uc`,
  6842. // `and_qq`, `baidu`, `kaios`, `bb`), or a version that did not parse, is
  6843. // skipped — the same browsers lightningcss's target mapping drops, so both
  6844. // minifiers prefix for the same selection.
  6845. const slot =
  6846. version === null ? undefined : SUPPORT_BROWSER_SLOT.get(name);
  6847. if (slot === undefined) continue;
  6848. // Every selected version is kept: a prefix window is an interval, so an
  6849. // older selection outside it does not answer for a newer one inside.
  6850. const versions = parsed[slot];
  6851. if (versions === undefined) {
  6852. parsed[slot] = [/** @type {number} */ (version)];
  6853. named++;
  6854. } else {
  6855. versions.push(/** @type {number} */ (version));
  6856. }
  6857. }
  6858. _parsedBrowsersMemo = named === 0 ? null : parsed;
  6859. }
  6860. _prefixBrowsers = _parsedBrowsersMemo;
  6861. };
  6862. /**
  6863. * A browserslist version to the same `major * 100000 + minor` integer the table
  6864. * is keyed in. A range (`10.0-10.2`) takes its low end, Safari `TP` is newest,
  6865. * and `all` (Opera Mini) is oldest (`0`).
  6866. * @param {string} version the version part of a `"name version"` entry
  6867. * @returns {number | null} the encoded version, or null when unreadable
  6868. */
  6869. const _encodeBrowserVersion = (version) => {
  6870. // Newer than any real version, but below `NEVER` — a still-prefixed feature
  6871. // carries `to === NEVER`, and `TP < NEVER` must hold for TP to need it.
  6872. if (version === "TP") return NEVER - 1;
  6873. if (version === "all") return 0;
  6874. const dash = version.indexOf("-");
  6875. const low = dash === -1 ? version : version.slice(0, dash);
  6876. const dot = low.indexOf(".");
  6877. const major = Number.parseInt(dot === -1 ? low : low.slice(0, dot), 10);
  6878. if (Number.isNaN(major)) return null;
  6879. const minor = dot === -1 ? 0 : Number.parseInt(low.slice(dot + 1), 10) || 0;
  6880. return major * 100000 + minor;
  6881. };
  6882. /**
  6883. * Whether every target browser reads a CSS ability, so a spelling that needs it
  6884. * may be reached for. No selection names no browser to answer for, so the
  6885. * ability is assumed — a build with no browserslist keeps every rewrite. A
  6886. * browser the table does not name is one nothing states support for, which
  6887. * answers no.
  6888. * @param {string} feature a `SUPPORTED_FROM` key
  6889. * @returns {boolean} true when the selection reads it
  6890. */
  6891. const _targetSupports = (feature) => _readsAll(SUPPORTED_FROM.get(feature));
  6892. /**
  6893. * Whether every target browser is at or past the versions in one support
  6894. * profile, named by the row it reads.
  6895. * @param {number | undefined} profile a `SUPPORT_PROFILES` index
  6896. * @returns {boolean} true when the selection reads it
  6897. */
  6898. const _readsAll = (profile) => {
  6899. const parsed = _prefixBrowsers;
  6900. if (parsed === null) return true;
  6901. if (profile === undefined) return false;
  6902. // One row of `SUPPORT_BROWSERS.length` versions, laid end to end with the rest.
  6903. const row = profile * SUPPORT_BROWSERS.length;
  6904. for (let at = 0; at < parsed.length; at++) {
  6905. const versions = parsed[at];
  6906. if (versions === undefined) continue;
  6907. const from = SUPPORT_PROFILES[row + at];
  6908. for (let i = 0; i < versions.length; i++) {
  6909. if (versions[i] < from) return false;
  6910. }
  6911. }
  6912. return true;
  6913. };
  6914. /**
  6915. * The vendor spellings at least one target browser still needs for a construct —
  6916. * a target at version V needs spelling S when some `[browser, from, to]` of S has
  6917. * `from <= V < to`. Null when not minifying for a target (leave prefixes alone),
  6918. * the construct is never prefixed, or no target needs any of its spellings.
  6919. * @param {Map<string, [string, number][]>} table its axis' prefix table
  6920. * @param {string} name the construct name (unprefixed, lowercased)
  6921. * @returns {Set<string> | null} the needed spellings, or null
  6922. */
  6923. const _neededPrefixes = (table, name) => {
  6924. const parsed = _prefixBrowsers;
  6925. if (parsed === null) return null;
  6926. let cache = _neededPrefixMemo.get(table);
  6927. if (cache === undefined) {
  6928. cache = new Map();
  6929. _neededPrefixMemo.set(table, cache);
  6930. }
  6931. const cached = cache.get(name);
  6932. if (cached !== undefined) return cached;
  6933. const prefixes = table.get(name);
  6934. /** @type {Set<string> | null} */
  6935. let result = null;
  6936. if (prefixes !== undefined) {
  6937. const needed = new Set();
  6938. for (const [prefix, windows] of prefixes) {
  6939. // The list's own `browser, from, to` triples, in the flat table the
  6940. // starts index into.
  6941. const end = PREFIX_WINDOW_STARTS[windows + 1];
  6942. for (let at = PREFIX_WINDOW_STARTS[windows]; at < end; at += 3) {
  6943. const versions = parsed[PREFIX_WINDOWS[at]];
  6944. if (versions === undefined) continue;
  6945. const from = PREFIX_WINDOWS[at + 1];
  6946. const to = PREFIX_WINDOWS[at + 2];
  6947. let hit = false;
  6948. for (let i = 0; i < versions.length; i++) {
  6949. if (versions[i] >= from && versions[i] < to) {
  6950. hit = true;
  6951. break;
  6952. }
  6953. }
  6954. if (hit) {
  6955. needed.add(prefix);
  6956. break;
  6957. }
  6958. }
  6959. }
  6960. if (needed.size !== 0) result = needed;
  6961. }
  6962. cache.set(name, result);
  6963. return result;
  6964. };
  6965. // A vendor prefix at the start of a name (`-webkit-`, `-moz-`, `-ms-`, `-o-`,
  6966. // `-khtml-`), captured so it can be split from the construct it sits on.
  6967. const VENDOR_PREFIX = /^(-[a-z]+-)(?=[a-z])/;
  6968. /**
  6969. * The base an at-rule's vendor spelling belongs to. Every at-rule spelling is
  6970. * its base with a prefix on it, so the prefix comes off again.
  6971. * @param {string} name a prefixed at-rule name, lowercased
  6972. * @returns {string} the unprefixed name
  6973. */
  6974. const _unprefixedAtRule = (name) =>
  6975. name.slice(
  6976. /** @type {RegExpExecArray} */ (VENDOR_PREFIX.exec(name))[1].length
  6977. );
  6978. /**
  6979. * Whether a present vendor-spelled construct is dead weight: its spelling is one
  6980. * the table knows for the base construct and no target browser needs it, so an
  6981. * unprefixed sibling already covers every target.
  6982. * @param {Map<string, [string, number][]>} table its axis' prefix table
  6983. * @param {string} base the unprefixed construct name
  6984. * @param {string} spelling the vendor spelling found in its place
  6985. * @returns {boolean} true when the vendor-spelled construct can be dropped
  6986. */
  6987. const _prefixRemovable = (table, base, spelling) => {
  6988. const spellings = table.get(base);
  6989. if (spellings === undefined) return false;
  6990. if (!spellings.some(([known]) => known === spelling)) return false;
  6991. const needed = _neededPrefixes(table, base);
  6992. return needed === null || !needed.has(spelling);
  6993. };
  6994. /**
  6995. * What a block's rules have shown so far: the signatures met (`seen`), and the
  6996. * prefixed ones an unprefixed twin would make dead — as an output piece the
  6997. * writer can take back (`retractable`, for a rule written straight out), or as
  6998. * the node itself (`pending`, for a rule whose parent still has to assemble it).
  6999. * @typedef {object} PrefixScope
  7000. * @property {Set<string>} seen the signatures met, and the `signature\0prefix`
  7001. * markers of the prefixed spellings among them
  7002. * @property {Map<string, number> | null} retractable each dead-if-twinned rule's
  7003. * piece index, by the signature of the twin that would make it dead
  7004. * @property {Map<string, Node> | null} pending each dead-if-twinned nested rule,
  7005. * by that same signature
  7006. * @property {Set<Node> | null} dead the nested rules a twin has since made dead,
  7007. * read by their parent as it assembles its body
  7008. */
  7009. /**
  7010. * The rules already met in the block a rule sits in, made on first use.
  7011. * @param {Node | null} parent the block's rule, null for the stylesheet itself
  7012. * @returns {PrefixScope} the block's running sibling state
  7013. */
  7014. const _prefixScope = (parent) => {
  7015. const scopes = /** @type {Map<Node | null, PrefixScope>} */ (
  7016. _seenPrefixRules
  7017. );
  7018. let scope = scopes.get(parent);
  7019. if (scope === undefined) {
  7020. scope = { seen: new Set(), retractable: null, pending: null, dead: null };
  7021. scopes.set(parent, scope);
  7022. }
  7023. return scope;
  7024. };
  7025. /**
  7026. * Drop the prefixed rule this one is the unprefixed twin of. The pair is usually
  7027. * adjacent — every stylesheet writes the prefixed spelling first — but nothing
  7028. * in between matters: a piece stays retractable until the stylesheet ends, and a
  7029. * nested rule until its parent assembles its body.
  7030. * @param {PrefixScope} scope the block's running sibling state
  7031. * @param {string} signature the unprefixed rule's sibling signature
  7032. * @returns {void}
  7033. */
  7034. const _dropPrefixTwin = (scope, signature) => {
  7035. const retractable = scope.retractable;
  7036. if (retractable !== null) {
  7037. const at = retractable.get(signature);
  7038. if (at !== undefined) {
  7039. /** @type {PrintContext} */ (_streamWriter).retract(at);
  7040. retractable.delete(signature);
  7041. return;
  7042. }
  7043. }
  7044. const pending = scope.pending;
  7045. if (pending === null) return;
  7046. const node = pending.get(signature);
  7047. if (node === undefined) return;
  7048. pending.delete(signature);
  7049. if (scope.dead === null) scope.dead = new Set();
  7050. scope.dead.add(node);
  7051. };
  7052. /**
  7053. * Remember a prefixed rule an unprefixed twin later in its block would make dead
  7054. * weight, both ways it can be dropped: as the rule just printed, which whoever
  7055. * writes it out takes as a piece of its own, and — for a nested one — as the
  7056. * node its parent skips while assembling its body, for the parents that do.
  7057. * @param {PrefixScope} scope the block's running sibling state
  7058. * @param {string} signature the rule's sibling signature
  7059. * @param {boolean} top whether the rule is the stylesheet's own
  7060. * @returns {void}
  7061. */
  7062. const _holdPrefixTwin = (scope, signature, top) => {
  7063. _prefixDropCandidate = { node: _currentNode, signature };
  7064. if (top) return;
  7065. if (scope.pending === null) scope.pending = new Map();
  7066. scope.pending.set(signature, _currentNode);
  7067. };
  7068. /**
  7069. * An at-rule (`@keyframes`), prefixed against the target: a prefixed copy is
  7070. * prepended for each prefix a target still needs, and a prefixed rule no target
  7071. * needs is dropped once its unprefixed twin has been seen. The `@name`'s prefix
  7072. * is stripped for the sibling signature, so `@-webkit-keyframes x` and
  7073. * `@keyframes x` pair up. Siblings are the rules of the block it sits in.
  7074. * @param {CssPath} path the accessor on the at-rule
  7075. * @param {string} ruleText the rule's own serialized text
  7076. * @param {string} prelude the rule's serialized prelude (`@name …`)
  7077. * @param {PrefixScope} scope the block's running sibling state
  7078. * @param {boolean} top whether the rule is the stylesheet's own, which is the only
  7079. * one whose text is still a piece a twin can take back
  7080. * @returns {string} the rule text, with prefixed copies added or itself dropped
  7081. */
  7082. const _prefixAtRule = (path, ruleText, prelude, scope, top) => {
  7083. const seen = scope.seen;
  7084. const name = path.name().toLowerCase();
  7085. const prefixed = VENDOR_PREFIX.test(name);
  7086. const base = prefixed ? _unprefixedAtRule(name) : name;
  7087. if (!PREFIXED_AT_RULES.has(base)) return ruleText;
  7088. // This rule's cross-prefix identity: the prelude with the `@name` folded to its
  7089. // unprefixed, lowercased spelling, so a cased `@Keyframes` and a prefixed
  7090. // `@-webkit-keyframes` share it (at-rule names are case-insensitive).
  7091. const signature = `@${base}${prelude.slice(1 + name.length)}`;
  7092. if (prefixed) {
  7093. const removable = _prefixRemovable(PREFIXED_AT_RULES, base, name);
  7094. if (seen.has(signature) && removable) return "";
  7095. seen.add(`${signature}\0${name}`);
  7096. // Its twin may still be a later rule, which is where it is dropped.
  7097. if (removable) _holdPrefixTwin(scope, signature, top);
  7098. return ruleText;
  7099. }
  7100. seen.add(signature);
  7101. _dropPrefixTwin(scope, signature);
  7102. const needed = _neededPrefixes(PREFIXED_AT_RULES, base);
  7103. if (needed === null) return ruleText;
  7104. let out = "";
  7105. for (const spelling of needed) {
  7106. // Skip only a spelling the source itself already carries (marked when the
  7107. // prefixed at-rule is met); a copy is still added for every unprefixed rule
  7108. // of this signature, so a later `@keyframes` that overrides an earlier keeps
  7109. // its prefixed twin winning too.
  7110. if (seen.has(`${signature}\0${spelling}`)) continue;
  7111. out += `@${spelling}${ruleText.slice(1 + name.length)}`;
  7112. }
  7113. return out + ruleText;
  7114. };
  7115. // The prefixed spelling of a prefixable selector back to `[base, prefix]`
  7116. // (`-webkit-input-placeholder` -> `["placeholder", "-webkit-input-"]`), built
  7117. // once from the forward table so a prefixed selector can be recognized for
  7118. // removal. BCD's prefix concatenates onto the base, so the spelling is exact.
  7119. /** @type {Map<string, [string, string]> | null} */
  7120. let _prefixedSelectorNames = null;
  7121. /**
  7122. * @param {string} name a selector's pseudo name
  7123. * @returns {[string, string] | undefined} its `[base, prefix]` when prefixed
  7124. */
  7125. const _prefixedSelectorName = (name) => {
  7126. if (_prefixedSelectorNames === null) {
  7127. _prefixedSelectorNames = new Map();
  7128. for (const [base, spellings] of PREFIXED_SELECTORS) {
  7129. for (const [spelling] of spellings) {
  7130. _prefixedSelectorNames.set(spelling, [base, spelling]);
  7131. }
  7132. }
  7133. }
  7134. return _prefixedSelectorNames.get(name);
  7135. };
  7136. // A property's vendor spelling back to the property it stands for, built once
  7137. // from the forward table. Read rather than derived: an engine as often renamed
  7138. // the property (`-ms-flex-order` for `order`) as prefixed its name, and only a
  7139. // prefix can be stripped back off.
  7140. /** @type {Map<string, string> | null} */
  7141. let _prefixedPropertyNames = null;
  7142. /**
  7143. * @param {string} property a declaration's property name
  7144. * @returns {string | undefined} the property it is a vendor spelling of
  7145. */
  7146. const _prefixedPropertyName = (property) => {
  7147. if (_prefixedPropertyNames === null) {
  7148. _prefixedPropertyNames = new Map();
  7149. for (const [base, spellings] of PREFIXED_PROPERTIES) {
  7150. for (const [spelling] of spellings) {
  7151. _prefixedPropertyNames.set(spelling, base);
  7152. }
  7153. }
  7154. }
  7155. return _prefixedPropertyNames.get(property);
  7156. };
  7157. /**
  7158. * One prefixed copy of a declaration, for a spelling a target still needs. A
  7159. * spelling whose older property read other keywords carries them with it, and
  7160. * writes nothing at all for a value that property cannot read — the map is its
  7161. * whole grammar.
  7162. * @param {string} spelling the vendor spelling to write
  7163. * @param {string} text the declaration's printed text
  7164. * @param {number} colon where its `:` sits
  7165. * @returns {string} the copy, empty where none can be written
  7166. */
  7167. const _prefixedDeclaration = (spelling, text, colon) => {
  7168. const keywords = PREFIXED_SPELLING_KEYWORDS.get(spelling);
  7169. if (keywords === undefined) return spelling + text.slice(colon);
  7170. const end = _printedValueEnd(text, colon);
  7171. const value = toLowerCaseIfNeeded(text.slice(colon + 1, end));
  7172. // A CSS-wide keyword is every property's, whatever its own grammar says.
  7173. if (CSS_WIDE_KEYWORDS.has(value)) return spelling + text.slice(colon);
  7174. const legacy = keywords.get(value);
  7175. // `!important` and the `;` carry over; the value between them is rewritten.
  7176. return legacy === undefined ? "" : `${spelling}:${legacy}${text.slice(end)}`;
  7177. };
  7178. // A selector prelude part split at its `(`: a functional pseudo prints as one
  7179. // token (`dir(rtl)`), so its name is matched and rewritten apart from the
  7180. // argument that carries over unchanged. A bare pseudo has no `(`.
  7181. /**
  7182. * @param {string} part a printed prelude token
  7183. * @returns {string} its name, without any `(argument)`
  7184. */
  7185. const _selectorPartName = (part) => {
  7186. const paren = part.indexOf("(");
  7187. return paren === -1 ? part : part.slice(0, paren);
  7188. };
  7189. // The shape of every name some engine spells its own way: one bit per length,
  7190. // under the pair of letters it starts with. A declaration whose first two
  7191. // letters and length are not one of those can be neither prefixed nor
  7192. // value-spelled, so its name is never read — which is most of them, `content`,
  7193. // `opacity` and `margin-left` among the commonest. 676 slots, filled the first
  7194. // time a parse prefixes for a target and kept after.
  7195. /** @type {Int32Array | null} */
  7196. let _prefixableNameShapes = null;
  7197. /**
  7198. * @returns {Int32Array} the lengths, by the two letters a name starts with
  7199. */
  7200. const _prefixableNames = () => {
  7201. if (_prefixableNameShapes === null) {
  7202. const shapes = new Int32Array(26 * 26);
  7203. for (const table of [PREFIXED_PROPERTIES, PREFIXED_VALUES]) {
  7204. for (const name of table.keys()) {
  7205. const slot = _nameShapeSlot(name.charCodeAt(0), name.charCodeAt(1));
  7206. if (slot === -1) continue;
  7207. shapes[slot] |= 1 << (name.length > 31 ? 31 : name.length);
  7208. }
  7209. }
  7210. _prefixableNameShapes = shapes;
  7211. }
  7212. return _prefixableNameShapes;
  7213. };
  7214. /**
  7215. * The shape table's slot for a name's first two characters, or `-1` where they
  7216. * are not both ASCII letters. Names match ASCII case-insensitively, so `A`-`Z`
  7217. * reads as its lowercase.
  7218. * @param {number} first the first character's code
  7219. * @param {number} second the second character's code
  7220. * @returns {number} the slot, or -1
  7221. */
  7222. const _nameShapeSlot = (first, second) => {
  7223. const letter = (first | 0x20) - CC_LOWER_A;
  7224. if (letter < 0 || letter > 25) return -1;
  7225. const next = (second | 0x20) - CC_LOWER_A;
  7226. return next < 0 || next > 25 ? -1 : letter * 26 + next;
  7227. };
  7228. /**
  7229. * Whether a printed declaration's name is worth reading — one some engine
  7230. * spells its own way, or a spelling itself.
  7231. * @param {string} text the declaration's printed text
  7232. * @param {number} colon where its `:` sits
  7233. * @returns {boolean} true when the name may take part in prefixing
  7234. */
  7235. const _mayPrefixDeclaration = (text, colon) => {
  7236. // A spelling to drop leads with `-`; a custom property leads with `--` and is
  7237. // neither prefixed nor spelled.
  7238. if (text.charCodeAt(0) === CC_HYPHEN_MINUS) {
  7239. return text.charCodeAt(1) !== CC_HYPHEN_MINUS;
  7240. }
  7241. const slot = _nameShapeSlot(text.charCodeAt(0), text.charCodeAt(1));
  7242. if (slot === -1) return false;
  7243. const lengths = _prefixableNames();
  7244. return ((lengths[slot] >>> (colon < 0 || colon > 31 ? 31 : colon)) & 1) !== 0;
  7245. };
  7246. /**
  7247. * The property a printed declaration sets, read from its text: a merged box
  7248. * shorthand writes a different property than the node it is stored on.
  7249. * @param {string} text the declaration's printed text
  7250. * @param {number} colon where its `:` sits
  7251. * @returns {string} the lowercased property name
  7252. */
  7253. const _printedProperty = (text, colon) =>
  7254. toLowerCaseIfNeeded(colon === -1 ? text : text.slice(0, colon));
  7255. // What a printed declaration carries after its value: the `;` closing it, and
  7256. // the `!important` before that where it has one (minifying leaves no space).
  7257. const _IMPORTANT = "!important";
  7258. /**
  7259. * The value a printed declaration sets, read from its text — the whole value, so
  7260. * only a declaration that is one keyword matches a keyword.
  7261. * @param {string} text the declaration's printed text
  7262. * @param {number} colon where its `:` sits
  7263. * @returns {string} the lowercased value
  7264. */
  7265. const _printedValue = (text, colon) =>
  7266. toLowerCaseIfNeeded(text.slice(colon + 1, _printedValueEnd(text, colon)));
  7267. /**
  7268. * A comma list's top-level items. What a later declaration has to write again for
  7269. * an earlier one to be dead rather than the fallback an engine reads instead.
  7270. * @param {string} value one printed declaration value
  7271. * @returns {string[]} its items, in order
  7272. */
  7273. const _valueItems = (value) => {
  7274. /** @type {string[]} */
  7275. const out = [];
  7276. let depth = 0;
  7277. let quote = 0;
  7278. let start = 0;
  7279. for (let i = 0; i <= value.length; i++) {
  7280. const cc = i === value.length ? CC_COMMA : value.charCodeAt(i);
  7281. if (quote !== 0) {
  7282. if (cc === CC_REVERSE_SOLIDUS) i++;
  7283. else if (cc === quote) quote = 0;
  7284. continue;
  7285. }
  7286. if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
  7287. quote = cc;
  7288. } else if (cc === CC_LEFT_PARENTHESIS) {
  7289. depth++;
  7290. } else if (cc === CC_RIGHT_PARENTHESIS) {
  7291. depth--;
  7292. } else if (cc === CC_COMMA && depth === 0) {
  7293. out.push(value.slice(start, i).trim());
  7294. start = i + 1;
  7295. }
  7296. }
  7297. return out;
  7298. };
  7299. // A vendor prefix on an item's first token, which is the name slot a
  7300. // `<custom-ident>` list reads there.
  7301. const _VENDOR_ITEM_PREFIX_RE = /^-[a-z]+-/i;
  7302. /**
  7303. * Whether a later declaration of a `<custom-ident>` list leaves an earlier one
  7304. * unreadable rather than standing as its fallback: every item the earlier writes
  7305. * is written again, and every item the later adds is one of them under another
  7306. * vendor spelling. A name is what that slot takes, so an engine knowing none of
  7307. * the spellings still parses the value — there is nothing to fall back to.
  7308. * `transition:box-shadow .25s` before `transition:box-shadow .25s,-webkit-box-shadow .25s`
  7309. * is the shape, which is what a tool adding prefixes writes.
  7310. * @param {string} later the later declaration's printed value
  7311. * @param {string} earlier the earlier declaration's printed value
  7312. * @returns {boolean} true when the earlier one can no longer be read
  7313. */
  7314. const _coveredByLater = (later, earlier) => {
  7315. const laterItems = _valueItems(later);
  7316. const earlierItems = _valueItems(earlier);
  7317. if (earlierItems.length === 0 || laterItems.length < earlierItems.length) {
  7318. return false;
  7319. }
  7320. for (const item of earlierItems) {
  7321. if (!laterItems.includes(item)) return false;
  7322. }
  7323. for (const item of laterItems) {
  7324. if (earlierItems.includes(item)) continue;
  7325. const bare = item.replace(_VENDOR_ITEM_PREFIX_RE, "");
  7326. let variant = false;
  7327. for (const other of earlierItems) {
  7328. if (other.replace(_VENDOR_ITEM_PREFIX_RE, "") === bare) {
  7329. variant = true;
  7330. break;
  7331. }
  7332. }
  7333. if (!variant) return false;
  7334. }
  7335. return true;
  7336. };
  7337. /**
  7338. * Where a printed declaration's value ends: before the `!important` it may carry
  7339. * and the `;` closing it.
  7340. * @param {string} text the declaration's printed text
  7341. * @param {number} colon where its `:` sits
  7342. * @returns {number} the offset one past the value
  7343. */
  7344. const _printedValueEnd = (text, colon) => {
  7345. let end = text.length;
  7346. if (text.charCodeAt(end - 1) === CC_SEMICOLON) end--;
  7347. const bang = end - _IMPORTANT.length;
  7348. if (bang > colon && text.startsWith(_IMPORTANT, bang)) end = bang;
  7349. return end;
  7350. };
  7351. // A property's vendor value spellings back to the keyword each stands for, built
  7352. // on first use from the forward table.
  7353. /** @type {Map<Map<string, [string, number][]>, Map<string, string>>} */
  7354. const _prefixedValueKeywords = new Map();
  7355. /**
  7356. * @param {Map<string, [string, number][]>} table one property's value table
  7357. * @param {string} value a value that may be a vendor spelling
  7358. * @returns {string | undefined} the keyword it spells, when it is one
  7359. */
  7360. const _prefixedValueKeyword = (table, value) => {
  7361. let reverse = _prefixedValueKeywords.get(table);
  7362. if (reverse === undefined) {
  7363. reverse = new Map();
  7364. for (const [keyword, spellings] of table) {
  7365. for (const [spelling] of spellings) reverse.set(spelling, keyword);
  7366. }
  7367. _prefixedValueKeywords.set(table, reverse);
  7368. }
  7369. return reverse.get(value);
  7370. };
  7371. /**
  7372. * Whether a prelude token is a pseudo some engine spells with a prefix. It must
  7373. * sit right after a `:` for the caller to ask, so a class of the same spelling is
  7374. * not mistaken for it; the name matches ASCII case-insensitively, as property and
  7375. * at-rule names do.
  7376. * @param {string} part a printed prelude token
  7377. * @returns {boolean} true when the table knows it, prefixed or not
  7378. */
  7379. const _prefixablePseudo = (part) => {
  7380. const name = toLowerCaseIfNeeded(_selectorPartName(part));
  7381. return (
  7382. PREFIXED_SELECTORS.has(name) || _prefixedSelectorName(name) !== undefined
  7383. );
  7384. };
  7385. /**
  7386. * One selector of a rule's prelude: the tokens it spans, and the sole prefixable
  7387. * pseudo in it — `-1` where it has none, `-2` where it has more than one, which
  7388. * leaves the whole rule alone.
  7389. * @typedef {object} PrefixableSelector
  7390. * @property {number} start its first prelude token
  7391. * @property {number} end one past its last
  7392. * @property {number} at the prefixable pseudo's token, or -1 / -2
  7393. */
  7394. /**
  7395. * Split a prelude into its selectors, marking the prefixable pseudo in each.
  7396. * @param {string[]} parts the printed prelude tokens
  7397. * @returns {PrefixableSelector[] | null} the selectors, or null when none carries one
  7398. */
  7399. const _prefixableSelectors = (parts) => {
  7400. // Nothing is built for a prelude that carries no prefixable pseudo, which is
  7401. // nearly every one: the scan below only reads the tokens after a `:`.
  7402. let any = false;
  7403. for (let i = 1; i < parts.length; i++) {
  7404. if (parts[i - 1] === ":" && _prefixablePseudo(parts[i])) {
  7405. any = true;
  7406. break;
  7407. }
  7408. }
  7409. if (!any) return null;
  7410. /** @type {PrefixableSelector[]} */
  7411. const selectors = [];
  7412. let start = 0;
  7413. let at = -1;
  7414. for (let i = 0; i <= parts.length; i++) {
  7415. if (i === parts.length || parts[i] === ",") {
  7416. // Whitespace around the comma says nothing, and would print back as a
  7417. // space inside a rewritten list.
  7418. let from = start;
  7419. let to = i;
  7420. while (from < to && parts[from] === _SEP) from++;
  7421. while (to > from && parts[to - 1] === _SEP) to--;
  7422. selectors.push({ start: from, end: to, at });
  7423. start = i + 1;
  7424. at = -1;
  7425. continue;
  7426. }
  7427. if (i !== 0 && parts[i - 1] === ":" && _prefixablePseudo(parts[i])) {
  7428. at = at === -1 ? i : -2;
  7429. }
  7430. }
  7431. return selectors;
  7432. };
  7433. /**
  7434. * A qualified rule prefixed against the target, for each selector of its list
  7435. * that is a prefixable pseudo (`::placeholder`): a copy carrying the pseudo's
  7436. * engine spelling is prepended for each prefix a target needs, and a
  7437. * prefixed-only rule no target needs is dropped once its unprefixed twin has
  7438. * been seen. A copy holds only the selectors that need that one prefix — an
  7439. * engine drops a whole list over one selector it cannot parse, so a copy must
  7440. * never mix spellings. The author's colons carry over.
  7441. * @param {string} ruleText the rule's own serialized text
  7442. * @param {string[]} parts the printed prelude tokens
  7443. * @param {string} soft the space before `{` (empty minifying)
  7444. * @param {string} body the rule's serialized block body
  7445. * @param {PrefixScope} scope the block's running sibling state
  7446. * @param {boolean} top whether the rule is the stylesheet's own, which is the only
  7447. * one whose text is still a piece a twin can take back
  7448. * @returns {string} the rule text, with prefixed copies added or itself dropped
  7449. */
  7450. const _prefixQualifiedRule = (ruleText, parts, soft, body, scope, top) => {
  7451. const seen = scope.seen;
  7452. const selectors = _prefixableSelectors(parts);
  7453. if (selectors === null) return ruleText;
  7454. // A functional pseudo (`dir(rtl)`) keeps its argument; only the name is
  7455. // matched and swapped, and the argument is part of the sibling signature so
  7456. // `:dir(rtl)` and `:dir(ltr)` stay distinct.
  7457. /** @type {string[]} */
  7458. const bases = [];
  7459. /** @type {string[]} */
  7460. const args = [];
  7461. /** @type {(string | undefined)[]} */
  7462. const carried = [];
  7463. /** @type {string | undefined} */
  7464. let listPrefix;
  7465. for (const selector of selectors) {
  7466. const at = selector.at;
  7467. if (at === -2) return ruleText;
  7468. if (at === -1) {
  7469. bases.push("");
  7470. args.push("");
  7471. carried.push(undefined);
  7472. continue;
  7473. }
  7474. const raw = parts[at];
  7475. const nameOnly = toLowerCaseIfNeeded(_selectorPartName(raw));
  7476. const found = _prefixedSelectorName(nameOnly);
  7477. // One spelling for the whole list: a list mixing a vendor-spelled pseudo
  7478. // with a plainly spelled one, or with a second spelling, is neither this
  7479. // rule's twin nor a copy of it.
  7480. if (found !== undefined) {
  7481. if (listPrefix !== undefined && listPrefix !== found[1]) return ruleText;
  7482. listPrefix = found[1];
  7483. }
  7484. bases.push(found === undefined ? nameOnly : found[0]);
  7485. args.push(raw.slice(_selectorPartName(raw).length));
  7486. carried.push(found === undefined ? undefined : found[1]);
  7487. }
  7488. /**
  7489. * @param {(index: number) => string | null} spell each selector's pseudo, or null to leave it out
  7490. * @returns {string} the prelude those selectors print as
  7491. */
  7492. const preludeOf = (spell) => {
  7493. /** @type {string[]} */
  7494. const out = [];
  7495. for (let i = 0; i < selectors.length; i++) {
  7496. const name = spell(i);
  7497. if (name === null) continue;
  7498. if (out.length !== 0) out.push(",");
  7499. const { start, end, at } = selectors[i];
  7500. for (let j = start; j < end; j++) out.push(j === at ? name : parts[j]);
  7501. }
  7502. return _join(out, false, _TRIM_COMBINATORS);
  7503. };
  7504. // This rule's cross-prefix identity: every prefixable pseudo folded to its
  7505. // unprefixed spelling, so a prefixed list and its twin pair up.
  7506. const signature = `s${preludeOf((i) =>
  7507. selectors[i].at === -1 ? "" : bases[i] + args[i]
  7508. )}`;
  7509. if (listPrefix !== undefined) {
  7510. // Every pseudo of the list carries that spelling, and each is dead weight on
  7511. // its own.
  7512. const removable = bases.every(
  7513. (base, i) =>
  7514. selectors[i].at === -1 ||
  7515. _prefixRemovable(
  7516. PREFIXED_SELECTORS,
  7517. base,
  7518. /** @type {string} */ (listPrefix)
  7519. )
  7520. );
  7521. if (seen.has(signature) && removable) return "";
  7522. seen.add(`${signature}\0${listPrefix}`);
  7523. // Its twin may still be a later rule, which is where it is dropped.
  7524. if (removable) _holdPrefixTwin(scope, signature, top);
  7525. return ruleText;
  7526. }
  7527. seen.add(signature);
  7528. _dropPrefixTwin(scope, signature);
  7529. /** @type {(Set<string> | null)[]} */
  7530. // One copy per spelling, never per engine: an engine that does not know one of
  7531. // a list's selectors drops the list whole, and two names of one engine arrived
  7532. // in different versions — `::-webkit-input-placeholder` in Chrome 6 and
  7533. // `:-webkit-full-screen` in 15, so a list of both is nothing to Chrome 6
  7534. // through 14. Selectors that take the same spelling take the same versions
  7535. // with it, so those do share a copy.
  7536. /** @type {(Set<string> | null)[]} */
  7537. const needed = [];
  7538. /** @type {Set<string> | null} */
  7539. let spellings = null;
  7540. for (let i = 0; i < selectors.length; i++) {
  7541. const one =
  7542. selectors[i].at === -1
  7543. ? null
  7544. : _neededPrefixes(PREFIXED_SELECTORS, bases[i]);
  7545. needed.push(one);
  7546. if (one === null) continue;
  7547. if (spellings === null) spellings = new Set();
  7548. for (const spelling of one) spellings.add(spelling);
  7549. }
  7550. if (spellings === null) return ruleText;
  7551. let out = "";
  7552. for (const spelling of spellings) {
  7553. // Skip only a spelling the source itself already carries (marked above when
  7554. // the vendor-spelled rule is met); a copy is still added for every
  7555. // unprefixed rule of this signature, so a later one that overrides an
  7556. // earlier keeps its prefixed twin winning too.
  7557. if (seen.has(`${signature}\0${spelling}`)) continue;
  7558. const list = preludeOf((i) => {
  7559. const one = needed[i];
  7560. return one === null || !one.has(spelling) ? null : spelling + args[i];
  7561. });
  7562. out += `${list}${soft}{${body}}`;
  7563. }
  7564. return out + ruleText;
  7565. };
  7566. // A plain number, optionally a percentage — the only argument form these color
  7567. // conversions are proven for.
  7568. const _COLOR_NUMBER_RE = /^([+-]?(?:\d+\.?\d*|\.\d+))(%?)$/;
  7569. // The hue's default unit; the other angle units would need their own conversion
  7570. // before the same boundary test could be applied.
  7571. const _COLOR_HUE_RE = /^([+-]?(?:\d+\.?\d*|\.\d+))(?:deg)?$/i;
  7572. // How far a channel must sit from a `.5` rounding boundary to be converted.
  7573. // Implementations only ever disagree at a tie: across 317520 `hsl()` / `hwb()`
  7574. // samples checked against headless Chromium, every one of the divergences was
  7575. // within 9.24e-14 of a boundary, and none of the 264084 converted outside it was
  7576. // wrong. The margin is many orders above that and far below a visible difference.
  7577. const _ROUNDING_MARGIN = 1e-6;
  7578. // The Lab family needs a far wider one. Its conversion is a chain of matrices
  7579. // rather than a handful of multiplications, and webpack's chain and Chromium's
  7580. // disagree by up to 0.035 of a byte — measured over ~6000 `lab()` / `lch()` /
  7581. // `oklab()` / `oklch()` samples read back from a canvas. This is ~3x that bound.
  7582. const _LAB_ROUNDING_MARGIN = 0.1;
  7583. /**
  7584. * Round a raw 0..255 channel, or return null when it sits close enough to a `.5`
  7585. * boundary that another implementation's last bit could round it the other way.
  7586. * @param {number} raw the channel, 0..255
  7587. * @param {number} margin how far from a boundary the channel must sit
  7588. * @returns {number | null} the byte, or null to keep the function
  7589. */
  7590. const _channelByte = (raw, margin) => {
  7591. // Past this the color is one sRGB cannot show, so clamping it into a hex would
  7592. // pick a different color rather than respell the same one. Inside it, clamping
  7593. // lands on the byte rounding would have picked anyway — which is what lets a
  7594. // Lab white point a few 1e-6 past 255, or a channel a hundredth of a byte
  7595. // under 0, still convert.
  7596. if (raw < -0.5 || raw > 255.5) return null;
  7597. if (Math.abs(Math.abs(raw - Math.floor(raw)) - 0.5) < margin) return null;
  7598. const byte = Math.round(raw);
  7599. return byte < 0 ? 0 : byte > 255 ? 255 : byte;
  7600. };
  7601. /**
  7602. * CSS Color 4 §7.1's `hsl()` -> sRGB, on 0..1 inputs, in 0..255 output.
  7603. * @param {number} h hue in degrees, already wrapped to 0..360
  7604. * @param {number} s saturation 0..1
  7605. * @param {number} l lightness 0..1
  7606. * @returns {number[]} the raw `[r, g, b]` channels, 0..255
  7607. */
  7608. const _hslToRgb = (h, s, l) => {
  7609. const a = s * Math.min(l, 1 - l);
  7610. return [0, 8, 4].map((n) => {
  7611. const k = (n + h / 30) % 12;
  7612. return (l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))) * 255;
  7613. });
  7614. };
  7615. /**
  7616. * CSS Color 4 §7.2's `hwb()` -> sRGB. Whiteness and blackness summing to 1 or
  7617. * more is the achromatic case the spec spells out separately.
  7618. * @param {number} h hue in degrees, already wrapped to 0..360
  7619. * @param {number} w whiteness 0..1
  7620. * @param {number} b blackness 0..1
  7621. * @returns {number[]} the raw `[r, g, b]` channels, 0..255
  7622. */
  7623. const _hwbToRgb = (h, w, b) => {
  7624. if (w + b >= 1) {
  7625. const gray = (w / (w + b)) * 255;
  7626. return [gray, gray, gray];
  7627. }
  7628. return _hslToRgb(h, 1, 0.5).map(
  7629. (channel) => (channel / 255) * (1 - w - b) * 255 + w * 255
  7630. );
  7631. };
  7632. /**
  7633. * Linear-light sRGB -> the gamma-encoded channel CSS serializes (CSS Color 4
  7634. * §10.2). Out-of-gamut input is returned as-is so the caller's range check sees
  7635. * it rather than a clipped value.
  7636. * @param {number} c one linear component
  7637. * @returns {number} the encoded component, nominally 0..1
  7638. */
  7639. const _gammaEncode = (c) => {
  7640. const sign = c < 0 ? -1 : 1;
  7641. const abs = Math.abs(c);
  7642. return abs > 0.0031308
  7643. ? sign * (1.055 * abs ** (1 / 2.4) - 0.055)
  7644. : 12.92 * c;
  7645. };
  7646. // The D50 white point Lab is defined against (CSS Color 4 §12), as XYZ.
  7647. const _D50 = [0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585];
  7648. // Bradford-adapted D50 -> D65, then XYZ -> linear-light sRGB. Applied as the two
  7649. // steps CSS Color 4's own sample code uses rather than one composed matrix: the
  7650. // composition loses enough precision to push `lab(100% 0 0)` outside the gamut
  7651. // check below.
  7652. const _D50_TO_D65 = [
  7653. [0.9554734527042182, -0.023098536874261423, 0.0632593086610217],
  7654. [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008],
  7655. [0.012314001688319899, -0.020507696433477912, 1.3303659366080753]
  7656. ];
  7657. const _XYZ_TO_LINEAR_SRGB = [
  7658. [3.2409699419045226, -1.537383177570094, -0.4986107602930034],
  7659. [-0.9692436362808796, 1.8759675015077202, 0.04155505740717559],
  7660. [0.05563007969699366, -0.20397695888897652, 1.0569715142428786]
  7661. ];
  7662. /**
  7663. * @param {number[][]} matrix a 3x3 matrix
  7664. * @param {number[]} vector the column it multiplies
  7665. * @returns {number[]} the product
  7666. */
  7667. const _multiply3 = (matrix, vector) =>
  7668. matrix.map(
  7669. (row) => row[0] * vector[0] + row[1] * vector[1] + row[2] * vector[2]
  7670. );
  7671. /**
  7672. * CIE Lab -> linear-light sRGB: Lab -> XYZ (D50) -> XYZ (D65) -> linear sRGB,
  7673. * with the matrices and constants CSS Color 4 §12 gives.
  7674. * @param {number} l lightness 0..100
  7675. * @param {number} a the a axis
  7676. * @param {number} b the b axis
  7677. * @returns {number[]} the linear `[r, g, b]` components, nominally 0..1
  7678. */
  7679. const _labToLinearSrgb = (l, a, b) => {
  7680. const e = 216 / 24389;
  7681. const k = 24389 / 27;
  7682. const fy = (l + 16) / 116;
  7683. const fx = a / 500 + fy;
  7684. const fz = fy - b / 200;
  7685. const xyz = [
  7686. (fx ** 3 > e ? fx ** 3 : (116 * fx - 16) / k) * _D50[0],
  7687. (l > k * e ? fy ** 3 : l / k) * _D50[1],
  7688. (fz ** 3 > e ? fz ** 3 : (116 * fz - 16) / k) * _D50[2]
  7689. ];
  7690. return _multiply3(_XYZ_TO_LINEAR_SRGB, _multiply3(_D50_TO_D65, xyz));
  7691. };
  7692. /**
  7693. * Oklab -> linear-light sRGB (CSS Color 4 §9.2's matrices).
  7694. * @param {number} l lightness 0..1
  7695. * @param {number} a the a axis
  7696. * @param {number} b the b axis
  7697. * @returns {number[]} the linear `[r, g, b]` components, nominally 0..1
  7698. */
  7699. const _oklabToLinearSrgb = (l, a, b) => {
  7700. const lp = l + 0.3963377773761749 * a + 0.2158037573099136 * b;
  7701. const mp = l - 0.1055613458156586 * a - 0.0638541728258133 * b;
  7702. const sp = l - 0.0894841775298119 * a - 1.2914855480194092 * b;
  7703. const lc = lp ** 3;
  7704. const mc = mp ** 3;
  7705. const sc = sp ** 3;
  7706. return [
  7707. 4.076741661347994 * lc - 3.307711590408193 * mc + 0.230969928729428 * sc,
  7708. -1.2684380040921763 * lc +
  7709. 2.6097574006633715 * mc -
  7710. 0.3413193963102197 * sc,
  7711. -0.004196086541837188 * lc -
  7712. 0.7034186144594493 * mc +
  7713. 1.7076147009309444 * sc
  7714. ];
  7715. };
  7716. /**
  7717. * The `<percentage>` that stands for 100% of each Lab-family axis (CSS Color 4
  7718. * §9, §12), as `[lightness, axis]`.
  7719. * @type {Map<string, number[]>}
  7720. */
  7721. const _LAB_SCALES = new Map([
  7722. ["lab", [100, 125]],
  7723. ["lch", [100, 150]],
  7724. ["oklab", [1, 0.4]],
  7725. ["oklch", [1, 0.4]]
  7726. ]);
  7727. /**
  7728. * Minify `hsl()` / `hwb()` / `lab()` / `lch()` / `oklab()` / `oklch()` to the
  7729. * shortest hex.
  7730. *
  7731. * Two guards keep the rewrite from changing the color. A channel landing within
  7732. * `_ROUNDING_MARGIN` of a `.5` boundary keeps the function, because that is the
  7733. * only place implementations disagree — it is why esbuild and lightningcss emit
  7734. * different bytes for `hwb(194 0% 0%)`. And a Lab-family color outside the sRGB
  7735. * gamut keeps its function too: hex cannot express it, so converting would clip
  7736. * it to a different color. Both are limitations, not correctness gaps — the
  7737. * declined share is small and every other minifier converts them regardless.
  7738. * @param {string} fn the lowercased function name
  7739. * @param {string} inner the already-joined argument text
  7740. * @param {boolean} hexAlpha whether the target reads a 4-/8-digit hex (see `output.environment`)
  7741. * @returns {string | null} the shortest color, or null to keep the function
  7742. */
  7743. const _minifyPolarColorFunction = (fn, inner, hexAlpha) => {
  7744. if (!_transforms.shortenColors) return null;
  7745. const isHsl = fn === "hsl" || fn === "hsla";
  7746. const labScale = _LAB_SCALES.get(fn);
  7747. if (!isHsl && fn !== "hwb" && labScale === undefined) return null;
  7748. const args = inner
  7749. .replace(/\//g, " ")
  7750. .split(/[\s,]+/)
  7751. .filter((part) => part.length !== 0);
  7752. if (args.length !== 3 && args.length !== 4) return null;
  7753. let alpha = 1;
  7754. if (args.length === 4) {
  7755. const parsed = _COLOR_NUMBER_RE.exec(args[3]);
  7756. if (parsed === null) return null;
  7757. alpha = Number(parsed[1]) / (parsed[2] === "%" ? 100 : 1);
  7758. if (!(alpha >= 0 && alpha <= 1)) return null;
  7759. // A partly transparent color needs the hex-alpha spelling, which is the
  7760. // target's question, not this one's.
  7761. if (alpha < 1 && !hexAlpha) return null;
  7762. }
  7763. /** @type {number[]} */
  7764. let raw;
  7765. if (isHsl || fn === "hwb") {
  7766. const hue = _COLOR_HUE_RE.exec(args[0]);
  7767. const first = _COLOR_NUMBER_RE.exec(args[1]);
  7768. const second = _COLOR_NUMBER_RE.exec(args[2]);
  7769. if (hue === null || first === null || second === null) return null;
  7770. if (first[2] !== "%" || second[2] !== "%") return null;
  7771. const x = Number(first[1]) / 100;
  7772. const y = Number(second[1]) / 100;
  7773. if (x < 0 || x > 1 || y < 0 || y > 1) return null;
  7774. let h = Number(hue[1]) % 360;
  7775. if (h < 0) h += 360;
  7776. raw = isHsl ? _hslToRgb(h, x, y) : _hwbToRgb(h, x, y);
  7777. } else {
  7778. const scale = /** @type {number[]} */ (labScale);
  7779. const isPolar = fn === "lch" || fn === "oklch";
  7780. const lightness = _COLOR_NUMBER_RE.exec(args[0]);
  7781. const second = _COLOR_NUMBER_RE.exec(args[1]);
  7782. const third = isPolar
  7783. ? _COLOR_HUE_RE.exec(args[2])
  7784. : _COLOR_NUMBER_RE.exec(args[2]);
  7785. if (lightness === null || second === null || third === null) return null;
  7786. const l =
  7787. Number(lightness[1]) * (lightness[2] === "%" ? scale[0] / 100 : 1);
  7788. let a;
  7789. let b;
  7790. if (isPolar) {
  7791. const chroma =
  7792. Number(second[1]) * (second[2] === "%" ? scale[1] / 100 : 1);
  7793. const hue = (Number(third[1]) * Math.PI) / 180;
  7794. a = chroma * Math.cos(hue);
  7795. b = chroma * Math.sin(hue);
  7796. } else {
  7797. a = Number(second[1]) * (second[2] === "%" ? scale[1] / 100 : 1);
  7798. b = Number(third[1]) * (third[2] === "%" ? scale[1] / 100 : 1);
  7799. }
  7800. const linear =
  7801. fn === "lab" || fn === "lch"
  7802. ? _labToLinearSrgb(l, a, b)
  7803. : _oklabToLinearSrgb(l, a, b);
  7804. // `_channelByte` decides the gamut from the encoded value, where the
  7805. // question is whether clamping would move the byte.
  7806. raw = linear.map((c) => _gammaEncode(c) * 255);
  7807. }
  7808. const margin =
  7809. labScale === undefined ? _ROUNDING_MARGIN : _LAB_ROUNDING_MARGIN;
  7810. /** @type {number[]} */
  7811. const channels = [];
  7812. for (const value of raw) {
  7813. const byte = _channelByte(value, margin);
  7814. if (byte === null) return null;
  7815. channels.push(byte);
  7816. }
  7817. if (alpha < 1) {
  7818. // The alpha is the author's own number rather than a conversion's output, so
  7819. // it takes no boundary guard: the engine quantizes it to 8 bits either way,
  7820. // exactly as `rgba()`'s does.
  7821. return _shortestAlphaColor(
  7822. channels[0],
  7823. channels[1],
  7824. channels[2],
  7825. _clamp255(alpha * 255)
  7826. );
  7827. }
  7828. return _shortestColor(channels[0], channels[1], channels[2]);
  7829. };
  7830. // Some mobile WebKit builds honor `-webkit-tap-highlight-color:rgba(0,0,0,0)`
  7831. // but ignore the equivalent `transparent`, so that one property keeps the
  7832. // function form (cssnano guards the same property).
  7833. /**
  7834. * @returns {boolean} whether the value being printed belongs to `-webkit-tap-highlight-color`
  7835. */
  7836. const _inTapHighlightColor = () =>
  7837. _valueDeclaration !== null &&
  7838. A.name(_valueDeclaration).toLowerCase() === "-webkit-tap-highlight-color";
  7839. /**
  7840. * Write one `:nth-*()` in its shortest equal spelling: the An+B microsyntax
  7841. * carries its own whitespace and signs, two keywords name a step of two, and a
  7842. * step selecting exactly one child is the child that has its own name.
  7843. * @param {string} name the lowercased function name
  7844. * @param {string} inner the already-joined argument text
  7845. * @returns {string} the whole replacement, `name(inner)` when nothing is shorter
  7846. */
  7847. const _minifyAnPlusB = (name, inner) => {
  7848. if (!_transforms.shortenSelectors) return `${name}(${inner})`;
  7849. // `An+B of S` selects among S, which no plain spelling names.
  7850. if (/\bof\b/i.test(inner)) return `${name}(${inner})`;
  7851. const nth = _minifyNth(inner);
  7852. if (nth === null) return `${name}(${inner})`;
  7853. const first = NTH_NAMED_EQUIVALENTS.get(name);
  7854. return nth === "1" && first !== undefined ? first : `${name}(${nth})`;
  7855. };
  7856. /**
  7857. * Whether the declaration being printed is the font-family longhand, the one
  7858. * place a `<family-name>` stands on its own rather than among other slots.
  7859. * @returns {boolean} true inside such a declaration
  7860. */
  7861. const _inFontFamily = () =>
  7862. _valueDeclaration !== null &&
  7863. equalsLowerCase(A.name(_valueDeclaration), "font-family");
  7864. /**
  7865. * Whether the string is the whole entry in its comma-separated slot:
  7866. * `<family-name>` takes `<string> | <custom-ident>+`, never a mix of the two.
  7867. * @param {CssPath} path the accessor positioned on the string token
  7868. * @returns {boolean} true when nothing else shares its slot
  7869. */
  7870. const _isLoneFamilyName = (path) => {
  7871. const parent = path.parent;
  7872. // Directly in the declaration's value: inside a function the string is an
  7873. // argument rather than a family.
  7874. if (parent === null || path.type(parent) !== T_DECLARATION) return false;
  7875. const self = path.start(path.node);
  7876. const count = path.childCount(parent);
  7877. let sharedSlot = false;
  7878. let seenSelf = false;
  7879. for (let at = 0; at < count; at++) {
  7880. const child = path.childAt(parent, at);
  7881. const type = path.type(child);
  7882. if (type === T_COMMA) {
  7883. if (seenSelf) return !sharedSlot;
  7884. sharedSlot = false;
  7885. continue;
  7886. }
  7887. if (type === T_WHITESPACE || type === T_COMMENT) continue;
  7888. if (path.start(child) === self) seenSelf = true;
  7889. else sharedSlot = true;
  7890. }
  7891. return seenSelf && !sharedSlot;
  7892. };
  7893. // One identifier, and a family name is a run of them parted by whitespace. The
  7894. // escapes a quoted name may carry are not identifier text, so one declines.
  7895. const _PLAIN_IDENT_RE = /^-?[A-Za-z_€-￿][\w€-￿-]*$/;
  7896. /**
  7897. * Unquote a font family whose text is already a run of identifiers, which is
  7898. * the other spelling `<family-name>` names. Returns null wherever the quotes
  7899. * carry something: a generic family's own keyword, a CSS-wide keyword, or text
  7900. * no identifier could spell.
  7901. * @param {string} raw the string token as written, quotes included
  7902. * @returns {string | null} the unquoted name, or null to keep the string
  7903. */
  7904. const _unquoteFontFamily = (raw) => {
  7905. if (!_transforms.normalizeQuotes) return null;
  7906. const quote = raw.charCodeAt(0);
  7907. if (quote !== CC_QUOTATION_MARK && quote !== CC_APOSTROPHE) return null;
  7908. if (raw.length < 2 || raw.charCodeAt(raw.length - 1) !== quote) return null;
  7909. const text = raw.slice(1, -1);
  7910. if (text.includes("\\")) return null;
  7911. const words = text.split(" ");
  7912. if (words.length === 0 || text.length + 1 >= raw.length) return null;
  7913. for (const word of words) {
  7914. if (!_PLAIN_IDENT_RE.test(word)) return null;
  7915. const lowered = toLowerCaseIfNeeded(word);
  7916. // Unquoted, either would read as the grammar's keyword instead of a name.
  7917. if (CSS_WIDE_KEYWORDS.has(lowered)) return null;
  7918. // A generic family is one identifier, so only a lone word can be read as
  7919. // one — `Apple Color Emoji` is three, whatever the last of them spells.
  7920. if (words.length === 1 && GENERIC_FONT_FAMILIES.has(lowered)) return null;
  7921. }
  7922. return text;
  7923. };
  7924. /**
  7925. * Whether the declaration being printed takes a color and never an identifier
  7926. * of the author's own, so a named color in it is unambiguously that color.
  7927. * @returns {boolean} true inside such a declaration
  7928. */
  7929. const _inColorOnlyProperty = () =>
  7930. _valueDeclaration !== null &&
  7931. COLOR_ONLY_PROPERTIES.has(toLowerCaseIfNeeded(A.name(_valueDeclaration)));
  7932. /**
  7933. * Whether the declaration being printed spells no name of the author's: its
  7934. * grammar is keywords alone, or it takes a color, which is keywords and numbers.
  7935. * An identifier standing directly in such a value is one of those keywords.
  7936. * @returns {boolean} true inside such a declaration
  7937. */
  7938. const _inKeywordOnlyValue = () => {
  7939. if (_valueDeclaration === null) return false;
  7940. // Memoized on the declaration: every identifier in one value asks the same
  7941. // question, and answering it reads the name, folds it and looks it up twice.
  7942. if (_keywordOnlyFor !== _valueDeclaration) {
  7943. _keywordOnlyFor = _valueDeclaration;
  7944. const property = _standardSpelling(
  7945. toLowerCaseIfNeeded(A.name(_valueDeclaration))
  7946. );
  7947. _keywordOnly =
  7948. KEYWORD_ONLY_PROPERTIES.has(property) ||
  7949. COLOR_ONLY_PROPERTIES.has(property);
  7950. }
  7951. return _keywordOnly;
  7952. };
  7953. // A plain (non-scientific, unitless) number — the only argument form these
  7954. // equivalences are proven for; anything else (a `var()`, a dimension) keeps the
  7955. // function, since rewriting an invalid declaration would activate it.
  7956. const _PLAIN_NUMBER_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)$/;
  7957. /**
  7958. * Minify an easing function to its shorter, value-identical form:
  7959. * `cubic-bezier()` to the keyword defining the same curve, `steps()` to
  7960. * `step-start` / `step-end`, and the default `end` position away. Returns null
  7961. * for anything else, keeping the function.
  7962. * @param {string} fn the lowercased function name
  7963. * @param {string} inner the already-joined argument text
  7964. * @returns {string | null} the shorter easing function, or null to keep the function
  7965. */
  7966. const _minifyEasingFunction = (fn, inner) => {
  7967. if (!_transforms.reduceFunctions) return null;
  7968. if (fn !== "cubic-bezier" && fn !== "steps") return null;
  7969. const args = inner.split(",");
  7970. if (fn === "cubic-bezier") {
  7971. if (args.length !== 4) return null;
  7972. let key = "";
  7973. for (let i = 0; i < 4; i++) {
  7974. const s = args[i].trim();
  7975. if (!_PLAIN_NUMBER_RE.test(s)) return null;
  7976. key += (i === 0 ? "" : ",") + Number.parseFloat(s);
  7977. }
  7978. const keyword = CUBIC_BEZIER_KEYWORDS.get(key);
  7979. return keyword === undefined ? null : keyword;
  7980. }
  7981. if (args.length !== 2) return null;
  7982. const count = args[0].trim();
  7983. if (!/^\d+$/.test(count)) return null;
  7984. const position = args[1].trim().toLowerCase();
  7985. if (position === "start" || position === "jump-start") {
  7986. // `start` is `jump-start`'s alias.
  7987. return count === "1" ? "step-start" : `steps(${count},start)`;
  7988. }
  7989. if (position !== "end" && position !== "jump-end") return null;
  7990. // `end` is `steps()`'s default position, so it always drops out.
  7991. return count === "1" ? "step-end" : `steps(${count})`;
  7992. };
  7993. // Code points a url-token cannot carry unescaped, so a `url("…")` string only
  7994. // drops its quotes without them.
  7995. // eslint-disable-next-line no-control-regex -- a control code point is exactly what a url-token may not carry
  7996. const _UNQUOTABLE_URL_RE = /[\s"'()\\\u0000-\u001F\u007F]/;
  7997. // …of those, the ones a backslash alone escapes into a url-token. Every other
  7998. // one it cannot: a control code point takes a hex escape that is never shorter,
  7999. // and a backslash already there starts an escape this printer did not write, so
  8000. // the string keeps its quotes rather than having that escape rewritten.
  8001. const _URL_ESCAPABLE = new Set([" ", '"', "'", "(", ")"]);
  8002. /**
  8003. * Rewrite a `url()`'s quoted body as the url-token spelling the same URL, or
  8004. * null where none does. Every code point is classified — escaped, kept, or
  8005. * refused — so nothing reaches the output unexamined.
  8006. * @param {string} body the string's content, quotes excluded
  8007. * @returns {string | null} the url-token text, or null to keep the quotes
  8008. */
  8009. const _escapeUrlBody = (body) => {
  8010. let out = "";
  8011. let escapes = 0;
  8012. for (const character of body) {
  8013. if (_URL_ESCAPABLE.has(character)) {
  8014. // Two escapes cost the two bytes the quotes did, so nothing is saved.
  8015. if (++escapes > 1) return null;
  8016. out += `\\${character}`;
  8017. continue;
  8018. }
  8019. const code = /** @type {number} */ (character.codePointAt(0));
  8020. if (character === "\\" || code < 0x20 || code === 0x7f) return null;
  8021. out += character;
  8022. }
  8023. return escapes === 0 ? null : out;
  8024. };
  8025. const _PERCENT_ESCAPE_RE = /%[0-9a-f]{2}/gi;
  8026. // A `data:` URL up to the comma that ends its metadata. Only past that comma is
  8027. // an escape content the URL parser decodes before anything reads it; anywhere
  8028. // else it is structure — `%26` in a query is a literal `&`, not a separator.
  8029. const _DATA_URL_METADATA_RE = /^data:[^,]*,/i;
  8030. /**
  8031. * Write each percent-escape a data URI's payload does not need as the byte it
  8032. * names — three bytes for one, over the markup an inline SVG is made of.
  8033. * @param {string} body the url's content, quotes excluded
  8034. * @param {boolean} bare whether it is written as a url-token rather than a string
  8035. * @param {string} quote the quote the body is written in, empty for a url-token
  8036. * @returns {string} the body, its needless escapes decoded
  8037. */
  8038. const _decodePercentEscapes = (body, bare, quote) => {
  8039. const metadata = _DATA_URL_METADATA_RE.exec(body);
  8040. if (metadata === null) return body;
  8041. return (
  8042. metadata[0] +
  8043. body.slice(metadata[0].length).replace(_PERCENT_ESCAPE_RE, (escape) => {
  8044. const code = Number.parseInt(escape.slice(1), 16);
  8045. // Each escape names one byte, not one code point: `%C3%A9` is two bytes
  8046. // of one character, and writing them apart would re-encode as four.
  8047. if (code < 0x20 || code >= 0x7f) return escape;
  8048. const one = String.fromCharCode(code);
  8049. // `#` would start the fragment and `%` the next escape, so those two
  8050. // stay; the quote and the escape character would end or extend the
  8051. // string, and a url-token carries none of what the quotes were holding.
  8052. if (one === "#" || one === "%" || one === quote || one === "\\") {
  8053. return escape;
  8054. }
  8055. return bare && _UNQUOTABLE_URL_RE.test(one) ? escape : one;
  8056. })
  8057. );
  8058. };
  8059. /**
  8060. * `url("a.png")` → `url(a.png)` when the quoted URL is also a valid url-token,
  8061. * and a data URI's payload written as the bytes its escapes name. Nothing else
  8062. * about the URL is rewritten, since webpack passes it through to a server that
  8063. * may read it verbatim.
  8064. * @param {string} fn the lowercased function name
  8065. * @param {string} inner the already-joined argument text
  8066. * @returns {string | null} the shorter `url()`, or null to keep the function
  8067. */
  8068. const _minifyUrlFunction = (fn, inner) => {
  8069. if (fn !== "url") return null;
  8070. const quote = inner.charCodeAt(0);
  8071. if (quote !== CC_QUOTATION_MARK && quote !== CC_APOSTROPHE) return null;
  8072. if (inner.length < 2 || inner.charCodeAt(inner.length - 1) !== quote) {
  8073. return null;
  8074. }
  8075. const written = inner.slice(1, -1);
  8076. const mark = String.fromCharCode(quote);
  8077. // The renderer reads source, so it is offered the decoded payload whatever
  8078. // the switches say — `escapes` decides how the escapes are *printed*, not
  8079. // whether a data URL holds a document.
  8080. const decoded = _decodePercentEscapes(written, false, mark);
  8081. // A `data:` payload a renderer rewrites is serialized afresh: what it hands
  8082. // back is no longer what the quotes were written around. Quoted as written
  8083. // where the answer is not in yet, the unquoted form below being unable to
  8084. // carry what a document payload contains.
  8085. if (_deferEmbeddedSource !== undefined) {
  8086. const deferred = _deferDataUrl(
  8087. decoded,
  8088. (url) => `${fn}(${_serializeUrl(url, mark)})`,
  8089. `${fn}(${mark}${written}${mark})`
  8090. );
  8091. if (deferred !== null) return deferred;
  8092. }
  8093. const rendered = _renderDataUrl(decoded);
  8094. if (rendered !== null) return `${fn}(${_serializeUrl(rendered, mark)})`;
  8095. // Taking the quotes off what is a url-token without them is `normalizeQuotes`;
  8096. // writing a percent-escape as the byte it names is not a switch of its own —
  8097. // an escape and the byte name one string, so nothing reads them apart.
  8098. const body = decoded;
  8099. if (!_transforms.normalizeQuotes) {
  8100. return body === written ? null : `${fn}(${mark}${body}${mark})`;
  8101. }
  8102. if (!_UNQUOTABLE_URL_RE.test(body)) return `${fn}(${body})`;
  8103. // A code point a url-token cannot carry can still be escaped into one, which
  8104. // costs a byte where the two quotes cost two — so one of them is shorter
  8105. // escaped and two are not.
  8106. const escaped = _escapeUrlBody(body);
  8107. if (escaped !== null) return `${fn}(${escaped})`;
  8108. // It keeps its quotes, and with them whatever the escapes gave back.
  8109. return body === written ? null : `${fn}(${mark}${body}${mark})`;
  8110. };
  8111. /**
  8112. * Whether `text` re-tokenizes as exactly one `<ident-token>` — no escapes, and
  8113. * no leading digit (`1x` is a dimension, `-1` a number) — so an attribute
  8114. * selector's quoted value can drop its quotes.
  8115. * @param {string} text the string's content, quotes excluded
  8116. * @returns {boolean} true when it is a bare identifier
  8117. */
  8118. const _isBareIdent = (text) => {
  8119. const n = text.length;
  8120. if (n === 0) return false;
  8121. for (let i = 0; i < n; i++) {
  8122. if (!_isIdentLike(text.charCodeAt(i))) return false;
  8123. }
  8124. const first = text.charCodeAt(0);
  8125. if (_isDigit(first)) return false;
  8126. // `-` alone is a delim, `-1` a number; `-x` / `--x` are idents.
  8127. if (first === CC_HYPHEN_MINUS) return n > 1 && !_isDigit(text.charCodeAt(1));
  8128. return true;
  8129. };
  8130. /**
  8131. * Whether the code point an escape stands for can be written literally at
  8132. * `index` of an identifier: it must be an ASCII ident code point, and the first
  8133. * one must also be able to *start* an ident (`\31 x` is the class `1x`, but a
  8134. * literal `1x` is a dimension). Non-ASCII stays escaped — writing it literally
  8135. * would make the stylesheet's own encoding load-bearing.
  8136. * @param {number} value the escape's code point
  8137. * @param {string} written the identifier text emitted so far
  8138. * @returns {boolean} true when the literal code point re-tokenizes the same
  8139. */
  8140. const _canUnescape = (value, written) => {
  8141. if (value >= 128 || !_isIdentCodePoint(value)) return false;
  8142. if (written.length === 0) {
  8143. return _isLetter(value) || value === CC_LOW_LINE;
  8144. }
  8145. // `-1` is a number and `--1` a valid ident, so only the second code point of
  8146. // a leading `-` is constrained.
  8147. if (written === "-") return !_isDigit(value);
  8148. return true;
  8149. };
  8150. /**
  8151. * Shorten the escapes in an identifier, two ways that both re-tokenize to the
  8152. * same name: write the code point literally where it needs no escape at all
  8153. * (`\41 bc` → `Abc`), and otherwise drop the whitespace that terminates a hex
  8154. * escape when the code point after it, *inside this same token*, cannot extend
  8155. * it (`\32 xl` → `\32xl`). A terminator that ends the token stays: the walk's
  8156. * own separator would take its place and swallow the whitespace that follows
  8157. * (`.\32 x` is the class `2` and a descendant `x`, not the class `2x`).
  8158. * @param {string} text the identifier's source text
  8159. * @returns {string} the identifier, escapes shortened
  8160. */
  8161. const _minifyIdentEscapes = (text) => {
  8162. if (!text.includes("\\")) return text;
  8163. const n = text.length;
  8164. let out = "";
  8165. let i = 0;
  8166. while (i < n) {
  8167. const c = text.charCodeAt(i);
  8168. if (c !== CC_REVERSE_SOLIDUS || i + 1 >= n) {
  8169. out += text[i];
  8170. i++;
  8171. continue;
  8172. }
  8173. let digitEnd = i + 1;
  8174. let value = 0;
  8175. while (
  8176. digitEnd < n &&
  8177. digitEnd - i <= 6 &&
  8178. _isHexDigit(text.charCodeAt(digitEnd))
  8179. ) {
  8180. value = value * 16 + Number.parseInt(text[digitEnd], 16);
  8181. digitEnd++;
  8182. }
  8183. const digits = digitEnd - i - 1;
  8184. if (digits === 0) {
  8185. // An identity escape (`\:`), which is what makes the code point literal —
  8186. // dropping it would change the token.
  8187. out += text[i] + text[i + 1];
  8188. i += 2;
  8189. continue;
  8190. }
  8191. // A hex escape ends at the first non-hex-digit; one whitespace there is
  8192. // consumed as its terminator rather than being part of the name — and a
  8193. // CRLF pair is one whitespace (`consumeExtraNewline`), so dropping only the
  8194. // CR would leave a raw newline inside the identifier.
  8195. let end = digitEnd;
  8196. if (end < n && _isWhiteSpace(text.charCodeAt(end))) {
  8197. end = consumeExtraNewline(text.charCodeAt(end), text, end + 1);
  8198. }
  8199. if (end !== digitEnd && end === n) {
  8200. // The terminator is also where the identifier stops. Rewriting it hands
  8201. // that job to the walk's own separator, which is not the same thing —
  8202. // `.a\31 .b` is one compound selector, `.a1 .b` two.
  8203. out += text.slice(i);
  8204. break;
  8205. }
  8206. if (_canUnescape(value, out)) {
  8207. out += String.fromCharCode(value);
  8208. i = end;
  8209. continue;
  8210. }
  8211. const keepTerminator =
  8212. end !== digitEnd &&
  8213. ((digits !== 6 && _isHexDigit(text.charCodeAt(end))) ||
  8214. _isWhiteSpace(text.charCodeAt(end)));
  8215. out += text.slice(i, keepTerminator ? end : digitEnd);
  8216. i = end;
  8217. }
  8218. return out;
  8219. };
  8220. /**
  8221. * Write a token's terminator back. A `\` left dangling at EOF has to be replaced
  8222. * rather than kept: kept, it would escape the terminator and leave the token open
  8223. * on exactly the input this repairs. What it stands for differs by token — nothing
  8224. * in a string (§4.3.5), U+FFFD in a url (§4.3.6 reads it as an escape, §4.3.7 ends
  8225. * one at EOF with the replacement character).
  8226. * @param {string} raw the token's source text, missing its terminator
  8227. * @param {string} terminator the character that closes it
  8228. * @param {string} dangling what a `\` left at EOF contributed to the token's value
  8229. * @returns {string} the terminated token
  8230. */
  8231. const _terminate = (raw, terminator, dangling) => {
  8232. let backslashes = 0;
  8233. while (
  8234. raw.length - 1 - backslashes > 0 &&
  8235. raw.charCodeAt(raw.length - 1 - backslashes) === CC_REVERSE_SOLIDUS
  8236. ) {
  8237. backslashes++;
  8238. }
  8239. const body = backslashes % 2 === 1 ? `${raw.slice(0, -1)}${dangling}` : raw;
  8240. return `${body}${terminator}`;
  8241. };
  8242. /**
  8243. * The `url()` counterpart of `_isClosedString`: §4.3.6 ends a url-token at EOF, so
  8244. * it has no closing `)` and the printer's next byte would land inside it.
  8245. * @param {string} raw the url-token's source text
  8246. * @returns {boolean} true when the `)` is absent or itself escaped
  8247. */
  8248. const _isUnterminatedUrl = (raw) => {
  8249. const n = raw.length;
  8250. if (n < 2 || raw.charCodeAt(n - 1) !== CC_RIGHT_PARENTHESIS) return true;
  8251. let backslashes = 0;
  8252. for (let i = n - 2; i > 0 && raw.charCodeAt(i) === CC_REVERSE_SOLIDUS; i--) {
  8253. backslashes++;
  8254. }
  8255. return backslashes % 2 === 1;
  8256. };
  8257. /**
  8258. * Normalize a string token's quotes, following cssnano's `postcss-normalize-string`:
  8259. * prefer `"`, switch to whichever quote needs fewer escapes, and unescape a quote
  8260. * the chosen wrapper no longer escapes. A string already holding a literal quote
  8261. * keeps its wrapper — the other kind is the cheap one there. Value-identical; a
  8262. * `\`-newline continuation is left alone because dropping it could fuse into a
  8263. * preceding hex escape.
  8264. * A hex escape stays as written: the character it names costs gzip bytes where
  8265. * it saves raw ones (measured in `configCases/css/minimize-values`).
  8266. * @param {string} raw the string token's source text, quotes included
  8267. * @returns {string} the normalized string token
  8268. */
  8269. const _minifyString = (raw) => {
  8270. if (!_transforms.normalizeQuotes) return raw;
  8271. const quote = raw.charCodeAt(0);
  8272. const n = raw.length;
  8273. // A string the tokenizer closed at EOF has no matching final quote; rewriting
  8274. // one would move where it ends.
  8275. if (n < 2 || raw.charCodeAt(n - 1) !== quote) return raw;
  8276. // The overwhelmingly common string — already `"`-wrapped, no escape and no
  8277. // literal `'` — is unchanged; two native scans beat the counting loop below.
  8278. if (
  8279. quote === CC_QUOTATION_MARK &&
  8280. !raw.includes("\\") &&
  8281. !raw.includes("'")
  8282. ) {
  8283. return raw;
  8284. }
  8285. let literalQuotes = 0;
  8286. let escapedDoubleQuotes = 0;
  8287. let escapedSingleQuotes = 0;
  8288. for (let i = 1; i < n - 1; i++) {
  8289. const c = raw.charCodeAt(i);
  8290. if (c === CC_REVERSE_SOLIDUS) {
  8291. // The escape swallows the final quote (`"x\"` at EOF): the string never
  8292. // closed, so its last character is content, not a wrapper to rewrite.
  8293. if (i + 1 === n - 1) return raw;
  8294. const next = raw.charCodeAt(i + 1);
  8295. if (next === CC_QUOTATION_MARK) escapedDoubleQuotes++;
  8296. else if (next === CC_APOSTROPHE) escapedSingleQuotes++;
  8297. i++;
  8298. } else if (c === CC_QUOTATION_MARK || c === CC_APOSTROPHE) {
  8299. literalQuotes++;
  8300. }
  8301. }
  8302. if (literalQuotes !== 0) return raw;
  8303. if (escapedDoubleQuotes === 0 && escapedSingleQuotes === 0) {
  8304. return quote === CC_QUOTATION_MARK ? raw : `"${raw.slice(1, -1)}"`;
  8305. }
  8306. let want = quote;
  8307. if (quote === CC_APOSTROPHE && escapedDoubleQuotes === 0) {
  8308. want = CC_QUOTATION_MARK;
  8309. } else if (quote === CC_QUOTATION_MARK && escapedSingleQuotes === 0) {
  8310. want = CC_APOSTROPHE;
  8311. }
  8312. const wrapper = String.fromCharCode(want);
  8313. let out = wrapper;
  8314. for (let i = 1; i < n - 1; i++) {
  8315. const c = raw.charCodeAt(i);
  8316. if (c !== CC_REVERSE_SOLIDUS) {
  8317. out += raw[i];
  8318. continue;
  8319. }
  8320. const next = raw.charCodeAt(i + 1);
  8321. out +=
  8322. (next === CC_QUOTATION_MARK || next === CC_APOSTROPHE) && next !== want
  8323. ? raw[i + 1]
  8324. : raw[i] + raw[i + 1];
  8325. i++;
  8326. }
  8327. return out + wrapper;
  8328. };
  8329. /**
  8330. * Lowercase the pseudo-class and pseudo-element names in a selector's printed
  8331. * parts, in place: the name after a `:` matches ASCII case-insensitively, while
  8332. * a type selector's does not (`linearGradient` is an SVG element) and neither
  8333. * does an id, a class or an attribute's value. A functional pseudo prints as one
  8334. * token and lowercases its own name, its argument being the author's.
  8335. * @param {string[]} parts the prelude's printed parts
  8336. * @returns {void}
  8337. */
  8338. /**
  8339. * Lowercase the names in a media condition's printed parts, in place: a feature
  8340. * name, a media type, the keywords between them and a dimension's unit all match
  8341. * ASCII case-insensitively. A custom media query's `--name` does not, and a
  8342. * string or a nested function (a style query) carries text no such rule covers.
  8343. * @param {string[]} parts the condition's printed parts
  8344. * @returns {void}
  8345. */
  8346. const _lowercaseConditionParts = (parts) => {
  8347. for (let i = 0; i < parts.length; i++) {
  8348. const part = parts[i];
  8349. // Folded first, for the reason `_foldPseudoNames` folds first.
  8350. const lowered = asciiLowerCaseName(part);
  8351. if (lowered === part) continue;
  8352. const first = part.charCodeAt(0);
  8353. if (
  8354. first === CC_QUOTATION_MARK ||
  8355. first === CC_APOSTROPHE ||
  8356. (first === CC_HYPHEN_MINUS && part.charCodeAt(1) === CC_HYPHEN_MINUS) ||
  8357. part.includes("(")
  8358. ) {
  8359. continue;
  8360. }
  8361. parts[i] = lowered;
  8362. }
  8363. };
  8364. /**
  8365. * Fold a selector's pseudo names to lowercase and drop the redundant colon of a
  8366. * CSS2 pseudo-element, in one pass over the printed parts: both read the part
  8367. * after a `:`, and the fold is what the legacy name is then matched against.
  8368. * Empty parts are stepped over — a comment between the colons printed away, and
  8369. * the tokenizer had already dropped it anyway.
  8370. * @param {string[]} parts the prelude's printed parts
  8371. * @returns {void}
  8372. */
  8373. const _foldPseudoNames = (parts) => {
  8374. const dropping = _transforms.shortenSelectors;
  8375. // The two non-empty parts before the current one.
  8376. let first = -1;
  8377. let second = -1;
  8378. for (let i = 0; i < parts.length; i++) {
  8379. let part = parts[i];
  8380. if (part.length === 0) continue;
  8381. // A pseudo's name matches ASCII case-insensitively, a type selector's does
  8382. // not (`linearGradient` is an SVG element) and neither does an id, a class
  8383. // or an attribute's value. A functional pseudo prints as one token and
  8384. // folds its own name, its argument being the author's.
  8385. if (second !== -1 && parts[second] === ":") {
  8386. const lowered = asciiLowerCaseName(part);
  8387. if (lowered !== part && !part.includes("(")) {
  8388. part = lowered;
  8389. parts[i] = lowered;
  8390. }
  8391. // Folded first, so the legacy name below is matched against the folded
  8392. // text rather than folded a second time.
  8393. if (
  8394. dropping &&
  8395. first !== -1 &&
  8396. parts[first] === ":" &&
  8397. LEGACY_PSEUDO_ELEMENTS.has(part)
  8398. ) {
  8399. parts[first] = "";
  8400. }
  8401. }
  8402. first = second;
  8403. second = i;
  8404. }
  8405. };
  8406. // The `An+B` microsyntax (CSS Syntax 3 §6), whose whitespace and `+` are its
  8407. // own: `2n + 1`, `+3` and `2N+1` all say what a shorter spelling does.
  8408. const _NTH_RE =
  8409. /^\s*(?:([+-]?)\s*(\d*)[nN]\s*(?:([+-])\s*(\d+))?|([+-]?)\s*(\d+))\s*$/;
  8410. /**
  8411. * Write one `An+B` in its shortest equal spelling.
  8412. * @param {string} text the argument between the parentheses
  8413. * @returns {string | null} the shortest spelling, or null when it is not `An+B`
  8414. */
  8415. const _minifyNth = (text) => {
  8416. const lower = text.trim().toLowerCase();
  8417. let a;
  8418. let b;
  8419. if (lower === "even") {
  8420. a = 2;
  8421. b = 0;
  8422. } else if (lower === "odd") {
  8423. a = 2;
  8424. b = 1;
  8425. } else {
  8426. const parts = _NTH_RE.exec(text);
  8427. if (parts === null) return null;
  8428. if (parts[6] !== undefined) {
  8429. a = 0;
  8430. b = Number(`${parts[5] === "-" ? "-" : ""}${parts[6]}`);
  8431. } else {
  8432. a = Number(
  8433. `${parts[1] === "-" ? "-" : ""}${parts[2] === "" ? "1" : parts[2]}`
  8434. );
  8435. b = parts[4] === undefined ? 0 : Number(`${parts[3]}${parts[4]}`);
  8436. }
  8437. }
  8438. // Past the safe range a rewrite would print a different integer than it read.
  8439. if (!Number.isSafeInteger(a) || !Number.isSafeInteger(b)) return null;
  8440. if (a === 0) return String(b);
  8441. // A step forward only ever reaches `B` again from below, and an index under 1
  8442. // matches nothing — so those terms are dropped by naming the first real one.
  8443. // Landing on the step itself is the bare `An`, which starts there anyway.
  8444. if (a > 0) {
  8445. if (b < 1) b = ((((b - 1) % a) + a) % a) + 1;
  8446. if (b === a) b = 0;
  8447. }
  8448. // The one An+B a keyword names in fewer bytes.
  8449. if (a === 2 && b === 1) return "odd";
  8450. const step = `${a === 1 ? "" : a === -1 ? "-" : a}n`;
  8451. return b === 0 ? step : `${step}${b > 0 ? "+" : "-"}${Math.abs(b)}`;
  8452. };
  8453. // One `urange` (CSS Syntax 3 §11.2): `U+` then hex digits, then either a `-`
  8454. // and a second run or trailing `?` wildcards. Case is insignificant.
  8455. const _URANGE_RE =
  8456. /^u\+(?:([\da-f]{0,6})(\?{1,6})|([\da-f]{1,6})(?:-([\da-f]{1,6}))?)$/i;
  8457. /**
  8458. * Write one `urange` in its shortest equal spelling: leading zeros carry
  8459. * nothing, and a range whose start is a prefix followed by zeros and whose end
  8460. * is that prefix followed by `f`s is what the `?` wildcard says.
  8461. * @param {string} range one urange token
  8462. * @returns {string} the shortest spelling, or `range` when nothing is shorter
  8463. */
  8464. const _minifyUnicodeRange = (range) => {
  8465. if (!_transforms.shortenNumbers) return range;
  8466. const parts = _URANGE_RE.exec(range);
  8467. if (parts === null) return range;
  8468. /** @type {(hex: string) => string} */
  8469. const strip = (hex) => hex.replace(/^0+(?=.)/, "");
  8470. // `U+00??` covers what `U+??` does — the zeros are as leading as any other.
  8471. if (parts[2] !== undefined) {
  8472. const head = parts[1].replace(/^0+/, "");
  8473. const out = `U+${head}${parts[2]}`;
  8474. return out.length < range.length ? out : range;
  8475. }
  8476. const start = strip(parts[3]);
  8477. if (parts[4] === undefined) {
  8478. const out = `U+${start}`;
  8479. return out.length < range.length ? out : range;
  8480. }
  8481. const end = strip(parts[4]);
  8482. let best = `U+${start}-${end}`;
  8483. // The wildcard needs both bounds the same width to compare digit by digit.
  8484. if (start.length <= end.length) {
  8485. const padded = start.padStart(end.length, "0");
  8486. let wild = 0;
  8487. while (
  8488. wild < padded.length &&
  8489. padded.charAt(padded.length - 1 - wild) === "0" &&
  8490. end.charAt(end.length - 1 - wild).toLowerCase() === "f"
  8491. ) {
  8492. wild++;
  8493. }
  8494. while (wild > 0) {
  8495. const head = padded.slice(0, padded.length - wild);
  8496. // `equalsLowerCase` lowercases only its first argument.
  8497. if (
  8498. equalsLowerCase(head, end.slice(0, end.length - wild).toLowerCase())
  8499. ) {
  8500. const out = `U+${head.replace(/^0+/, "")}${"?".repeat(wild)}`;
  8501. if (out.length < best.length) best = out;
  8502. break;
  8503. }
  8504. wild--;
  8505. }
  8506. }
  8507. return best.length < range.length ? best : range;
  8508. };
  8509. // `@keyframes` and every vendor spelling of it, whose prelude is a name and
  8510. // whose child preludes are keyframe selectors rather than selector lists.
  8511. const KEYFRAMES_AT_RULE_RE = /^(?:-[a-z]+-)?keyframes$/i;
  8512. // Media Queries 4 §2.4: `min-`/`max-` prefixes exist only on range-type media
  8513. // features, and `min-X: Y` is exactly `X >= Y`. The range spelling is the newer
  8514. // one, so it is only reached for where the target reads it.
  8515. const _RANGE_PREFIX_RE = /^(min|max)-(.+)$/i;
  8516. /**
  8517. * Rewrite a media feature's `min-` / `max-` prefix to the range spelling, in the
  8518. * printed parts, in place. Only a whole `(<feature>:<value>)` — a condition made
  8519. * of anything else (a boolean feature, an `and` chain, a nested block) is left
  8520. * for its own parts to handle.
  8521. * @param {string[]} parts the block's printed parts
  8522. * @returns {void}
  8523. */
  8524. const _useRangeSpelling = (parts) => {
  8525. if (!_transforms.shortenMediaQueries) return;
  8526. // The separators go with the join's condition trim, so a spaced
  8527. // `( min-width : 1px )` is the same feature as a tight one.
  8528. /** @type {number[]} */
  8529. const filled = [];
  8530. for (let i = 0; i < parts.length; i++) {
  8531. if (parts[i].length !== 0 && parts[i] !== _SEP) filled.push(i);
  8532. }
  8533. if (filled.length < 3 || parts[filled[1]] !== ":") return;
  8534. const feature = _RANGE_PREFIX_RE.exec(parts[filled[0]]);
  8535. if (feature === null) return;
  8536. parts[filled[0]] = feature[2];
  8537. parts[filled[1]] = feature[1].toLowerCase() === "min" ? ">=" : "<=";
  8538. };
  8539. // One printed `(<feature><comparison><value>)`, the shape the range spelling
  8540. // leaves behind. Media Queries 4 §2.4.3 writes an interval with both
  8541. // comparisons pointing the same way, so a pair is only ever joined as `<`.
  8542. const _RANGE_CONDITION_RE = /^\(([-\w]+)(>=|<=|>|<)([^()<>]+)\)$/;
  8543. /**
  8544. * Collapse an `and` of two one-sided ranges on one feature into the interval
  8545. * that says the same (`(width>=1px) and (width<=2px)` is `(1px<=width<=2px)`),
  8546. * in the printed parts, in place. Only a bounded pair — two comparisons the
  8547. * same way round are not an interval, and `or` is not a conjunction.
  8548. * @param {string[]} parts the prelude's printed parts
  8549. * @returns {void}
  8550. */
  8551. const _collapseRangeInterval = (parts) => {
  8552. if (!_transforms.shortenMediaQueries) return;
  8553. /** @type {number[]} */
  8554. const filled = [];
  8555. for (let i = 0; i < parts.length; i++) {
  8556. if (parts[i].length !== 0 && parts[i] !== _SEP) filled.push(i);
  8557. }
  8558. for (let i = 0; i + 2 < filled.length; i++) {
  8559. if (!equalsLowerCase(parts[filled[i + 1]], "and")) continue;
  8560. const left = _RANGE_CONDITION_RE.exec(parts[filled[i]]);
  8561. if (left === null) continue;
  8562. const right = _RANGE_CONDITION_RE.exec(parts[filled[i + 2]]);
  8563. if (right === null) continue;
  8564. if (!equalsLowerCase(left[1], right[1])) continue;
  8565. // One has to bound it from below and the other from above.
  8566. const lower = left[2].startsWith(">") ? left : right;
  8567. const upper = left[2].startsWith(">") ? right : left;
  8568. if (!lower[2].startsWith(">") || !upper[2].startsWith("<")) continue;
  8569. const low = `${lower[3]}${lower[2] === ">=" ? "<=" : "<"}`;
  8570. parts[filled[i]] = `(${low}${left[1]}${upper[2]}${upper[3]})`;
  8571. parts[filled[i + 1]] = "";
  8572. parts[filled[i + 2]] = "";
  8573. i += 2;
  8574. }
  8575. };
  8576. /**
  8577. * Drop the universal selector a compound already implies (`*:before` is
  8578. * `:before`), in the printed parts, in place. `*` carries no specificity and
  8579. * matches every element, so a qualified compound means the same without it.
  8580. * @param {string[]} parts the prelude's printed parts
  8581. * @returns {void}
  8582. */
  8583. /**
  8584. * Whether the simple selector at `at` selects a featureless element, which
  8585. * matches no type or universal selector — so the `*` before it is what keeps
  8586. * the rule from matching, not a spelling of it.
  8587. * @param {string[]} parts the printed selector, one piece per token
  8588. * @param {number} at where the simple selector starts
  8589. * @returns {boolean} true when the `*` before it has to stay
  8590. */
  8591. const _selectsFeatureless = (parts, at) => {
  8592. if (parts[at] !== ":") return false;
  8593. let name = at + 1;
  8594. while (name < parts.length && parts[name].length === 0) name++;
  8595. if (name >= parts.length) return false;
  8596. // A functional pseudo-class arrives with its arguments attached.
  8597. const open = parts[name].indexOf("(");
  8598. const named = open === -1 ? parts[name] : parts[name].slice(0, open);
  8599. return FEATURELESS_PSEUDO_CLASSES.has(toLowerCaseIfNeeded(named));
  8600. };
  8601. /**
  8602. * Drop the universal selector a compound already implies (`*:before` is
  8603. * `:before`), in the printed parts, in place. `*` carries no specificity and
  8604. * matches every element, so a qualified compound means the same without it —
  8605. * except before a featureless pseudo-class, which no element with features
  8606. * matches.
  8607. * @param {string[]} parts the prelude's printed parts
  8608. * @returns {void}
  8609. */
  8610. const _dropImpliedUniversalSelector = (parts) => {
  8611. if (!_transforms.shortenSelectors) return;
  8612. let previous = -1;
  8613. for (let i = 0; i < parts.length; i++) {
  8614. const part = parts[i];
  8615. if (part.length === 0) continue;
  8616. if (
  8617. previous !== -1 &&
  8618. parts[previous] === "*" &&
  8619. COMPOUND_CONTINUATIONS.has(part.charAt(0)) &&
  8620. (previous === 0 || parts[previous - 1] !== "|") &&
  8621. !_selectsFeatureless(parts, i)
  8622. ) {
  8623. parts[previous] = "";
  8624. }
  8625. previous = i;
  8626. }
  8627. };
  8628. /**
  8629. * Split a selector list at the commas that separate its selectors. A comma
  8630. * inside `:is(…)`, an attribute value or a string belongs to one selector.
  8631. * @param {string} prelude the printed selector list
  8632. * @returns {string[]} the selectors, in written order
  8633. */
  8634. const _splitSelectorList = (prelude) => {
  8635. /** @type {string[]} */
  8636. const out = [];
  8637. let depth = 0;
  8638. let quote = 0;
  8639. let start = 0;
  8640. for (let i = 0; i < prelude.length; i++) {
  8641. const code = prelude.charCodeAt(i);
  8642. if (code === CC_REVERSE_SOLIDUS) {
  8643. i++;
  8644. continue;
  8645. }
  8646. if (quote !== 0) {
  8647. if (code === quote) quote = 0;
  8648. continue;
  8649. }
  8650. if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
  8651. quote = code;
  8652. } else if (code === CC_LEFT_PARENTHESIS || code === CC_LEFT_SQUARE) {
  8653. depth++;
  8654. } else if (code === CC_RIGHT_PARENTHESIS || code === CC_RIGHT_SQUARE) {
  8655. depth--;
  8656. } else if (code === CC_COMMA && depth === 0) {
  8657. out.push(prelude.slice(start, i));
  8658. start = i + 1;
  8659. }
  8660. }
  8661. out.push(prelude.slice(start));
  8662. return out;
  8663. };
  8664. // A substitution expands to a whole token sequence, so two identical references
  8665. // are not one repeated value: with `--x:1px 2px`, `margin:var(--x) var(--x)` is
  8666. // four values, not two. `--name(` is a custom function (CSS Functions).
  8667. const _SUBSTITUTION_RE = new RegExp(
  8668. `(?:^|[^\\w-])(?:${[...SUBSTITUTION_FUNCTIONS].join("|")})\\(|--[\\w-]*\\(`,
  8669. "i"
  8670. );
  8671. /**
  8672. * Whether a value reads a substitution, so its tokens are not what the engine
  8673. * will see there.
  8674. * @param {string} text the text to look in
  8675. * @returns {boolean} true when a substitution may stand in it
  8676. */
  8677. const _hasSubstitution = (text) =>
  8678. // Every shape `_SUBSTITUTION_RE` accepts ends in `(`, and most values hold
  8679. // none at all — so one scan for it answers them without running the shape.
  8680. text.includes("(") && _SUBSTITUTION_RE.test(text);
  8681. /**
  8682. * {@link _hasSubstitution} over a span of the input, which is cut out only once
  8683. * the span turns out to hold a `(` at all.
  8684. * @param {number} from the span's start offset
  8685. * @param {number} to the span's end offset
  8686. * @returns {boolean} true when a substitution may stand in it
  8687. */
  8688. const _hasSubstitutionInSpan = (from, to) => {
  8689. // The walk asks for a declaration's span and the printer asks again for the
  8690. // same one, so one entry spares the second scan and the slice under it.
  8691. if (from === _substitutionSpanFrom && to === _substitutionSpanTo) {
  8692. return _substitutionSpanHas;
  8693. }
  8694. let has = false;
  8695. for (let i = from; i < to; i++) {
  8696. if (_input.charCodeAt(i) === CC_LEFT_PARENTHESIS) {
  8697. has = _SUBSTITUTION_RE.test(_input.slice(from, to));
  8698. break;
  8699. }
  8700. }
  8701. _substitutionSpanFrom = from;
  8702. _substitutionSpanTo = to;
  8703. _substitutionSpanHas = has;
  8704. return has;
  8705. };
  8706. // The span `_hasSubstitutionInSpan` last answered for. Offsets index the current
  8707. // input, so a new stylesheet resets them.
  8708. let _substitutionSpanFrom = -1;
  8709. let _substitutionSpanTo = -1;
  8710. let _substitutionSpanHas = false;
  8711. /**
  8712. * Split a declaration value into its top-level components. Whitespace parts
  8713. * them, and so does a `/` delim — it separates `border-radius`'s two boxes and
  8714. * needs no whitespace of its own. A comment is no tree node, so the gap it
  8715. * leaves between two children parts them too: it ends both tokens, and a
  8716. * rewritten value joins its components with a space that has to stand where
  8717. * the comment did.
  8718. * @param {CssPath} path the accessor positioned on the declaration
  8719. * @param {Node} node the declaration whose value's children are read
  8720. * @param {PrintContext} writer the print context (children's printed text)
  8721. * @returns {string[]} the components, with a `/` delim as its own entry
  8722. */
  8723. const _valueComponents = (path, node, writer) => {
  8724. /** @type {string[]} */
  8725. const components = [];
  8726. let current = "";
  8727. let previousEnd = -1;
  8728. const count = path.childCount(node);
  8729. for (let at = 0; at < count; at++) {
  8730. const child = path.childAt(node, at);
  8731. const type = path.type(child);
  8732. const text = writer.get(child);
  8733. const start = path.start(child);
  8734. if (current.length !== 0 && start !== previousEnd) {
  8735. components.push(current);
  8736. current = "";
  8737. }
  8738. previousEnd = path.end(child);
  8739. if (type === T_WHITESPACE || (type === T_DELIM && text === "/")) {
  8740. if (current.length !== 0) components.push(current);
  8741. current = "";
  8742. if (type === T_DELIM) components.push("/");
  8743. continue;
  8744. }
  8745. current += text;
  8746. }
  8747. if (current.length !== 0) components.push(current);
  8748. return components;
  8749. };
  8750. /**
  8751. * A custom property's value, token for token: every token is written back as
  8752. * written, and a run of whitespace and dropped comments between two of them is
  8753. * one boundary — the space they need, or nothing where a comma or a block edge
  8754. * is already one, which leaves every `var()` the same token stream.
  8755. * @param {CssPath} path the accessor positioned on the declaration
  8756. * @param {ComponentValue[]} children the tokens to print
  8757. * @param {PrintContext} writer the print context (holds the kept comments)
  8758. * @param {number} from source offset the tokens start at
  8759. * @param {number} to source offset they end at
  8760. * @returns {string} the printed value
  8761. */
  8762. const _customPropertyValue = (path, children, writer, from, to) => {
  8763. let out = "";
  8764. let at = from;
  8765. // The boundary held back: whether a whitespace token stood in it, whether a
  8766. // dropped comment did, and whether a comma closed it.
  8767. let spaced = false;
  8768. let dropped = false;
  8769. let comma = false;
  8770. for (const child of children) {
  8771. const start = path.start(child);
  8772. // Every other token is a child of its own, so a gap is comments only.
  8773. if (at !== start) {
  8774. const kept = writer.takeInserts(at, start);
  8775. if (kept === "") {
  8776. dropped = true;
  8777. } else {
  8778. if (spaced && out !== "") out += " ";
  8779. out += kept;
  8780. spaced = false;
  8781. dropped = false;
  8782. comma = false;
  8783. }
  8784. }
  8785. at = path.end(child);
  8786. const type = path.type(child);
  8787. if (type === T_WHITESPACE) {
  8788. spaced = true;
  8789. continue;
  8790. }
  8791. if (type === T_COMMA) {
  8792. out += ",";
  8793. spaced = false;
  8794. dropped = false;
  8795. comma = true;
  8796. continue;
  8797. }
  8798. const text = _customPropertyToken(path, child, writer);
  8799. // A comma or a block delimiter is a boundary of its own, so the whitespace
  8800. // beside one says nothing — except before `(`, which an ident in front of
  8801. // makes a function token, and that is what `_wouldFuseTokens` answers.
  8802. if (!comma && out !== "") {
  8803. const last = out.charCodeAt(out.length - 1);
  8804. const fuses = dropped && _wouldFuseTokens(last, text, out);
  8805. const parted =
  8806. CUSTOM_PROPERTY_CLOSERS.includes(out[out.length - 1]) ||
  8807. CUSTOM_PROPERTY_OPENERS.includes(text[0]);
  8808. if (fuses || (spaced && !parted)) out += " ";
  8809. }
  8810. out += text;
  8811. spaced = false;
  8812. dropped = false;
  8813. comma = false;
  8814. }
  8815. // A trailing boundary separates the last token from a `)` or the value's end,
  8816. // neither of which it can fuse with; only a kept comment in it still prints.
  8817. if (at !== to) {
  8818. const kept = writer.takeInserts(at, to);
  8819. if (kept !== "") {
  8820. if (spaced && out !== "") out += " ";
  8821. out += kept;
  8822. }
  8823. }
  8824. return out;
  8825. };
  8826. /** What makes a token worth reading through rather than writing back whole. */
  8827. const CUSTOM_PROPERTY_REWRITABLE_RE = /[\t\n\f\r ,]|\/\*/;
  8828. // Block delimiters a token cannot fuse across, so whitespace beside one goes.
  8829. // `(` is not among the openers: an ident in front of it makes a function token.
  8830. const CUSTOM_PROPERTY_CLOSERS = ")]}";
  8831. const CUSTOM_PROPERTY_OPENERS = "[{";
  8832. /**
  8833. * One token of a custom property's value, as written — recursing into a
  8834. * function or block so the boundaries nested in one print as they do at the top
  8835. * level. A source holding neither whitespace, a comma nor a comment holds none
  8836. * at any depth, so it is written back whole. The one token not written as it
  8837. * stands is a url or a string the input ran out of inside, which the engine
  8838. * closes there and so does this.
  8839. * @param {CssPath} path the accessor positioned on the declaration
  8840. * @param {ComponentValue} child the token to print
  8841. * @param {PrintContext} writer the print context (holds the kept comments)
  8842. * @returns {string} the printed token
  8843. */
  8844. const _customPropertyToken = (path, child, writer) => {
  8845. // Rewriting: the token already printed minified, its own nesting included.
  8846. if (_rewriteCustomProperties) return writer.get(child);
  8847. const source = path.source(child);
  8848. const type = path.type(child);
  8849. // The input ran out inside this token, so the engine closed it there and the
  8850. // printer has to as well — otherwise the `}` written after it is read as part
  8851. // of the token rather than as the end of the rule.
  8852. if (path.end(child) === _input.length) {
  8853. // A url token holds a value its text no longer spells only when an escape
  8854. // was truncated; `url(foo` the engine echoes open, and so does this.
  8855. if (
  8856. type === T_URL &&
  8857. source !== _input.slice(path.start(child), path.end(child))
  8858. ) {
  8859. return `${source})`;
  8860. }
  8861. if (type === T_STRING && !_isClosedString(source)) {
  8862. return source + source[0];
  8863. }
  8864. }
  8865. if (
  8866. (type !== T_FUNCTION && type !== T_SIMPLE_BLOCK) ||
  8867. !CUSTOM_PROPERTY_REWRITABLE_RE.test(source)
  8868. ) {
  8869. return source;
  8870. }
  8871. const start = path.start(child);
  8872. const end = path.end(child);
  8873. let opened = path.nameEnd(child) + 1;
  8874. let closer = ")";
  8875. if (type === T_SIMPLE_BLOCK) {
  8876. const block = path.blockToken(child);
  8877. opened = start + 1;
  8878. closer = block === "[" ? "]" : block === "{" ? "}" : ")";
  8879. }
  8880. // Closed at EOF: there is no closer to write back, and it ends the value.
  8881. const closed = _input[end - 1] === closer;
  8882. const inner = _customPropertyValue(
  8883. path,
  8884. path.children(child),
  8885. writer,
  8886. opened,
  8887. closed ? end - 1 : end
  8888. );
  8889. return `${_input.slice(start, opened)}${inner}${closed ? closer : ""}`;
  8890. };
  8891. /**
  8892. * Split top-level components into the layers a comma parts. A comma is no
  8893. * separator of its own, so it rides on the component it follows and a layer's
  8894. * last component has to be cut back out of it.
  8895. * @param {string[]} components the value's top-level components
  8896. * @returns {string[][]} one entry per layer, each its own component list
  8897. */
  8898. const _valueLayers = (components) => {
  8899. /** @type {string[][]} */
  8900. const layers = [];
  8901. /** @type {string[]} */
  8902. let current = [];
  8903. for (const component of components) {
  8904. let depth = 0;
  8905. let start = 0;
  8906. for (let i = 0; i < component.length; i++) {
  8907. const character = component[i];
  8908. if (character === "(") {
  8909. depth++;
  8910. } else if (character === ")") {
  8911. depth--;
  8912. } else if (character === "," && depth === 0) {
  8913. if (i > start) current.push(component.slice(start, i));
  8914. layers.push(current);
  8915. current = [];
  8916. start = i + 1;
  8917. }
  8918. }
  8919. if (start < component.length) current.push(component.slice(start));
  8920. }
  8921. layers.push(current);
  8922. return layers;
  8923. };
  8924. /**
  8925. * Drop the box values CSS's `{1,4}` notation already implies: an omitted value
  8926. * is copied from the opposite side, so a 4th equal to the 2nd, a 3rd equal to
  8927. * the 1st and a 2nd equal to the 1st are each redundant.
  8928. * @param {string[]} values one box's components
  8929. * @returns {string[] | null} the kept values, or `null` when the box cannot be collapsed
  8930. */
  8931. const _collapseBox = (values) => {
  8932. const n = values.length;
  8933. if (n === 0 || n > 4) return null;
  8934. for (const value of values) {
  8935. if (_hasSubstitution(value)) return null;
  8936. if (n > 1 && CSS_WIDE_KEYWORDS.has(toLowerCaseIfNeeded(value))) {
  8937. return null;
  8938. }
  8939. }
  8940. let end = n;
  8941. if (end === 4 && values[3] === values[1]) end = 3;
  8942. if (end === 3 && values[2] === values[0]) end = 2;
  8943. if (end === 2 && values[1] === values[0]) end = 1;
  8944. return values.slice(0, end);
  8945. };
  8946. /**
  8947. * Whether a shorthand's slots mix a bare non-zero number with a length or a
  8948. * percentage. No box slot reads both, so one of them was never read at all.
  8949. * @param {string[]} values one shorthand's slot values
  8950. * @returns {boolean} true when the two kinds stand together
  8951. */
  8952. const _mixesBareNumberWithLength = (values) => {
  8953. let bareNumber = false;
  8954. let measured = false;
  8955. for (const value of values) {
  8956. const kind = _componentKind(value);
  8957. if (kind === _COMPONENT_NUMBER) bareNumber = true;
  8958. else if (kind !== _COMPONENT_OTHER) measured = true;
  8959. }
  8960. return bareNumber && measured;
  8961. };
  8962. // What a shorthand component is, coarsely: a fold is only safe between two of
  8963. // the same kind, since a slot taking one takes the other. `0` is a length
  8964. // wherever a dimension is, and a bare non-zero number is neither (`padding:.25`
  8965. // is invalid, and folding it in would make the whole shorthand invalid).
  8966. const _COMPONENT_OTHER = 0;
  8967. const _COMPONENT_LENGTH = 1;
  8968. const _COMPONENT_PERCENTAGE = 2;
  8969. const _COMPONENT_NUMBER = 3;
  8970. /**
  8971. * @param {string} component one printed component of a shorthand's value
  8972. * @returns {number} one of the `_COMPONENT_*` kinds
  8973. */
  8974. const _componentKind = (component) => {
  8975. if (!NUMERIC_COMPONENT_RE.test(component)) return _COMPONENT_OTHER;
  8976. const last = component.charCodeAt(component.length - 1);
  8977. if (last === CC_PERCENTAGE) return _COMPONENT_PERCENTAGE;
  8978. // A unit is what makes a non-zero number a length, and a digit is no unit —
  8979. // `.25` is a bare number, which no box slot taking a length accepts.
  8980. if (!_isDigit(last)) return _COMPONENT_LENGTH;
  8981. return ZERO_NUMBER_RE.test(component) ? _COMPONENT_LENGTH : _COMPONENT_NUMBER;
  8982. };
  8983. // A number with an optional sign and fraction, then a unit or `%` or nothing.
  8984. const NUMERIC_COMPONENT_RE = /^[-+]?(?:\d+(?:\.\d+)?|\.\d+)(?:%|[a-z]+)?$/i;
  8985. // The same, written zero — every spelling of it, so `0.0` is a length too.
  8986. const ZERO_NUMBER_RE = /^[-+]?0*(?:\.0*)?$/;
  8987. /**
  8988. * One box or pair shorthand's value read back as the slots it fills — `1px 2px`
  8989. * as all four sides, so a longhand can take one of them over.
  8990. * @param {string} value the shorthand's printed value
  8991. * @param {number} slots how many the shorthand holds (4 for a box, 2 for a pair)
  8992. * @returns {string[] | null} the filled slots, or null when it fills none of them
  8993. */
  8994. const _expandBox = (value, slots) => {
  8995. const written = _splitTopLevelSpaces(value);
  8996. if (written.length === 0 || written.length > slots) return null;
  8997. const out = [...written];
  8998. // The `{1,4}` rule: an omitted slot takes the one two back, and the second
  8999. // takes the first.
  9000. for (let i = written.length; i < slots; i++) out.push(out[i < 2 ? 0 : i - 2]);
  9001. return out;
  9002. };
  9003. /**
  9004. * Fold a longhand into the shorthand of its own family standing directly before
  9005. * it, where the merged spelling is shorter than the two declarations. Adjacent
  9006. * only: anything between them is read between them, so folding would move what
  9007. * it wrote past it.
  9008. * @param {CssPath} path the accessor
  9009. * @param {Node[]} items the block's children
  9010. * @param {string[]} texts their printed text, rewritten in place
  9011. * @param {Set<number>} superseded the indices already dropped, added to here
  9012. * @returns {void}
  9013. */
  9014. const _foldFollowingLonghands = (path, items, texts, superseded) => {
  9015. let shorthand = -1;
  9016. for (let i = 0; i < items.length; i++) {
  9017. if (path.type(items[i]) !== T_DECLARATION || texts[i].length === 0) {
  9018. shorthand = -1;
  9019. continue;
  9020. }
  9021. if (superseded.has(i)) continue;
  9022. if (shorthand !== -1 && _foldInto(path, items, texts, shorthand, i)) {
  9023. superseded.add(i);
  9024. continue;
  9025. }
  9026. shorthand = i;
  9027. }
  9028. };
  9029. /**
  9030. * @param {CssPath} path the accessor
  9031. * @param {Node[]} items the block's children
  9032. * @param {string[]} texts their printed text, rewritten in place
  9033. * @param {number} at the shorthand's index
  9034. * @param {number} from the longhand's index
  9035. * @returns {boolean} true when the two were folded into one
  9036. */
  9037. const _foldInto = (path, items, texts, at, from) => {
  9038. if (path.important(items[at]) !== path.important(items[from])) return false;
  9039. const shorthandText = texts[at];
  9040. const longhandText = texts[from];
  9041. const shorthandColon = shorthandText.indexOf(":");
  9042. const longhandColon = longhandText.indexOf(":");
  9043. if (shorthandColon <= 0 || longhandColon <= 0) return false;
  9044. const property = _printedProperty(shorthandText, shorthandColon);
  9045. const sides = BOX_LONGHANDS.get(property) || PAIR_LONGHANDS.get(property);
  9046. if (sides === undefined) return false;
  9047. const slot = sides.indexOf(_printedProperty(longhandText, longhandColon));
  9048. if (slot === -1) return false;
  9049. const shorthandValue = _printedValue(shorthandText, shorthandColon);
  9050. const longhandValue = _printedValue(longhandText, longhandColon);
  9051. // A substitution may stand for any number of slots, and a comma list is no
  9052. // box at all — neither reads back as the slots this fills.
  9053. if (_hasSubstitution(shorthandValue) || _hasSubstitution(longhandValue)) {
  9054. return false;
  9055. }
  9056. if (_valueItems(shorthandValue).length !== 1) return false;
  9057. if (_splitTopLevelSpaces(longhandValue).length !== 1) return false;
  9058. // Validity is per property, and a component the property does not accept
  9059. // makes the whole shorthand invalid — which loses the slots that were fine.
  9060. // So the two are folded only where they are the same narrow numeric class,
  9061. // which every box slot of that family accepts wherever one of them does.
  9062. const kind = _componentKind(longhandValue);
  9063. if (kind === _COMPONENT_OTHER) return false;
  9064. for (const component of _splitTopLevelSpaces(shorthandValue)) {
  9065. if (_componentKind(component) !== kind) return false;
  9066. }
  9067. const filled = _expandBox(shorthandValue, sides.length);
  9068. if (filled === null) return false;
  9069. filled[slot] = longhandValue;
  9070. const collapsed = _collapseBox(filled);
  9071. if (collapsed === null) return false;
  9072. if (
  9073. collapsed.length !== 1 &&
  9074. ONE_VALUE_PAIR_SHORTHANDS.has(property) &&
  9075. !_overflowTwoValuesAllowed
  9076. ) {
  9077. return false;
  9078. }
  9079. if (
  9080. collapsed.length !== 1 &&
  9081. PLACE_SHORTHANDS.has(property) &&
  9082. !_placeShorthandAllowed
  9083. ) {
  9084. return false;
  9085. }
  9086. const important = path.important(items[at]) ? _IMPORTANT : "";
  9087. const folded = `${property}:${collapsed.join(" ")}${important};`;
  9088. if (folded.length >= shorthandText.length + longhandText.length) return false;
  9089. texts[at] = folded;
  9090. return true;
  9091. };
  9092. /**
  9093. * @param {string[]} a one component list
  9094. * @param {string[]} b another component list
  9095. * @returns {boolean} whether they are the same components in the same order
  9096. */
  9097. const _sameComponents = (a, b) =>
  9098. a.length === b.length && a.every((value, i) => value === b[i]);
  9099. /**
  9100. * Turn components back into fragments: a separator between each pair, except
  9101. * around the `/`, which needs none.
  9102. * @param {string[]} components components in order
  9103. * @returns {string[]} the fragments to join
  9104. */
  9105. const _spaced = (components) => {
  9106. /** @type {string[]} */
  9107. const parts = [];
  9108. for (const component of components) {
  9109. if (
  9110. parts.length !== 0 &&
  9111. component !== "/" &&
  9112. parts[parts.length - 1] !== "/"
  9113. ) {
  9114. parts.push(_SEP);
  9115. }
  9116. parts.push(component);
  9117. }
  9118. return parts;
  9119. };
  9120. /**
  9121. * Collapse a `{1,4}` box-notation value (see `BOX_SHORTHANDS`). `border-radius`
  9122. * carries two boxes — `<horizontal> / <vertical>` — which collapse independently,
  9123. * and a vertical box equal to the horizontal one is what the `/`-less form
  9124. * already means.
  9125. * @param {CssPath} path the accessor positioned on the declaration
  9126. * @param {string} property the declaration's lowercased property name
  9127. * @param {Node} node the declaration whose value's children are read
  9128. * @param {PrintContext} writer the print context (children's printed text)
  9129. * @returns {string[] | null} the fragments to join, or `null` to keep the value as it is
  9130. */
  9131. const _collapseBoxShorthand = (path, property, node, writer) => {
  9132. const components = _valueComponents(path, node, writer);
  9133. const slash = components.indexOf("/");
  9134. if (slash === -1) {
  9135. const box = _collapseBox(components);
  9136. return box === null || box.length === components.length
  9137. ? null
  9138. : _spaced(box);
  9139. }
  9140. // Only `border-radius` takes a second box. On the others a `/` is invalid, so
  9141. // the browser already drops the declaration — collapsing it would switch it on.
  9142. if (!SLASH_BOX_SHORTHANDS.has(property)) return null;
  9143. if (components.lastIndexOf("/") !== slash) return null;
  9144. const horizontal = _collapseBox(components.slice(0, slash));
  9145. const vertical = _collapseBox(components.slice(slash + 1));
  9146. if (horizontal === null || vertical === null) return null;
  9147. // Rebuilt even when neither box collapsed: the `/` is a delim token, so the
  9148. // whitespace around it is insignificant either way.
  9149. return _spaced(
  9150. _sameComponents(horizontal, vertical)
  9151. ? horizontal
  9152. : [...horizontal, "/", ...vertical]
  9153. );
  9154. };
  9155. // A number with no unit and no percent, which `flex` reads as a factor.
  9156. const _BARE_NUMBER_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
  9157. /**
  9158. * Rewrite a `flex` value to its keyword spelling where one exists.
  9159. * @param {CssPath} path the accessor positioned on the declaration
  9160. * @param {Node} node the declaration whose value's children are read
  9161. * @param {PrintContext} writer the print context (children's printed text)
  9162. * @returns {string[] | null} the fragments to join, or `null` to keep the value as it is
  9163. */
  9164. const _collapseFlexShorthand = (path, node, writer) => {
  9165. const components = _valueComponents(path, node, writer);
  9166. if (components.length !== 3) return null;
  9167. const keyword = FLEX_KEYWORDS.get(components.join(" ").toLowerCase());
  9168. if (keyword !== undefined) return [keyword];
  9169. // `<'flex-shrink'>` follows the grow factor and defaults to 1, so a `1` there
  9170. // says nothing — but only over a basis that cannot be read as a factor
  9171. // itself: CSS Flexbox 1 §7.1.1 reads a unitless zero not preceded by two
  9172. // factors as a factor, so `1 1 0` is not `1 0`.
  9173. return components[1] === "1" && !_BARE_NUMBER_RE.test(components[2])
  9174. ? [components[0], components[2]]
  9175. : null;
  9176. };
  9177. /**
  9178. * Rewrite a `font-weight` keyword to the number it is defined as. Only the
  9179. * longhand (and the `@font-face` descriptor, where the keywords mean the same):
  9180. * inside the `font` shorthand a `normal` may be the style or the variant
  9181. * instead, and the shorthand's own grammar decides which.
  9182. * @param {CssPath} path the accessor positioned on the declaration
  9183. * @param {Node} node the declaration whose value's children are read
  9184. * @param {PrintContext} writer the print context (children's printed text)
  9185. * @returns {string[] | null} the fragments to join, or `null` to keep the value as it is
  9186. */
  9187. const _collapseFontWeight = (path, node, writer) => {
  9188. const count = path.childCount(node);
  9189. let only = "";
  9190. for (let at = 0; at < count; at++) {
  9191. const text = writer.get(path.childAt(node, at));
  9192. if (text.length === 0) continue;
  9193. // A second component is a value this rewrite is not defined for.
  9194. if (only.length !== 0) return null;
  9195. only = text;
  9196. }
  9197. if (only.length === 0) return null;
  9198. const number = FONT_WEIGHT_NUMBERS.get(only.toLowerCase());
  9199. return number === undefined ? null : [number];
  9200. };
  9201. // The slots of one `<single-transition>`, told apart by what each can spell.
  9202. const _TRANSITION_TIME_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:s|ms)$/i;
  9203. const _TRANSITION_EASING_FUNCTION_RE = /^(?:cubic-bezier|steps|linear)\(/i;
  9204. /**
  9205. * Write one `transition` in the order its grammar lists the slots. `||` makes
  9206. * them order-free, so the same declaration has many spellings and one of them
  9207. * repeats across a stylesheet. The two `<time>`s stay in the order they were
  9208. * written — the first is the duration and the second the delay.
  9209. * @param {string[]} components the value's top-level components
  9210. * @returns {string[] | null} the fragments to join, or `null` to keep the value
  9211. */
  9212. const _orderTransitionSlots = (components) => {
  9213. if (components.length < 2) return null;
  9214. const times = [];
  9215. const easings = [];
  9216. const behaviors = [];
  9217. const names = [];
  9218. for (const one of components) {
  9219. const lowered = toLowerCaseIfNeeded(one);
  9220. if (_TRANSITION_TIME_RE.test(one)) {
  9221. times.push(one);
  9222. } else if (
  9223. EASING_KEYWORDS.has(lowered) ||
  9224. _TRANSITION_EASING_FUNCTION_RE.test(one)
  9225. ) {
  9226. easings.push(one);
  9227. } else if (TRANSITION_BEHAVIORS.has(lowered)) {
  9228. behaviors.push(one);
  9229. } else if (_PLAIN_IDENT_RE.test(one)) {
  9230. names.push(one);
  9231. } else {
  9232. return null;
  9233. }
  9234. }
  9235. // An easing keyword is a valid property name too, so a value with no name of
  9236. // its own leaves which slot it fills to the engine — that one is left alone.
  9237. if (names.length !== 1 || times.length > 2 || easings.length > 1) return null;
  9238. if (behaviors.length > 1) return null;
  9239. // `all` is the property a layer naming none transitions, so writing it is
  9240. // spare — as long as something else is left to keep the layer from emptying.
  9241. const named =
  9242. toLowerCaseIfNeeded(names[0]) === "all" && components.length > 1
  9243. ? []
  9244. : names;
  9245. const ordered = [...named, ...times, ...easings, ...behaviors];
  9246. if (ordered.length === 0) return null;
  9247. return ordered.join(" ") === components.join(" ") ? null : ordered;
  9248. };
  9249. /**
  9250. * Drop the trailing zero lengths a shadow's notation already implies. A shadow
  9251. * states its offsets as one run of lengths, and the ones past the count the
  9252. * grammar makes mandatory default to zero — so a trailing `0` says nothing.
  9253. * @param {string} property the declaration's lowercased property name
  9254. * @param {string[]} components the value's top-level components
  9255. * @returns {string[] | null} the fragments to join, or `null` to keep the value
  9256. */
  9257. const _dropShadowZeroLengths = (property, components) => {
  9258. const minimum = SHADOW_PROPERTIES.get(property);
  9259. if (minimum === undefined) return null;
  9260. // A quoted string could carry the comma the layer split reads.
  9261. for (const component of components) {
  9262. if (component.includes('"') || component.includes("'")) return null;
  9263. }
  9264. const layers = _valueLayers(components);
  9265. /** @type {string[]} */
  9266. const out = [];
  9267. let changed = false;
  9268. for (let i = 0; i < layers.length; i++) {
  9269. const layer = layers[i];
  9270. if (layer.length === 0) return null;
  9271. let end = layer.length;
  9272. while (end > 0 && !_NUMERIC_RE.test(layer[end - 1])) end--;
  9273. let start = end;
  9274. while (start > 0 && _NUMERIC_RE.test(layer[start - 1])) start--;
  9275. let last = end;
  9276. // The components are as authored — the zero-unit drop prints later — so a
  9277. // trailing zero is `0px` as often as `0`.
  9278. while (last - start > minimum && _isZeroLength(layer[last - 1])) {
  9279. last--;
  9280. changed = true;
  9281. }
  9282. const kept = [...layer.slice(0, last), ...layer.slice(end)];
  9283. const final = layers.length - 1;
  9284. for (let j = 0; j < kept.length; j++) {
  9285. out.push(j === kept.length - 1 && i !== final ? `${kept[j]},` : kept[j]);
  9286. }
  9287. }
  9288. return changed ? out : null;
  9289. };
  9290. /**
  9291. * Shorten every `<single-transition>` in a `transition`. Each layer is its own
  9292. * set of slots, so both the initial-keyword drop and the slot order run per
  9293. * layer rather than over the whole flat list.
  9294. * @param {string} property the declaration's lowercased property name
  9295. * @param {string[]} components the value's top-level components
  9296. * @returns {string[] | null} the fragments to join, or `null` to keep the value
  9297. */
  9298. const _collapseTransitionLayers = (property, components) => {
  9299. // A quoted string could carry a comma the layer split would part.
  9300. for (const component of components) {
  9301. if (component.includes('"') || component.includes("'")) return null;
  9302. }
  9303. const layers = _valueLayers(components);
  9304. /** @type {string[]} */
  9305. const out = [];
  9306. let changed = false;
  9307. for (let i = 0; i < layers.length; i++) {
  9308. let layer = layers[i];
  9309. if (layer.length === 0) return null;
  9310. const dropped = _dropInitialKeywords(property, layer);
  9311. if (dropped !== null) {
  9312. layer = dropped;
  9313. changed = true;
  9314. }
  9315. const timed = _dropZeroDuration(layer);
  9316. if (timed !== null) {
  9317. layer = timed;
  9318. changed = true;
  9319. }
  9320. const ordered = _orderTransitionSlots(layer);
  9321. if (ordered !== null) {
  9322. layer = ordered;
  9323. changed = true;
  9324. }
  9325. const last = layers.length - 1;
  9326. for (let j = 0; j < layer.length; j++) {
  9327. out.push(
  9328. j === layer.length - 1 && i !== last ? `${layer[j]},` : layer[j]
  9329. );
  9330. }
  9331. }
  9332. return changed ? out : null;
  9333. };
  9334. // A `font` component that is the size slot also carries a number.
  9335. const _CARRIES_DIGIT_RE = /\d/;
  9336. /**
  9337. * Write the `font` shorthand's weight as the number naming it. The slots before
  9338. * `<font-size>` are the style / variant / weight / width ones, and the family
  9339. * only ever follows the size — so a `bold` with a size after it is the weight,
  9340. * while `font: 12px bold` names a family and keeps the word.
  9341. * @param {string[]} components the value's top-level components
  9342. * @returns {string[] | null} the fragments to join, or `null` to keep the value
  9343. */
  9344. const _numberFontShorthandWeight = (components) => {
  9345. const size = components.findIndex(
  9346. (one) =>
  9347. _CARRIES_DIGIT_RE.test(one) ||
  9348. FONT_SIZE_KEYWORDS.has(toLowerCaseIfNeeded(one))
  9349. );
  9350. if (size <= 0) return null;
  9351. let changed = false;
  9352. const out = components.map((one, index) => {
  9353. if (index < size && equalsLowerCase(one, "bold")) {
  9354. changed = true;
  9355. return "700";
  9356. }
  9357. return one;
  9358. });
  9359. return changed ? out : null;
  9360. };
  9361. // A component that is exactly zero, whatever unit the zero-unit drop left it in.
  9362. const _ZERO_COMPONENT_RE = /^[+-]?0(?:\.0*)?$/;
  9363. // Zero written bare or as a percentage — a percentage of any size is nothing.
  9364. const _ZERO_OR_PERCENTAGE_RE = /^[+-]?0(?:\.0*)?%?$/;
  9365. const _ONE_COMPONENT_RE = /^\+?1(?:\.0*)?$/;
  9366. /**
  9367. * @param {string} value a printed component
  9368. * @returns {boolean} whether it is the number one
  9369. */
  9370. const _isOne = (value) => _ONE_COMPONENT_RE.test(value);
  9371. // A value that is one percentage and nothing else.
  9372. const _LONE_PERCENTAGE_RE = /^([+-]?)(\d*)(?:\.(\d*))?%$/;
  9373. /**
  9374. * Write an `<alpha-value>` percentage as the number naming the same quantity:
  9375. * the decimal point moved two places, which is exact where dividing is not.
  9376. * @param {string} property the lowercased property name
  9377. * @param {string} value the printed value
  9378. * @returns {string} the value, or the shorter number
  9379. */
  9380. const _numberAlphaValue = (property, value) => {
  9381. if (!_transforms.shortenNumbers) return value;
  9382. if (!ALPHA_VALUE_PROPERTIES.has(property)) return value;
  9383. const parts = _LONE_PERCENTAGE_RE.exec(value);
  9384. if (parts === null) return value;
  9385. const digits = `00${parts[2]}`;
  9386. const shifted = _normalizeNumber(
  9387. `${parts[1]}${digits.slice(0, -2)}.${digits.slice(-2)}${parts[3] || ""}`
  9388. );
  9389. return shifted.length < value.length ? shifted : value;
  9390. };
  9391. // A ratio whose denominator is the `1` an omitted one means, taken as a whole
  9392. // component so the `1` of `2/10` is no match.
  9393. const _RATIO_OVER_ONE_RE = /(^|\s)((?:\d+\.?\d*|\.\d+))\s*\/\s*1(?=$|\s)/g;
  9394. /**
  9395. * Drop a `<ratio>`'s denominator where it is the 1 an omitted one means.
  9396. * @param {string} property the lowercased property name
  9397. * @param {string} value the printed value
  9398. * @returns {string} the value, its `/1` dropped
  9399. */
  9400. const _dropRatioDenominator = (property, value) => {
  9401. if (!_transforms.shortenNumbers) return value;
  9402. // A substitution could expand to a number of its own, turning the `1` into
  9403. // the denominator of a ratio this does not see.
  9404. if (!RATIO_PROPERTIES.has(property) || _hasSubstitution(value)) {
  9405. return value;
  9406. }
  9407. return value.replace(_RATIO_OVER_ONE_RE, "$1$2");
  9408. };
  9409. /**
  9410. * Drop a layer's second value where the one-value form already means it.
  9411. * @param {string} property the lowercased property name
  9412. * @param {string} value the printed value
  9413. * @returns {string} the value, shortened where it says nothing
  9414. */
  9415. const _dropDefaultSecondValue = (property, value) => {
  9416. if (!_transforms.shortenValues) return value;
  9417. if (!AUTO_SECOND_VALUE_PROPERTIES.has(property)) return value;
  9418. // With `--x:1px 2px` the `auto` is a third value, so dropping it would turn a
  9419. // declaration the engine discards into one it keeps.
  9420. if (_hasSubstitution(value)) return value;
  9421. let changed = false;
  9422. const layers = _splitTopLevelArguments(value).map((layer) => {
  9423. const parts = _splitTopLevelSpaces(layer.trim());
  9424. if (parts.length !== 2 || !equalsLowerCase(parts[1], "auto")) return layer;
  9425. // Each of these stands alone, so the second value makes a declaration the
  9426. // engine drops — one a later declaration was written to beat.
  9427. const first = toLowerCaseIfNeeded(parts[0]);
  9428. if (
  9429. first === "cover" ||
  9430. first === "contain" ||
  9431. CSS_WIDE_KEYWORDS.has(first)
  9432. ) {
  9433. return layer;
  9434. }
  9435. changed = true;
  9436. return parts[0];
  9437. });
  9438. return changed ? layers.join(",") : value;
  9439. };
  9440. /**
  9441. * Reduce a transform function until it stops getting shorter: one reduction
  9442. * uncovers the next, `translate3d(x,0,0)` leaving the `translate(x,0)` that is
  9443. * `translate(x)`.
  9444. * @param {string} fn the lowercased function name
  9445. * @param {string} inner the already-joined argument text
  9446. * @returns {string | null} the shortest call, or null to keep the function
  9447. */
  9448. const _reduceTransformFunctionDeep = (fn, inner) => {
  9449. let out = _reduceTransformFunction(fn, inner);
  9450. if (out === null) return null;
  9451. // Fed back as the name and arguments it was built from, rather than printed
  9452. // and matched apart again — the reduction already hands back both.
  9453. for (;;) {
  9454. const next = _reduceTransformFunction(out[0], out[1]);
  9455. if (next === null) return `${out[0]}(${out[1]})`;
  9456. out = next;
  9457. }
  9458. };
  9459. // A component that is zero however it is spelled.
  9460. /** @type {(arg: string) => boolean} */
  9461. const _isZeroComponent = (arg) => _ZERO_COMPONENT_RE.test(arg);
  9462. // A translation's components are `<length-percentage>`, where a zero of either
  9463. // kind is the same no-op — a percentage resolves against the element's own size.
  9464. /** @type {(arg: string) => boolean} */
  9465. const _isZeroOffset = (arg) =>
  9466. _ZERO_OR_PERCENTAGE_RE.test(arg) || _ZERO_LENGTH_RE.test(arg);
  9467. // A translation's z is a `<length>` alone, so a percentage is invalid there and
  9468. // dropping it would revive a declaration the engine throws away.
  9469. /** @type {(arg: string) => boolean} */
  9470. const _isZeroLength = (arg) =>
  9471. _ZERO_COMPONENT_RE.test(arg) || _ZERO_LENGTH_RE.test(arg);
  9472. // The components a 3D matrix holds at zero to be the 2D one it names.
  9473. const _MATRIX3D_IDENTITY_ZEROS = [2, 3, 6, 7, 8, 9, 11, 14];
  9474. // Each transform function that names a shorter one, and how. A name absent here
  9475. // reduces to nothing, which is what lets the caller ask before parting arguments.
  9476. /** @type {Map<string, (args: string[]) => [string, string] | null>} */
  9477. const _TRANSFORM_REDUCERS = new Map([
  9478. [
  9479. "translate",
  9480. (args) => {
  9481. if (args.length !== 2) return null;
  9482. if (_isZeroOffset(args[1])) return ["translate", args[0]];
  9483. if (_isZeroOffset(args[0])) return ["translateY", args[1]];
  9484. return null;
  9485. }
  9486. ],
  9487. [
  9488. "translate3d",
  9489. (args) => {
  9490. if (args.length !== 3) return null;
  9491. if (_isZeroOffset(args[0]) && _isZeroOffset(args[1])) {
  9492. return ["translateZ", args[2]];
  9493. }
  9494. if (_isZeroLength(args[2])) return ["translate", `${args[0]},${args[1]}`];
  9495. return null;
  9496. }
  9497. ],
  9498. [
  9499. // `scale(x, x)` is `scale(x)` — the second factor defaults to the first.
  9500. "scale",
  9501. (args) => {
  9502. if (args.length !== 2) return null;
  9503. if (args[0] === args[1]) return ["scale", args[0]];
  9504. // A factor of 1 scales nothing along its axis, leaving the other axis's
  9505. // own function.
  9506. if (_isOne(args[1])) return ["scaleX", args[0]];
  9507. if (_isOne(args[0])) return ["scaleY", args[1]];
  9508. return null;
  9509. }
  9510. ],
  9511. [
  9512. "scale3d",
  9513. (args) => {
  9514. if (args.length !== 3) return null;
  9515. // A z factor of 1 scales nothing along it, leaving the 2D scale.
  9516. if (_isOne(args[2])) return ["scale", `${args[0]},${args[1]}`];
  9517. // ...and a 2D pair of 1 leaves the z scale alone.
  9518. if (_isOne(args[0]) && _isOne(args[1])) return ["scaleZ", args[2]];
  9519. return null;
  9520. }
  9521. ],
  9522. [
  9523. // CSS Transforms 2 §12: a 3D matrix whose third row and column are the
  9524. // identity's is the 2D matrix of the six values it leaves.
  9525. "matrix3d",
  9526. (args) => {
  9527. if (args.length !== 16) return null;
  9528. for (const i of _MATRIX3D_IDENTITY_ZEROS) {
  9529. if (!_isZeroComponent(args[i])) return null;
  9530. }
  9531. if (!_isOne(args[10]) || !_isOne(args[15])) return null;
  9532. return [
  9533. "matrix",
  9534. `${args[0]},${args[1]},${args[4]},${args[5]},${args[12]},${args[13]}`
  9535. ];
  9536. }
  9537. ],
  9538. [
  9539. // CSS Transforms 2 §13.1: `rotateZ(a)` names the rotation `rotate(a)` does.
  9540. "rotatez",
  9541. (args) => (args.length === 1 ? ["rotate", args[0]] : null)
  9542. ],
  9543. [
  9544. "rotate3d",
  9545. (args) => {
  9546. if (args.length !== 4) return null;
  9547. // The engine normalizes the axis, so a scaled component still names it
  9548. // but a negative one turns the rotation the other way.
  9549. const axis =
  9550. _isZeroComponent(args[1]) &&
  9551. _isZeroComponent(args[2]) &&
  9552. _isOne(args[0])
  9553. ? "rotateX"
  9554. : _isZeroComponent(args[0]) &&
  9555. _isZeroComponent(args[2]) &&
  9556. _isOne(args[1])
  9557. ? "rotateY"
  9558. : _isZeroComponent(args[0]) &&
  9559. _isZeroComponent(args[1]) &&
  9560. _isOne(args[2])
  9561. ? "rotate"
  9562. : null;
  9563. return axis === null ? null : [axis, args[3]];
  9564. }
  9565. ]
  9566. ]);
  9567. /**
  9568. * Reduce a transform function to the shorter one naming the same matrix: a
  9569. * translation whose other axes are zero is that axis's own function, and a
  9570. * uniform scale needs one factor. CSS Transforms 1 §7 defines each as the
  9571. * matrix it multiplies, so the two spellings compute alike.
  9572. * @param {string} fn the lowercased function name
  9573. * @param {string} inner the already-joined argument text
  9574. * @returns {[string, string] | null} the shorter call as its name and
  9575. * arguments, or null to keep the function
  9576. */
  9577. const _reduceTransformFunction = (fn, inner) => {
  9578. if (!_transforms.reduceFunctions) return null;
  9579. const reduce = _TRANSFORM_REDUCERS.get(fn);
  9580. // Asked before the arguments are parted: a function naming no shorter one is
  9581. // most of what a stylesheet calls, and it keeps its own spelling.
  9582. if (reduce === undefined) return null;
  9583. const args = inner.split(",");
  9584. for (let i = 0; i < args.length; i++) {
  9585. const one = args[i].trim();
  9586. if (one.length === 0) return null;
  9587. args[i] = one;
  9588. }
  9589. return reduce(args);
  9590. };
  9591. /**
  9592. * Drop the direction a linear gradient flows in anyway. CSS Images 3 §3.1: with
  9593. * no `<side-or-corner>` and no angle the gradient runs top to bottom, which is
  9594. * what `to bottom` and `180deg` each name.
  9595. * @param {string} fn the lowercased function name
  9596. * @param {string} inner the already-joined argument text
  9597. * @returns {string | null} the arguments without it, or null to keep them
  9598. */
  9599. const _dropDefaultGradientDirection = (fn, inner) => {
  9600. if (!LINEAR_GRADIENTS.has(fn) || !_transforms.reduceFunctions) return null;
  9601. const comma = inner.indexOf(",");
  9602. if (comma === -1) return null;
  9603. const first = inner.slice(0, comma).trim().toLowerCase().replace(/\s+/g, " ");
  9604. if (!DEFAULT_GRADIENT_DIRECTIONS.has(first)) return null;
  9605. return inner.slice(comma + 1).trim();
  9606. };
  9607. /**
  9608. * Split on the whitespace at the top of one value, a nested call's own spaces
  9609. * staying inside it.
  9610. * @param {string} text one comma-separated piece of a call's body
  9611. * @returns {string[]} its components
  9612. */
  9613. const _splitTopLevelSpaces = (text) => {
  9614. /** @type {string[]} */
  9615. const parts = [];
  9616. let depth = 0;
  9617. let start = 0;
  9618. for (let i = 0; i < text.length; i++) {
  9619. const code = text.charCodeAt(i);
  9620. if (code === CC_LEFT_PARENTHESIS) {
  9621. depth++;
  9622. } else if (code === CC_RIGHT_PARENTHESIS) {
  9623. depth--;
  9624. } else if (depth === 0 && _isWhiteSpace(code)) {
  9625. if (i > start) parts.push(text.slice(start, i));
  9626. start = i + 1;
  9627. }
  9628. }
  9629. if (text.length > start) parts.push(text.slice(start));
  9630. return parts;
  9631. };
  9632. /**
  9633. * Fold what a gradient's own grammar already says about its color stops: the
  9634. * last stop's position where the fix-up puts it there anyway (CSS Images 3
  9635. * §3.4.3), and — where the target reads the two-position syntax — two adjacent
  9636. * stops of one color as the one stop naming both (CSS Images 4 §3.4).
  9637. * @param {string} fn the lowercased function name
  9638. * @param {string} inner the already-joined argument text
  9639. * @param {boolean} double whether a two-position stop may be emitted
  9640. * @returns {string | null} the rewritten arguments, or null to keep them
  9641. */
  9642. const _foldGradientStops = (fn, inner, double) => {
  9643. if (!_transforms.reduceFunctions) return null;
  9644. const implied = GRADIENT_LAST_POSITIONS.get(fn);
  9645. if (implied === undefined) return null;
  9646. const args = _splitTopLevelArguments(inner);
  9647. // A one-argument gradient states no stop list to read.
  9648. if (args.length < 2) return null;
  9649. let changed = false;
  9650. // The last argument is always a color stop: the grammar puts one after every
  9651. // color hint. A stop already carrying two positions is not that one value.
  9652. const last = _splitTopLevelSpaces(args[args.length - 1]);
  9653. if (last.length === 2 && implied.has(last[1].toLowerCase())) {
  9654. args[args.length - 1] = last[0];
  9655. changed = true;
  9656. }
  9657. if (double) {
  9658. for (let i = args.length - 1; i > 0; i--) {
  9659. const one = _splitTopLevelSpaces(args[i]);
  9660. const before = _splitTopLevelSpaces(args[i - 1]);
  9661. // Two positions on one stop are the two stops they would be written as,
  9662. // so only a pair naming one color folds — and a color hint, which is a
  9663. // position alone, is no stop to fold with.
  9664. if (
  9665. one.length !== 2 ||
  9666. before.length !== 2 ||
  9667. !equalsLowerCase(before[0], one[0].toLowerCase())
  9668. ) {
  9669. continue;
  9670. }
  9671. args[i - 1] = `${before[0]} ${before[1]} ${one[1]}`;
  9672. args.splice(i, 1);
  9673. changed = true;
  9674. }
  9675. }
  9676. return changed ? args.join(",") : null;
  9677. };
  9678. /**
  9679. * Collapse a value the property's own grammar already implies: two equal
  9680. * `<repeat-style>` keywords are the one-value form.
  9681. * @param {string} property the declaration's lowercased property name
  9682. * @param {string[]} components the value's top-level components
  9683. * @returns {string[] | null} the fragments to join, or `null` to keep the value
  9684. */
  9685. const _collapseRepeatedPair = (property, components) => {
  9686. if (
  9687. components.length === 2 &&
  9688. REPEAT_STYLE_PROPERTIES.has(property) &&
  9689. equalsLowerCase(components[0], components[1].toLowerCase()) &&
  9690. // Both halves have to be that axis: the production also sits in shorthands
  9691. // where a repeated value is some other slot, and `background: red red` is
  9692. // a declaration the engine drops rather than one to make valid.
  9693. REPEAT_STYLE_KEYWORDS.has(toLowerCaseIfNeeded(components[0]))
  9694. ) {
  9695. return [components[0]];
  9696. }
  9697. return null;
  9698. };
  9699. /**
  9700. * How a component would be looked up in a slot's spellings: a call is its name
  9701. * with empty parentheses, anything else is itself.
  9702. * @param {string} component one lowercased top-level component
  9703. * @returns {string} the spelling to look up
  9704. */
  9705. const _componentSpelling = (component) => {
  9706. const open = component.indexOf("(");
  9707. return open === -1 ? component : `${component.slice(0, open)}()`;
  9708. };
  9709. // A `<time>` of zero, whichever unit it is spelled in.
  9710. const ZERO_TIME_RE = /^[+-]?(?:0+\.?0*|\.0+)m?s$/i;
  9711. /**
  9712. * Drop a duration of zero, which is the duration a transition runs with anyway.
  9713. * Only where the layer states one `<time>`: the first fills the duration slot
  9714. * and the second the delay, so dropping the duration out of a pair hands the
  9715. * delay's value to the duration. Called for `transition` alone, which is the
  9716. * one shorthand whose layers are read here.
  9717. * @param {string[]} components one layer's top-level components
  9718. * @returns {string[] | null} the fragments to join, or `null` to keep the value
  9719. */
  9720. const _dropZeroDuration = (components) => {
  9721. if (components.length < 2) return null;
  9722. let at = -1;
  9723. for (let i = 0; i < components.length; i++) {
  9724. if (!_TRANSITION_TIME_RE.test(components[i])) continue;
  9725. if (at !== -1) return null;
  9726. at = i;
  9727. }
  9728. if (at === -1 || !ZERO_TIME_RE.test(components[at])) return null;
  9729. const kept = [...components];
  9730. kept.splice(at, 1);
  9731. return kept;
  9732. };
  9733. /**
  9734. * Drop the shorthand slots holding what they already default to. A sibling out
  9735. * of the same slot's spellings — one of its keywords, or a call to one of its
  9736. * functions — means the value fills that slot twice, which is a declaration the
  9737. * engine drops; dropping one of them would revive it.
  9738. * @param {string} property the declaration's lowercased property name
  9739. * @param {string[]} components the value's top-level components
  9740. * @returns {string[] | null} the fragments to join, or `null` to keep the value
  9741. */
  9742. const _dropInitialKeywords = (property, components) => {
  9743. const table = SHORTHAND_INITIAL_KEYWORDS.get(property);
  9744. if (table === undefined || components.length < 2) return null;
  9745. // A comma parts two layers and a `/` reaches a slot through another's value;
  9746. // either way the slots are no longer this one flat list.
  9747. for (const component of components) {
  9748. if (component.includes(",") || component === "/") return null;
  9749. }
  9750. const lowered = components.map((one) =>
  9751. _componentSpelling(toLowerCaseIfNeeded(one))
  9752. );
  9753. const kept = [];
  9754. for (let i = 0; i < components.length; i++) {
  9755. const siblings = table.get(lowered[i]);
  9756. if (
  9757. siblings !== undefined &&
  9758. !lowered.some((other, j) => j !== i && siblings.has(other))
  9759. ) {
  9760. continue;
  9761. }
  9762. kept.push(components[i]);
  9763. }
  9764. if (kept.length === components.length) return null;
  9765. if (kept.length !== 0) return kept;
  9766. // Every slot held its own initial, so any one of them says all of them.
  9767. let shortest = components[0];
  9768. for (const component of components) {
  9769. if (component.length < shortest.length) shortest = component;
  9770. }
  9771. return [shortest];
  9772. };
  9773. /**
  9774. * Rewrite a `<position>` written as edge keywords into the percentages they
  9775. * resolve to. Keywords only: an offset beside one is the 3/4-value syntax,
  9776. * where the keyword names an edge to measure from rather than a place.
  9777. * @param {string} property the declaration's lowercased property name
  9778. * @param {string[]} components the value's top-level components
  9779. * @returns {string[] | null} the fragments to join, or `null` to keep the value
  9780. */
  9781. const _collapsePositionKeywords = (property, components) => {
  9782. if (
  9783. !POSITION_PROPERTIES.has(property) ||
  9784. components.length === 0 ||
  9785. components.length > 2
  9786. ) {
  9787. return null;
  9788. }
  9789. /** @type {string | undefined} */
  9790. let x;
  9791. /** @type {string | undefined} */
  9792. let y;
  9793. for (const component of components) {
  9794. const keyword = toLowerCaseIfNeeded(component);
  9795. const onX = POSITION_X_KEYWORDS.get(keyword);
  9796. const onY = POSITION_Y_KEYWORDS.get(keyword);
  9797. if (onX === undefined && onY === undefined) return null;
  9798. // `center` is on both axes and is what a free axis already resolves to.
  9799. if (onX !== undefined && onY !== undefined) continue;
  9800. if (onX === undefined) {
  9801. if (y !== undefined) return null;
  9802. y = onY;
  9803. } else {
  9804. if (x !== undefined) return null;
  9805. x = onX;
  9806. }
  9807. }
  9808. const across = x === undefined ? "50%" : x;
  9809. const down = y === undefined ? "50%" : y;
  9810. // A trailing `50%` is what the omitted second value means.
  9811. const shorter = down === "50%" ? [across] : [across, down];
  9812. return shorter.join(" ").length < components.join(" ").length
  9813. ? shorter
  9814. : null;
  9815. };
  9816. /**
  9817. * Drop a `<position>`'s second value where it names the centre an omitted one
  9818. * already means. A pair of keywords takes the percentage rewrite above; this is
  9819. * for the pairs an offset keeps out of it.
  9820. * @param {string} property the declaration's lowercased property name
  9821. * @param {string[]} components the value's top-level components
  9822. * @returns {string[] | null} the fragments to join, or `null` to keep the value
  9823. */
  9824. const _dropCenterPositionTail = (property, components) => {
  9825. if (!POSITION_PROPERTIES.has(property) || components.length !== 2) {
  9826. return null;
  9827. }
  9828. const down = toLowerCaseIfNeeded(components[1]);
  9829. if (down !== "center" && down !== "50%") return null;
  9830. // A `top` / `bottom` first value is no x-position, so that pair is the
  9831. // order-free keyword syntax and dropping half would leave an invalid value.
  9832. const across = toLowerCaseIfNeeded(components[0]);
  9833. return POSITION_X_KEYWORDS.has(across) || _NUMERIC_RE.test(across)
  9834. ? [components[0]]
  9835. : null;
  9836. };
  9837. // One `grid-template-areas` row, whose quotes bound it — the whitespace between
  9838. // its cell names parts them, and a run of it says no more than one space.
  9839. const _AREA_ROW_RE = /^(["'])([\s\S]*)\1$/;
  9840. /**
  9841. * Squeeze a `grid-template-areas` value: each row keeps the cell names it
  9842. * lists, and the whitespace between two rows carries nothing at all — the
  9843. * quotes already part them.
  9844. * @param {CssPath} path the accessor positioned on the declaration
  9845. * @param {Node} node the declaration whose value's children are read
  9846. * @param {PrintContext} writer the print context (children's printed text)
  9847. * @returns {string[] | null} the fragments to join, or `null` to keep the value
  9848. */
  9849. const _collapseGridTemplateAreas = (path, node, writer) => {
  9850. const components = _valueComponents(path, node, writer);
  9851. const rows = [];
  9852. for (const component of components) {
  9853. const row = _AREA_ROW_RE.exec(component);
  9854. if (row === null) return null;
  9855. rows.push(`${row[1]}${row[2].trim().replace(/\s+/g, " ")}${row[1]}`);
  9856. }
  9857. return rows.length === 0 ? null : rows;
  9858. };
  9859. /**
  9860. * The safe transforms that rewrite one fragment of a declaration value on its
  9861. * own, whatever stands beside it.
  9862. * @param {string} fragment the fragment's printed text
  9863. * @param {string} property the declaration's lowercased property name
  9864. * @param {boolean} minify whether printing minified
  9865. * @returns {string} the rewritten fragment
  9866. */
  9867. const _valueFragment = (fragment, property, minify) => {
  9868. let out = fragment;
  9869. if (minify && !_inSupportsPrelude && _transforms.reduceFunctions) {
  9870. out = _unwrapCalc(out, property);
  9871. }
  9872. if (minify && !ZERO_UNIT_KEEPING_PROPERTIES.has(property)) {
  9873. out = _dropZeroLengthUnit(out);
  9874. out = _dropZeroLengthUnitInCall(out);
  9875. }
  9876. return out;
  9877. };
  9878. // Which property a vendor spelling is a spelling of, so the value of
  9879. // `-webkit-transition` minifies the way `transition`'s does. Built from the
  9880. // table the prefixing pass already carries rather than by cutting a `-webkit-`
  9881. // off, since a name wearing a prefix is not always the same property as the
  9882. // one without it. Made on first use: a sheet writing no prefixed property never
  9883. // pays for it.
  9884. /** @type {Map<string, string> | null} */
  9885. let _standardSpellings = null;
  9886. /**
  9887. * The standard property a name spells, or the name itself.
  9888. * @param {string} property a lowercased property name
  9889. * @returns {string} the property whose value rules apply
  9890. */
  9891. const _standardSpelling = (property) => {
  9892. if (property.charCodeAt(0) !== CC_HYPHEN_MINUS) return property;
  9893. if (_standardSpellings === null) {
  9894. _standardSpellings = new Map();
  9895. for (const [standard, spellings] of PREFIXED_PROPERTIES) {
  9896. for (const [spelling] of spellings) {
  9897. _standardSpellings.set(spelling, standard);
  9898. }
  9899. }
  9900. }
  9901. const standard = _standardSpellings.get(property);
  9902. return standard === undefined ? property : standard;
  9903. };
  9904. /**
  9905. * Shorten a shorthand declaration's value to an equivalent spelling. A prefixed
  9906. * spelling is a different property, and neither table lists one.
  9907. * @param {CssPath} path the accessor positioned on the declaration
  9908. * @param {string} property the declaration's lowercased property name
  9909. * @param {Node} node the declaration whose value's children are read
  9910. * @param {PrintContext} writer the print context (children's printed text)
  9911. * @returns {string[] | null} the fragments to join, or `null` to keep the value as it is
  9912. */
  9913. const _collapseShorthand = (path, property, node, writer) => {
  9914. if (!_transforms.shortenValues) return null;
  9915. // A value holding a substitution is the token stream it was written as, so
  9916. // nothing in it collapses. Read off the declaration rather than the walk's
  9917. // flag: this runs as the declaration is printed, once its children are done.
  9918. if (_hasSubstitution(path.source())) return null;
  9919. if (BOX_SHORTHANDS.has(property)) {
  9920. return _collapseBoxShorthand(path, property, node, writer);
  9921. }
  9922. if (property === "font-weight") {
  9923. return _collapseFontWeight(path, node, writer);
  9924. }
  9925. if (property === "flex") {
  9926. return _collapseFlexShorthand(path, node, writer);
  9927. }
  9928. if (property === "grid-template-areas") {
  9929. return _collapseGridTemplateAreas(path, node, writer);
  9930. }
  9931. const written = _valueComponents(path, node, writer);
  9932. const shadow = _dropShadowZeroLengths(property, written);
  9933. if (shadow !== null) return shadow;
  9934. if (property === "transition") {
  9935. return _collapseTransitionLayers(property, written);
  9936. }
  9937. // A slot holding its own initial goes first, so what follows reads the
  9938. // components that are left rather than the ones the author wrote.
  9939. let dropped = _dropInitialKeywords(property, written);
  9940. let components = dropped === null ? written : dropped;
  9941. // A component spelling the property's own initial says nothing beside another:
  9942. // omitting the group it belongs to leaves exactly that keyword.
  9943. const omittable = OMITTABLE_INITIAL_KEYWORDS.get(property);
  9944. if (omittable !== undefined && components.length > 1) {
  9945. const [keyword, slot] = omittable;
  9946. // Only when the slot the keyword fills is named once. A value naming it
  9947. // twice is invalid, and dropping the initial would leave the valid value
  9948. // the author did not write.
  9949. const filled = components.filter((one) =>
  9950. slot.includes(toLowerCaseIfNeeded(one))
  9951. );
  9952. const kept =
  9953. filled.length === 1
  9954. ? components.filter((one) => !equalsLowerCase(one, keyword))
  9955. : components;
  9956. if (kept.length !== 0 && kept.length !== components.length) {
  9957. components = kept;
  9958. dropped = kept;
  9959. }
  9960. }
  9961. if (property === "display" && components.length === 2) {
  9962. const pair = `${toLowerCaseIfNeeded(components[0])} ${toLowerCaseIfNeeded(
  9963. components[1]
  9964. )}`;
  9965. // `<display-outside> || <display-inside>` is order-free, so both readings.
  9966. const short =
  9967. DISPLAY_SHORT_FORMS.get(pair) ||
  9968. DISPLAY_SHORT_FORMS.get(pair.split(" ").reverse().join(" "));
  9969. if (short !== undefined) return [short];
  9970. }
  9971. if (property === "font") {
  9972. return _numberFontShorthandWeight(components) || dropped;
  9973. }
  9974. // `initial` computes to the property's initial value, so where that value is
  9975. // a shorter keyword the two are the same declaration.
  9976. if (components.length === 1 && equalsLowerCase(components[0], "initial")) {
  9977. const keyword = INITIAL_VALUE_KEYWORDS.get(property);
  9978. if (keyword !== undefined) return [keyword];
  9979. }
  9980. // Grammar matching skips whitespace and a `)` fuses with nothing, so between
  9981. // two calls it separates the same tokens either way — whatever the property,
  9982. // which is what reaches the prefixed spellings no table names.
  9983. if (
  9984. components.length > 1 &&
  9985. components.every((one) => one.endsWith(")") && one.includes("("))
  9986. ) {
  9987. return components;
  9988. }
  9989. // A `font-stretch` keyword is the percentage it names, in fewer bytes.
  9990. if (property === "font-stretch" && components.length === 1) {
  9991. const percentage = FONT_STRETCH_PERCENTAGES.get(
  9992. toLowerCaseIfNeeded(components[0])
  9993. );
  9994. if (percentage !== undefined && percentage.length < components[0].length) {
  9995. return [percentage];
  9996. }
  9997. }
  9998. const position = _collapsePositionKeywords(property, components);
  9999. if (position !== null) return position;
  10000. const centered = _dropCenterPositionTail(property, components);
  10001. if (centered !== null) return centered;
  10002. return _collapseRepeatedPair(property, components) || dropped;
  10003. };
  10004. // The length units, shared by the zero-unit drop and the value classifier. No
  10005. // dataset states the list, so it is the spec's, spelled out.
  10006. // cspell:ignore rlh cqmin cqmax vmin vmax dvmin dvmax lvmin lvmax svmin svmax whib
  10007. const _LENGTH_UNITS =
  10008. "px|em|rem|ex|ch|cap|ic|lh|rlh|v[wh]|vmin|vmax|v[ib]|[sld]v[wh]|[sld]vmin|[sld]vmax|[sld]v[ib]|cq[whib]|cqmin|cqmax|cm|mm|in|pt|pc|q";
  10009. const _NUMERIC_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?(%|[a-z]*)$/i;
  10010. // Only the four lengths `_minifyHash` accepts: a 5- or 7-digit hash is no
  10011. // color, and merging one would take the whole shorthand down with it.
  10012. const _HEX_COLOR_RE = /^#(?:[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/i;
  10013. const _IDENT_RE = /^-?[a-z_][-\w]*$/i;
  10014. const _URL_RE = /^(?:url|src)\(/i;
  10015. const _LENGTH_UNIT_RE = new RegExp(`^(?:${_LENGTH_UNITS})$`, "i");
  10016. /**
  10017. * The value classes a printed component could be read as, for deciding which
  10018. * slot of an order-free shorthand would claim it. Only the classes those slots
  10019. * name, which the generator asserts is this list; anything else — a function
  10020. * other than `url()`, an escape, a substitution — is `null`, which declines the
  10021. * merge rather than guessing a slot.
  10022. * @param {string} value one printed component
  10023. * @param {string} lower the same, lowercased
  10024. * @returns {Set<string> | null} the classes, or `null` when unclassifiable
  10025. */
  10026. const _valueClasses = (value, lower) => {
  10027. const numeric = _NUMERIC_RE.exec(value);
  10028. if (numeric !== null) {
  10029. const unit = toLowerCaseIfNeeded(numeric[1]);
  10030. if (unit === "%") return new Set(["percentage"]);
  10031. // CSS Values 4 §5: a zero length may drop its unit, so a bare `0` is a
  10032. // `<length>`; any other bare number is a `<number>`, which no slot takes.
  10033. if (unit === "") return Number(value) === 0 ? new Set(["length"]) : null;
  10034. return _LENGTH_UNIT_RE.test(unit) ? new Set(["length"]) : null;
  10035. }
  10036. if (_HEX_COLOR_RE.test(value)) return new Set(["color"]);
  10037. if (value.charCodeAt(0) === 0x22 || value.charCodeAt(0) === 0x27) {
  10038. return new Set(["string"]);
  10039. }
  10040. if (_URL_RE.test(value)) return new Set(["url", "image"]);
  10041. if (!_IDENT_RE.test(value)) return null;
  10042. if (CSS_WIDE_KEYWORDS.has(lower)) return null;
  10043. const classes = new Set(["custom-ident", "ident"]);
  10044. if (COLOR_KEYWORDS.has(lower)) classes.add("color");
  10045. return classes;
  10046. };
  10047. /**
  10048. * The shorthand value a family merge may emit: every longhand's value, in the
  10049. * grammar's order. Each has to parse back into the longhand it was authored on,
  10050. * so a value a second slot would also take declines the merge — `outline`'s
  10051. * `auto` is both an `outline-style` and an `outline-color`.
  10052. * @param {string[]} longhands the family's longhands, in grammar order
  10053. * @param {string[]} values one component per longhand, in the same order
  10054. * @returns {string | null} the shorthand value, or `null` when it is ambiguous
  10055. */
  10056. const _familyValue = (longhands, values) => {
  10057. for (let i = 0; i < values.length; i++) {
  10058. const lower = toLowerCaseIfNeeded(values[i]);
  10059. const classes = _valueClasses(values[i], lower);
  10060. if (classes === null) return null;
  10061. let owner = -1;
  10062. for (let j = 0; j < longhands.length; j++) {
  10063. const keywords = /** @type {string[]} */ (
  10064. FAMILY_SLOT_KEYWORDS.get(longhands[j])
  10065. );
  10066. let takes = keywords.includes(lower);
  10067. if (!takes) {
  10068. const slot = /** @type {string[]} */ (
  10069. FAMILY_SLOT_CLASSES.get(longhands[j])
  10070. );
  10071. for (const name of slot) {
  10072. if (classes.has(name)) {
  10073. takes = true;
  10074. break;
  10075. }
  10076. }
  10077. }
  10078. if (!takes) continue;
  10079. if (owner !== -1) return null;
  10080. owner = j;
  10081. }
  10082. if (owner !== i) return null;
  10083. }
  10084. return values.join(" ");
  10085. };
  10086. /**
  10087. * Merge a box family's four longhands into their shorthand, which sets exactly
  10088. * those four (see `BOX_LONGHANDS`) — so the rule computes the same either way.
  10089. *
  10090. * Every guard here is about *not* reviving something the browser drops, or
  10091. * moving a declaration past one that could overwrite it: the four must be
  10092. * adjacent, each declared once, each a single component, and all agree on
  10093. * `!important`. `_collapseBox` refuses a `var()` or a CSS-wide keyword, both of
  10094. * which mean something else in a shorthand.
  10095. * @param {CssPath} path the accessor positioned on the rule
  10096. * @param {Declaration[]} decls the rule's declarations, in source order
  10097. * @param {PrintContext} writer the print context (children's printed text)
  10098. * @param {Map<string, string[]>} table the shorthand-to-longhands map to merge by
  10099. * @param {number} mode how the table's values are written: `MERGE_BOX` for the
  10100. * `{1,4}` notation, `MERGE_FAMILY` for order-free slots, `MERGE_SLASH` for slots
  10101. * a `/` stands between
  10102. * @param {Map<string, number>} at which declaration wrote each property, by name
  10103. * @param {Set<string>} repeated the properties written more than once
  10104. * @param {Uint32Array | null} rulesBefore how many child rules stand before each
  10105. * declaration, or null where the block holds none
  10106. * @returns {Map<Node, string> | null} replacement text per declaration, or null
  10107. */
  10108. const _mergeBoxLonghands = (
  10109. path,
  10110. decls,
  10111. writer,
  10112. table,
  10113. mode,
  10114. at,
  10115. repeated,
  10116. rulesBefore
  10117. ) => {
  10118. if (!_transforms.mergeLonghands) return null;
  10119. /** @type {Map<Node, string> | null} */
  10120. let out = null;
  10121. // What an earlier shorthand of this table already wrote or blanked. Two of
  10122. // them can share a longhand (`corner-top-shape` and `corner-left-shape` both
  10123. // set `corner-top-left-shape`), and the second landing on the first's blanked
  10124. // tail would leave that tail uncovered — dropping its declaration.
  10125. /** @type {Set<Node>} */
  10126. const claimed = new Set();
  10127. for (const [shorthand, longhands] of table) {
  10128. // A plain loop: this runs for every table entry of every rule, where one
  10129. // closure per entry is the allocation that dominates.
  10130. let missing = false;
  10131. for (let i = 0; i < longhands.length && !missing; i++) {
  10132. missing = !at.has(longhands[i]) || repeated.has(longhands[i]);
  10133. }
  10134. if (missing) continue;
  10135. // The shorthands newer than the longhands they merge.
  10136. if (shorthand === "inset" && !_insetShorthandAllowed) continue;
  10137. if (!_placeShorthandAllowed && PLACE_SHORTHANDS.has(shorthand)) continue;
  10138. const indexes = longhands.map((l) => /** @type {number} */ (at.get(l)));
  10139. if (indexes.some((i) => claimed.has(decls[i]))) continue;
  10140. const ordered = [...indexes].sort((a, b) => a - b);
  10141. // A child rule between the first and the last is one the merge steps over.
  10142. if (
  10143. rulesBefore !== null &&
  10144. rulesBefore[ordered[ordered.length - 1]] !== rulesBefore[ordered[0]]
  10145. ) {
  10146. continue;
  10147. }
  10148. // The merge moves whatever stands between the four below the shorthand, so
  10149. // nothing between them may write one of those properties. Blocked by name
  10150. // prefix rather than by a writers table: `mdn-data` maps `margin-top` back
  10151. // to `margin` alone, missing `border`, `border-top` and every logical
  10152. // property, so only the family's own prefix is safe to trust.
  10153. // A pair family states no prefix of its own: its longhands are the two
  10154. // names, so anything sharing either one's first segment is what could be
  10155. // stepped over (`align-items` is not under a `place` prefix).
  10156. const head = (/** @type {string} */ name) =>
  10157. /** @type {RegExpExecArray} */ (/^[^-]+/.exec(name))[0];
  10158. const prefix = BOX_FAMILY_PREFIX.get(shorthand) || head(shorthand);
  10159. const families = new Set([prefix, ...longhands.map(head)]);
  10160. let blocked = false;
  10161. for (
  10162. let i = ordered[0] + 1;
  10163. i < ordered[ordered.length - 1] && !blocked;
  10164. i++
  10165. ) {
  10166. if (indexes.includes(i)) continue;
  10167. // A vendor prefix hides the family a legacy alias writes, so it comes
  10168. // off first: `-webkit-margin-start` sets `margin-left` in Chromium.
  10169. const between = toLowerCaseIfNeeded(path.name(decls[i])).replace(
  10170. /^-[a-z]+-/,
  10171. ""
  10172. );
  10173. blocked = between === "all" || between === shorthand;
  10174. for (const family of families) {
  10175. if (between === family || between.startsWith(`${family}-`)) {
  10176. blocked = true;
  10177. }
  10178. }
  10179. }
  10180. if (blocked) continue;
  10181. const important = path.important(decls[indexes[0]]);
  10182. if (indexes.some((i) => path.important(decls[i]) !== important)) continue;
  10183. const values = [];
  10184. for (const i of indexes) {
  10185. const components = _valueComponents(path, decls[i], writer);
  10186. if (components.length !== 1) break;
  10187. values.push(components[0]);
  10188. }
  10189. if (values.length !== longhands.length) continue;
  10190. // A bare number is no length, so one written beside a length is a value the
  10191. // property never read — the engine dropped that declaration and kept the
  10192. // others. Merging them writes it into a shorthand the engine drops whole,
  10193. // which loses the slots that were fine.
  10194. if (_mixesBareNumberWithLength(values)) continue;
  10195. /** @type {string | null} */
  10196. let value;
  10197. if (mode === MERGE_FAMILY) {
  10198. value = _familyValue(longhands, values);
  10199. if (value === null) continue;
  10200. } else if (mode === MERGE_SLASH) {
  10201. // The same two refusals a box makes: a `var()` may expand across the `/`
  10202. // into another slot, and a CSS-wide keyword beside another value is a
  10203. // shorthand the engine drops whole.
  10204. let refused = false;
  10205. for (const slot of values) {
  10206. if (
  10207. _hasSubstitution(slot) ||
  10208. CSS_WIDE_KEYWORDS.has(toLowerCaseIfNeeded(slot))
  10209. ) {
  10210. refused = true;
  10211. break;
  10212. }
  10213. }
  10214. if (refused) continue;
  10215. // Every slot written: an omitted one means "the first slot where that is
  10216. // a `<custom-ident>`, else `auto`", which is not "the same value".
  10217. value = values.join("/");
  10218. } else {
  10219. // A pair collapses by the same rule as a box, and needs the same refusals:
  10220. // a `var()` may expand to both values, and a CSS-wide keyword alongside
  10221. // another value is a shorthand the engine drops whole.
  10222. const box = _collapseBox(values);
  10223. if (box === null) continue;
  10224. // The two-value spelling is the newer one here, so it is written only
  10225. // where the target reads it; the collapse to one value is as old as the
  10226. // longhands and always is.
  10227. if (
  10228. box.length !== 1 &&
  10229. ONE_VALUE_PAIR_SHORTHANDS.has(shorthand) &&
  10230. !_overflowTwoValuesAllowed
  10231. ) {
  10232. continue;
  10233. }
  10234. // A keyword only some of the longhands take makes the shorthand invalid,
  10235. // where the declaration writing it stood on its own: `justify-items:left`
  10236. // is read and `place-items:left` is dropped whole.
  10237. const unshared = UNSHARED_LONGHAND_KEYWORDS.get(shorthand);
  10238. if (unshared !== undefined) {
  10239. // Every slot is one component here, so each is a keyword or nothing.
  10240. let refused = false;
  10241. for (const slot of box) {
  10242. if (unshared.has(toLowerCaseIfNeeded(slot))) {
  10243. refused = true;
  10244. break;
  10245. }
  10246. }
  10247. if (refused) continue;
  10248. }
  10249. value = box.join(" ");
  10250. }
  10251. if (out === null) out = new Map();
  10252. out.set(
  10253. decls[ordered[0]],
  10254. `${shorthand}:${value}${important ? "!important" : ""};`
  10255. );
  10256. for (let i = 1; i < ordered.length; i++) out.set(decls[ordered[i]], "");
  10257. for (const i of ordered) claimed.add(decls[i]);
  10258. }
  10259. return out;
  10260. };
  10261. // How a table's slots are written into the shorthand.
  10262. const MERGE_BOX = 0;
  10263. const MERGE_FAMILY = 1;
  10264. const MERGE_SLASH = 2;
  10265. /** @type {[Map<string, string[]>, number][]} */
  10266. const MERGE_TABLES = [
  10267. [BOX_LONGHANDS, MERGE_BOX],
  10268. [PAIR_LONGHANDS, MERGE_BOX],
  10269. [FAMILY_LONGHANDS, MERGE_FAMILY],
  10270. [SLASH_LONGHANDS, MERGE_SLASH]
  10271. ];
  10272. /**
  10273. * A printed rule the merge can take apart again. `prelude` is how much of `text`
  10274. * comes before the `{`, so a parent parts it without re-scanning; `children` is
  10275. * what a mergeable at-rule's block is made of, so two blocks join at the seam
  10276. * their texts would otherwise hide.
  10277. * @typedef {object} RuleEntry
  10278. * @property {string} text the rule as printed
  10279. * @property {number} prelude the prelude's length, `-1` for a rule that cannot join
  10280. * @property {boolean} atRule whether it is an at-rule
  10281. * @property {boolean} plain whether its block holds declarations and no rule,
  10282. * so another's declarations may follow them without crossing one
  10283. * @property {number} listable whether its prelude may also join another's
  10284. * selector list, which a plain block under a plain selector may: `LIST_UNKNOWN`
  10285. * until a join asks, then `LIST_YES` / `LIST_NO`
  10286. * @property {number} listKind which shape answers that — a selector, a keyframe
  10287. * selector or a nested selector
  10288. * @property {RuleEntry[] | null} children a mergeable at-rule's block, else null
  10289. * @property {string} head the block's text but for its last child, so a run of
  10290. * joins appends what it adds rather than re-reading what it has
  10291. */
  10292. // Each printed rule the merge can join -> its entry. Cleared per top-level node.
  10293. /** @type {Map<Node, RuleEntry>} */
  10294. const _ruleEntry = new Map();
  10295. // Whether a rule's prelude may join another's selector list. Answered only when
  10296. // a join asks — two adjacent rules printing the same block, which is rare — so
  10297. // the shape test does not run per rule.
  10298. const LIST_UNKNOWN = 0;
  10299. const LIST_YES = 1;
  10300. const LIST_NO = 2;
  10301. // Which shape answers it, kept because the parent it is read from is gone by the
  10302. // time a join asks.
  10303. const LIST_KIND_SELECTOR = 0;
  10304. const LIST_KIND_KEYFRAME = 1;
  10305. const LIST_KIND_NESTED = 2;
  10306. // Anything else standing in a block: it parts a run rather than joining one.
  10307. /** @type {(text: string) => RuleEntry} */
  10308. const _opaqueEntry = (text) => ({
  10309. text,
  10310. prelude: -1,
  10311. atRule: false,
  10312. plain: false,
  10313. listable: LIST_NO,
  10314. listKind: LIST_KIND_SELECTOR,
  10315. children: null,
  10316. head: ""
  10317. });
  10318. /**
  10319. * Whether this rule's prelude may join another's selector list, running the
  10320. * shape test the first time a join asks and remembering the answer.
  10321. * @param {RuleEntry} entry a printed rule
  10322. * @returns {boolean} true when its selectors may join a list
  10323. */
  10324. const _entryListable = (entry) => {
  10325. if (entry.listable === LIST_UNKNOWN) {
  10326. entry.listable = _isJoinablePrelude(
  10327. entry.text.slice(0, entry.prelude),
  10328. entry.listKind === LIST_KIND_KEYFRAME,
  10329. entry.listKind === LIST_KIND_NESTED
  10330. )
  10331. ? LIST_YES
  10332. : LIST_NO;
  10333. }
  10334. return entry.listable === LIST_YES;
  10335. };
  10336. /**
  10337. * A node's entry, or an opaque one where the text it printed is not the text the
  10338. * block ended up carrying.
  10339. * @param {Node} node a block item
  10340. * @param {string} text the text the block carries for it
  10341. * @returns {RuleEntry} its entry
  10342. */
  10343. const _ruleEntryOf = (node, text) => {
  10344. const entry = _ruleEntry.get(node);
  10345. return entry !== undefined && entry.text === text
  10346. ? entry
  10347. : _opaqueEntry(text);
  10348. };
  10349. // A selector every engine parses: compounds of a type, universal, class, id or
  10350. // attribute selector joined by a combinator, and nothing else. One selector an
  10351. // engine cannot parse invalidates the whole list it is joined into, so joining a
  10352. // selector the engine keeps to one it drops loses the first — a pseudo it does
  10353. // not know (`:local(.foo)`, `::-moz-placeholder`), or a shape the parser passed
  10354. // through without validating (`. class`, from `./**c**/ /**c**/class`).
  10355. // TODO widen this to the pseudos `mdn-data`'s `selectors.json` names, derived in
  10356. // the generator like every other table, once it also says which engine reads
  10357. // which prefix.
  10358. const _IDENT = String.raw`(?:[-\w\u00A0-\uFFFF]|\\[\s\S])+`;
  10359. const _STRING = String.raw`"(?:[^"\\]|\\[\s\S])*"|'(?:[^'\\]|\\[\s\S])*'`;
  10360. // The matchers Selectors 4 §6 defines, and only `i` after one: an engine that
  10361. // cannot read the `s` modifier drops the selector, and with it the list.
  10362. const _ATTRIBUTE = String.raw`\[(?:${_IDENT}\|)?${_IDENT}(?:[~|^$*]?=(?:${_IDENT}|${_STRING})(?: [iI])?)?\]`;
  10363. // A pseudo-class or pseudo-element with no selector inside it: argument-less, or
  10364. // the `An+B` of `:nth-*()`, whose grammar is closed. One holding a selector list
  10365. // (`:not()`, `:is()`, `:has()`) would have to have its argument checked too.
  10366. const _PSEUDO = String.raw`::?[-\w]+(?:\(\s*(?:[-+\dn ]+|[oO][dD][dD]|[eE][vV][eE][nN])\s*\))?`;
  10367. const _COMPOUND = `(?:\\*|${_IDENT}|[.#]${_IDENT}|${_ATTRIBUTE})(?:[.#]${_IDENT}|${_ATTRIBUTE}|${_PSEUDO})*`;
  10368. const _JOINABLE_SELECTOR_RE = new RegExp(
  10369. `^${_COMPOUND}(?:(?:\\s*[>+~]\\s*|\\s+)${_COMPOUND})*$`
  10370. );
  10371. // The same, for a rule nested in another: `&` joins there because an engine that
  10372. // cannot read it cannot read the block holding it either, so both selectors are
  10373. // dropped together rather than one taking the other down.
  10374. const _NESTED_COMPOUND = `(?:&(?:${_COMPOUND})?|${_COMPOUND})`;
  10375. const _JOINABLE_NESTED_RE = new RegExp(
  10376. `^${_NESTED_COMPOUND}(?:(?:\\s*[>+~]\\s*|\\s+)${_NESTED_COMPOUND})*$`
  10377. );
  10378. // A keyframe selector is a percentage or `to` — `from` is already printed `0%`.
  10379. // Its grammar is closed, so every one of them joins.
  10380. const _JOINABLE_KEYFRAME_RE = /^(?:\d+(?:\.\d+)?%|to)$/i;
  10381. // The full-progress keyframe selector, in every spelling `to` names.
  10382. const _KEYFRAME_FULL_RE = /^100(?:\.0+)?%$/;
  10383. /**
  10384. * Whether a printed prelude is a list of selectors every engine parses, so
  10385. * another may be joined onto it without risking the whole list.
  10386. * @param {string} prelude the printed prelude
  10387. * @param {boolean} keyframe whether the rule sits in a `@keyframes`
  10388. * @param {boolean} nested whether the rule sits in another qualified rule
  10389. * @returns {boolean} true when it may be joined
  10390. */
  10391. const _isJoinablePrelude = (prelude, keyframe, nested) => {
  10392. const shape = keyframe
  10393. ? _JOINABLE_KEYFRAME_RE
  10394. : nested
  10395. ? _JOINABLE_NESTED_RE
  10396. : _JOINABLE_SELECTOR_RE;
  10397. // One selector is the common case and needs no list built to walk it.
  10398. if (prelude.includes(",")) {
  10399. for (const one of _splitSelectorList(prelude)) {
  10400. if (!shape.test(one)) return false;
  10401. }
  10402. } else if (!shape.test(prelude)) {
  10403. return false;
  10404. }
  10405. return keyframe || _pseudosReadable(prelude);
  10406. };
  10407. // Whether the selection reads each pseudo spelling met, which is the same answer
  10408. // for every prelude carrying it and for every stylesheet built for it — so it is
  10409. // kept until the selection itself changes rather than cleared per print.
  10410. /** @type {Map<string, boolean>} */
  10411. const _selectorReadMemo = new Map();
  10412. /** @type {(number[] | undefined)[] | null} */
  10413. let _selectorReadFor = null;
  10414. /**
  10415. * Whether every target browser reads every pseudo in a prelude. One selector an
  10416. * engine cannot parse invalidates the whole list it is joined into, so a pseudo
  10417. * no target is known to read keeps the selector out of one. An escape and a
  10418. * quoted attribute value are stepped over: the `:` of `.sm\:flex` and of
  10419. * `[href="a:b"]` starts no pseudo.
  10420. * @param {string} prelude a printed prelude the shape accepted
  10421. * @returns {boolean} true when the target reads all of them
  10422. */
  10423. const _pseudosReadable = (prelude) => {
  10424. if (!prelude.includes(":")) return true;
  10425. if (_selectorReadFor !== _prefixBrowsers) {
  10426. _selectorReadMemo.clear();
  10427. _selectorReadFor = _prefixBrowsers;
  10428. }
  10429. for (let i = 0; i < prelude.length; i++) {
  10430. const code = prelude.charCodeAt(i);
  10431. if (code === CC_REVERSE_SOLIDUS) {
  10432. i++;
  10433. continue;
  10434. }
  10435. if (code === CC_QUOTATION_MARK || code === CC_APOSTROPHE) {
  10436. for (i++; i < prelude.length; i++) {
  10437. const inner = prelude.charCodeAt(i);
  10438. if (inner === CC_REVERSE_SOLIDUS) i++;
  10439. else if (inner === code) break;
  10440. }
  10441. continue;
  10442. }
  10443. if (code !== CC_COLON) continue;
  10444. let end = i + 1;
  10445. if (prelude.charCodeAt(end) === CC_COLON) end++;
  10446. const from = end;
  10447. while (end < prelude.length && _isIdentCodePoint(prelude.charCodeAt(end))) {
  10448. end++;
  10449. }
  10450. if (end === from) return false;
  10451. const spelling = toLowerCaseIfNeeded(prelude.slice(i, end));
  10452. let reads = _selectorReadMemo.get(spelling);
  10453. if (reads === undefined) {
  10454. // A pseudo-element reads with one colon as well as two, and the table
  10455. // carries whichever spelling the standard names.
  10456. const since =
  10457. SELECTOR_SUPPORTED_FROM.get(spelling) ||
  10458. (spelling.charCodeAt(1) === CC_COLON
  10459. ? undefined
  10460. : SELECTOR_SUPPORTED_FROM.get(`:${spelling}`));
  10461. // A pseudo the table does not name is one no engine is known to parse —
  10462. // a vendor spelling, a CSS-modules one, or simply newer than the data —
  10463. // and no selection makes it safe, so this precedes the target check.
  10464. reads = since !== undefined && _readsAll(since);
  10465. _selectorReadMemo.set(spelling, reads);
  10466. }
  10467. if (!reads) return false;
  10468. i = end - 1;
  10469. }
  10470. return true;
  10471. };
  10472. /**
  10473. * The properties a printed block declares at its top level. A nested rule is not
  10474. * one, and neither is anything inside a string, a call or a block.
  10475. * @param {string} body the block's text, its braces excluded
  10476. * @returns {Set<string>} the property names, lowercased
  10477. */
  10478. const _blockProperties = (body) => {
  10479. const out = new Set();
  10480. let depth = 0;
  10481. let quote = 0;
  10482. let start = 0;
  10483. let colon = -1;
  10484. let block = false;
  10485. for (let i = 0; i < body.length; i++) {
  10486. const cc = body.charCodeAt(i);
  10487. if (quote !== 0) {
  10488. if (cc === CC_REVERSE_SOLIDUS) {
  10489. i++;
  10490. } else if (cc === quote) {
  10491. quote = 0;
  10492. }
  10493. continue;
  10494. }
  10495. if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
  10496. quote = cc;
  10497. } else if (
  10498. cc === CC_LEFT_PARENTHESIS ||
  10499. cc === CC_LEFT_SQUARE ||
  10500. cc === CC_LEFT_CURLY
  10501. ) {
  10502. if (depth === 0 && cc === CC_LEFT_CURLY) block = true;
  10503. depth++;
  10504. } else if (
  10505. cc === CC_RIGHT_PARENTHESIS ||
  10506. cc === CC_RIGHT_SQUARE ||
  10507. cc === CC_RIGHT_CURLY
  10508. ) {
  10509. depth--;
  10510. } else if (depth === 0) {
  10511. if (cc === CC_COLON && colon === -1) {
  10512. colon = i;
  10513. } else if (cc === CC_SEMICOLON) {
  10514. if (colon !== -1 && !block) {
  10515. out.add(toLowerCaseIfNeeded(body.slice(start, colon)));
  10516. }
  10517. start = i + 1;
  10518. colon = -1;
  10519. block = false;
  10520. }
  10521. }
  10522. }
  10523. if (colon !== -1 && !block) {
  10524. out.add(toLowerCaseIfNeeded(body.slice(start, colon)));
  10525. }
  10526. return out;
  10527. };
  10528. /**
  10529. * Whether two ranges of two strings read the same, without cutting either out.
  10530. * @param {string} a the first string
  10531. * @param {number} aStart where its range starts
  10532. * @param {number} aEnd where its range ends
  10533. * @param {string} b the second string
  10534. * @param {number} bStart where its range starts
  10535. * @param {number} bEnd where its range ends
  10536. * @returns {boolean} true when the two ranges are equal
  10537. */
  10538. const _rangeEquals = (a, aStart, aEnd, b, bStart, bEnd) => {
  10539. const length = aEnd - aStart;
  10540. if (length !== bEnd - bStart) return false;
  10541. for (let i = 0; i < length; i++) {
  10542. if (a.charCodeAt(aStart + i) !== b.charCodeAt(bStart + i)) return false;
  10543. }
  10544. return true;
  10545. };
  10546. // A layer with no name: the one at-rule prelude that is not the same rule twice.
  10547. const ANONYMOUS_LAYER_RE = /^@layer\s*$/i;
  10548. /**
  10549. * Join two adjacent rules into one. A qualified rule keeps its block and gathers
  10550. * the other's selectors; an at-rule keeps its prelude and gathers the other's
  10551. * block — the block of a condition the sheet already opened once, whose own
  10552. * rules then meet at the seam and are offered the same join.
  10553. * @param {RuleEntry} before the earlier rule
  10554. * @param {RuleEntry} entry the later rule
  10555. * @param {boolean} owned whether `before` is this run's own accumulator, whose
  10556. * children may be extended rather than copied
  10557. * @returns {RuleEntry | null} the one rule, or `null` when they do not join
  10558. */
  10559. const _joinRuleEntries = (before, entry, owned) => {
  10560. if (!_transforms.mergeRules) return null;
  10561. if (before.prelude === -1 || entry.prelude === -1) return null;
  10562. if (before.atRule !== entry.atRule) return null;
  10563. // Compared where they stand: two rules meet far more often than they join, and
  10564. // cutting each one's prelude and block out to answer that is two pieces of
  10565. // string per meeting for an answer that is usually no.
  10566. const preludesEqual = _rangeEquals(
  10567. before.text,
  10568. 0,
  10569. before.prelude,
  10570. entry.text,
  10571. 0,
  10572. entry.prelude
  10573. );
  10574. if (before.atRule) {
  10575. if (!preludesEqual) return null;
  10576. const beforePrelude = before.text.slice(0, before.prelude);
  10577. // CSS Cascade 5 §6.4.1: every `@layer {` opens a layer of its own, and a
  10578. // later layer beats an earlier one whatever the selectors say — so joining
  10579. // the two hands the block back to specificity.
  10580. if (ANONYMOUS_LAYER_RE.test(beforePrelude)) return null;
  10581. const beforeChildren = before.children;
  10582. const children = entry.children;
  10583. if (beforeChildren === null || children === null) return null;
  10584. // An empty block can be the whole point of the rule — `@layer a{}` declares
  10585. // where the layer sits in the cascade — so it is never folded away.
  10586. if (beforeChildren.length === 0 || children.length === 0) return null;
  10587. // A run of them extends one array rather than copying a growing one.
  10588. const joined = owned ? beforeChildren : [...beforeChildren];
  10589. // Both sides are joined already, so the pair meeting at the seam is the one
  10590. // new adjacency — and joining it cannot make another, the rule it leaves
  10591. // having the prelude or the block the one before it already declined.
  10592. const at = joined.length - 1;
  10593. const seam = _joinRuleEntries(joined[at], children[0], false);
  10594. let from = 0;
  10595. if (seam !== null) {
  10596. joined[at] = seam;
  10597. from = 1;
  10598. }
  10599. let head = before.head;
  10600. // Whatever the join leaves before the new last child joins the head, which
  10601. // is why a run costs what it adds rather than what it has.
  10602. if (from < children.length) {
  10603. head += joined[at].text;
  10604. for (let i = from; i < children.length - 1; i++) head += children[i].text;
  10605. }
  10606. for (let i = from; i < children.length; i++) joined.push(children[i]);
  10607. // Each block dropped the `;` its own `}` made redundant; the one that is no
  10608. // longer last brings its own back, so the joined body drops it again. Only
  10609. // the last child can end in one.
  10610. let lastText = joined[joined.length - 1].text;
  10611. while (
  10612. lastText.length !== 0 &&
  10613. lastText.charCodeAt(lastText.length - 1) === CC_SEMICOLON
  10614. ) {
  10615. lastText = lastText.slice(0, -1);
  10616. }
  10617. return {
  10618. text: `${beforePrelude}{${head}${lastText}}`,
  10619. prelude: before.prelude,
  10620. atRule: true,
  10621. plain: false,
  10622. listable: LIST_NO,
  10623. listKind: LIST_KIND_SELECTOR,
  10624. children: joined,
  10625. head
  10626. };
  10627. }
  10628. const blocksEqual = _rangeEquals(
  10629. before.text,
  10630. before.prelude,
  10631. before.text.length,
  10632. entry.text,
  10633. entry.prelude,
  10634. entry.text.length
  10635. );
  10636. // The same selector twice is one rule: nothing stands between them, so its
  10637. // declarations are read in the order they were written either way.
  10638. if (preludesEqual) {
  10639. // Two identical rules are one: only the last of a set of identical
  10640. // declarations can be read, so the earlier block says nothing.
  10641. if (blocksEqual) return before;
  10642. // Declarations after a nested rule are the implicit `& {…}` the engine
  10643. // builds for them, which is a rule the sheet did not have.
  10644. if (!before.plain) return null;
  10645. const beforePrelude = before.text.slice(0, before.prelude);
  10646. const body = before.text.slice(before.prelude + 1, -1);
  10647. const rest = entry.text.slice(entry.prelude + 1, -1);
  10648. // One block holds a property once, so a property both of them declare would
  10649. // lose the earlier declaration the two rules keep — and a shorthand holds
  10650. // every longhand its name prefixes, `all` holding the lot.
  10651. const declared = _blockProperties(body);
  10652. for (const property of _blockProperties(rest)) {
  10653. for (const one of declared) {
  10654. if (
  10655. one === property ||
  10656. one === "all" ||
  10657. property === "all" ||
  10658. one.startsWith(`${property}-`) ||
  10659. property.startsWith(`${one}-`)
  10660. ) {
  10661. return null;
  10662. }
  10663. }
  10664. }
  10665. // Each block already dropped the `;` its own `}` made redundant, so the one
  10666. // that is no longer last needs it back.
  10667. const joined =
  10668. body.length === 0 || rest.length === 0 ? body + rest : `${body};${rest}`;
  10669. return {
  10670. text: `${beforePrelude}{${joined}}`,
  10671. prelude: before.prelude,
  10672. atRule: false,
  10673. plain: entry.plain,
  10674. // The same prelude answers the same way, so the join carries the state it
  10675. // is in rather than resolving it — unless the block it took on is not one
  10676. // a list may lend its selectors to.
  10677. listable: entry.plain ? before.listable : LIST_NO,
  10678. listKind: before.listKind,
  10679. children: null,
  10680. head: ""
  10681. };
  10682. }
  10683. if (!blocksEqual || !_entryListable(before) || !_entryListable(entry)) {
  10684. return null;
  10685. }
  10686. // Concatenated, never canonicalized: a run of joins would then re-read the
  10687. // whole list once per rule, and no `configCases` sheet loses a byte to it.
  10688. const list = `${before.text.slice(0, before.prelude)},${entry.text.slice(
  10689. 0,
  10690. entry.prelude
  10691. )}`;
  10692. return {
  10693. text: list + before.text.slice(before.prelude),
  10694. prelude: list.length,
  10695. atRule: false,
  10696. plain: true,
  10697. listable: LIST_YES,
  10698. listKind: before.listKind,
  10699. children: null,
  10700. head: ""
  10701. };
  10702. };
  10703. // A named layer block, up to the `{` its body opens with.
  10704. const NAMED_LAYER_BLOCK_RE = /^@layer [^{;]+\{/;
  10705. /**
  10706. * The `@layer <name> {` a printed sibling opens with, when the whole of it is
  10707. * one named layer block. An anonymous `@layer {` is a layer of its own and is
  10708. * not one of these (see `ANONYMOUS_LAYER_RE`).
  10709. * @param {string} text a sibling's printed text
  10710. * @returns {string | null} its opener, or null when it is not such a block
  10711. */
  10712. const _namedLayerOpener = (text) => {
  10713. if (text.length === 0 || text.charCodeAt(0) !== CC_AT_SIGN) return null;
  10714. if (text.charCodeAt(text.length - 1) !== CC_RIGHT_CURLY) return null;
  10715. const opener = NAMED_LAYER_BLOCK_RE.exec(text);
  10716. return opener === null ? null : opener[0];
  10717. };
  10718. // `@layer ` — what an opener carries in front of the layer it names.
  10719. const NAMED_LAYER_OPENER_HEAD = "@layer ".length;
  10720. /** @typedef {{ at: number, subtree: boolean, taken?: TakenPiece }} SeenLayer a named block already out, whether a later sibling wrote inside its layer's subtree, and the piece its rules are keyed in */
  10721. /**
  10722. * Record what a named block about to go out does to the ones already out. A
  10723. * block writes into its own layer, and into layers under it only when it opens
  10724. * one — and a block for `a.b` writes where a block for `a` opening `b` inside
  10725. * itself writes, so the two spellings are one layer and the order within it is
  10726. * one the cascade reads. A block whose subtree a later sibling wrote into may
  10727. * still be folded into, but only by one that stays in its own layer.
  10728. * @param {Map<string, SeenLayer>} layers the blocks seen so far, by opener
  10729. * @param {string} name the layer this block opens
  10730. * @param {boolean} deep whether it opens a layer of its own
  10731. * @returns {void}
  10732. */
  10733. const _noteLayerBlock = (layers, name, deep) => {
  10734. for (const [opener, seen] of layers) {
  10735. const other = opener.slice(NAMED_LAYER_OPENER_HEAD, -1).trim();
  10736. if (other === name) continue;
  10737. if (name.startsWith(`${other}.`)) seen.subtree = true;
  10738. else if (deep && other.startsWith(`${name}.`)) layers.delete(opener);
  10739. }
  10740. };
  10741. /**
  10742. * Whether a named layer block opens a layer of its own inside itself.
  10743. * @param {string} text the block's printed text
  10744. * @param {string} opener its `@layer <name> {`
  10745. * @returns {boolean} whether it writes past its own layer
  10746. */
  10747. const _opensNestedLayer = (text, opener) =>
  10748. text.includes("@layer", opener.length);
  10749. // The at-rules whose place in the sheet is what they say: where a layer is first
  10750. // named fixes its order against the others, and `@charset`, `@import` and
  10751. // `@namespace` are read only ahead of the rules they precede. A rule holding one
  10752. // is never dropped as a repeat, wherever in its text it sits.
  10753. const POSITIONAL_AT_RULE_RE = /@(?:charset|import|layer|namespace)\b/i;
  10754. /**
  10755. * Gather each named `@layer` block into the first sibling block of that name.
  10756. * They are one layer however far apart they stand, and what separates them is in
  10757. * another layer or in none — either way ordered against these by the cascade
  10758. * rather than by where they sit, so moving the later body up is not a move the
  10759. * cascade can see. An anonymous `@layer {` is a layer of its own and is left
  10760. * alone (see `ANONYMOUS_LAYER_RE`).
  10761. * @param {string[]} texts the siblings' printed texts, rewritten in place
  10762. * @returns {void}
  10763. */
  10764. const _mergeNamedLayerBlocks = (texts) => {
  10765. if (!_transforms.mergeRules) return;
  10766. /** @type {Map<string, SeenLayer> | null} */
  10767. let first = null;
  10768. for (let i = 0; i < texts.length; i++) {
  10769. const text = texts[i];
  10770. const prelude = _namedLayerOpener(text);
  10771. if (prelude === null) {
  10772. // A sibling naming a layer any other way writes into one of these, and
  10773. // the order within a layer is the cascade's, so nothing folds over it.
  10774. if (first !== null && POSITIONAL_AT_RULE_RE.test(text)) first.clear();
  10775. continue;
  10776. }
  10777. if (first === null) first = new Map();
  10778. const seen = first.get(prelude);
  10779. const deep = _opensNestedLayer(text, prelude);
  10780. _noteLayerBlock(
  10781. first,
  10782. prelude.slice(NAMED_LAYER_OPENER_HEAD, -1).trim(),
  10783. deep
  10784. );
  10785. if (seen === undefined || (deep && seen.subtree)) {
  10786. first.set(prelude, { at: i, subtree: false });
  10787. continue;
  10788. }
  10789. // Both bodies keep their order, so the layer reads as it was written.
  10790. const at = seen.at;
  10791. texts[at] = `${texts[at].slice(0, -1)}${text.slice(prelude.length)}`;
  10792. texts[i] = "";
  10793. }
  10794. };
  10795. /**
  10796. * Join adjacent sibling rules that print the same block. Nothing stands between
  10797. * them, so the cascade is unchanged; the later rule's text becomes empty and
  10798. * its selectors move onto the earlier one.
  10799. * @param {Node[]} items the parent's children in source order
  10800. * @param {string[]} texts their printed texts, rewritten in place
  10801. * @param {Set<Node> | null} pending the prefixed rules an unprefixed twin may
  10802. * still make dead, which have to stay one rule to be dropped whole
  10803. * @returns {void}
  10804. */
  10805. const _mergeAdjacentRules = (items, texts, pending) => {
  10806. if (!_transforms.mergeRules) return;
  10807. let previous = -1;
  10808. /** @type {RuleEntry | null} */
  10809. let held = null;
  10810. // Whether `held` is a rule this run built, rather than one the printer stored.
  10811. let owned = false;
  10812. for (let i = 0; i < items.length; i++) {
  10813. if (texts[i].length === 0) continue;
  10814. if (pending !== null && pending.has(items[i])) {
  10815. previous = i;
  10816. held = null;
  10817. owned = false;
  10818. continue;
  10819. }
  10820. const entry = _ruleEntryOf(items[i], texts[i]);
  10821. if (held !== null) {
  10822. const merged = _joinRuleEntries(held, entry, owned);
  10823. if (merged !== null) {
  10824. held = merged;
  10825. owned = true;
  10826. _ruleEntry.set(items[previous], merged);
  10827. texts[previous] = merged.text;
  10828. texts[i] = "";
  10829. continue;
  10830. }
  10831. }
  10832. // Anything else between them — a declaration, a rule that cannot join —
  10833. // parts the run: in a nested block a declaration is read at its own place.
  10834. previous = i;
  10835. held = entry.prelude === -1 ? null : entry;
  10836. owned = false;
  10837. }
  10838. };
  10839. // CSS Values 4 §5: "for zero lengths the unit identifier is optional". Only the
  10840. // length units — a zero time, angle, frequency, resolution or `<flex>` still
  10841. // needs its unit, and `0%` is a percentage, a different type.
  10842. const _ZERO_LENGTH_RE = new RegExp(`^0(?:${_LENGTH_UNITS})$`, "i");
  10843. /**
  10844. * Drop a zero length's unit. Only a whole component: anything inside `calc()`
  10845. * or a `var()` fallback stays, since there a bare `0` is a `<number>` and the
  10846. * expression would stop parsing. `_dropZeroLengthUnitInCall` reaches into the
  10847. * calls where it is a length instead.
  10848. * @param {string} fragment one printed top-level component
  10849. * @returns {string} the component, its zero unit dropped
  10850. */
  10851. const _dropZeroLengthUnit = (fragment) =>
  10852. _transforms.shortenNumbers && _ZERO_LENGTH_RE.test(fragment) ? "0" : fragment;
  10853. // A zero angle argument, on its own or one of a comma list.
  10854. const _ZERO_ANGLE_ARGUMENT_RE = /(^|,)\s*0(?:deg|grad|rad|turn)\s*(?=,|$)/gi;
  10855. /**
  10856. * Drop the unit a zero argument does not need, for a call whose own grammar
  10857. * makes it droppable — a `<zero>` beside `<angle>`, or a length.
  10858. * @param {string} fn the lowercased function name
  10859. * @param {string} inner the text between the parentheses
  10860. * @returns {string} the arguments, such zero units dropped
  10861. */
  10862. const _dropCallZeroUnit = (fn, inner) => {
  10863. if (ZERO_ANGLE_FUNCTIONS.has(fn)) {
  10864. return inner.replace(_ZERO_ANGLE_ARGUMENT_RE, "$10");
  10865. }
  10866. return LENGTH_ONLY_FUNCTIONS.has(fn) ? _dropZeroLengthUnit(inner) : inner;
  10867. };
  10868. // One call, split into its name and the text between its parentheses.
  10869. const _LONE_CALL_RE = /^([-\w]+)\(([^()]*)\)$/;
  10870. /**
  10871. * The same, for the arguments of a call whose every number is a length. The
  10872. * split is on top-level separators of a body holding no nested call, so each
  10873. * piece is a whole argument and a `calc()` inside one is never reached.
  10874. * @param {string} fragment one printed top-level component
  10875. * @returns {string} the component, its arguments' zero units dropped
  10876. */
  10877. const _dropZeroLengthUnitInCall = (fragment) => {
  10878. if (!_transforms.shortenNumbers) return fragment;
  10879. const match = _LONE_CALL_RE.exec(fragment);
  10880. if (match === null) return fragment;
  10881. if (!LENGTH_ONLY_FUNCTIONS.has(toLowerCaseIfNeeded(match[1]))) {
  10882. return fragment;
  10883. }
  10884. const body = match[2].replace(/[^\s,]+/g, _dropZeroLengthUnit);
  10885. return `${match[1]}(${body})`;
  10886. };
  10887. // A `calc()` holding one constant, which is the only shape the parentheses can
  10888. // come off. `-` is matched so a negative is recognized and then kept.
  10889. const _LONE_CALC_RE = /^calc\((-?(?:\d*\.\d+|\d+))(%|[a-z]+)?\)$/i;
  10890. // A folded term that means the same thing bare as it does inside `calc()`,
  10891. // whatever the property and wherever it stands: positive, and either carrying a
  10892. // unit (so never an `<integer>` context) or already a non-zero integer. A
  10893. // unitless fraction is left out — there the property decides, which only the
  10894. // declaration printer knows — as is anything negative. Zero is the one number a
  10895. // length also accepts, so `width:calc(0)` is dropped where `width:0` is not.
  10896. const _BARE_TERM_RE = /^(?:\d*\.\d+|\d+)(?:%|[a-z]+)$|^(?!0+$)\d+$/i;
  10897. // The same, one level in: a folded term standing as an operand of an outer math
  10898. // expression is arithmetic rather than a value, so no property judges it and a
  10899. // fraction or a zero needs no parentheses either. One non-negative term only —
  10900. // `calc(1px - calc(0px - 5px))` may not become `calc(1px - -5px)`, nor
  10901. // `calc(1px - calc(1em + 1px))` a sum whose second term changed sign.
  10902. const _NESTED_TERM_RE = /^(?:\d*\.\d+|\d+)(?:%|[a-z]+)?$/i;
  10903. /**
  10904. * Take the parentheses off a folded `calc()`, where the bare value means the
  10905. * same thing. Two shapes where it does not, both measured in headless Chromium:
  10906. * a negative is clamped inside `calc()` and a parse error outside it on a
  10907. * property that takes none (`width:calc(-5px)` renders at `0`, `width:-5px` at
  10908. * `auto`), and a fraction is rounded where the grammar wants an `<integer>`
  10909. * (`z-index:calc(1.5)` computes to `2`, `z-index:1.5` is dropped). A unit or a
  10910. * percentage settles the second on its own, since no `<integer>` carries
  10911. * either. A unitless zero is a third: it is the one number a length accepts, so
  10912. * `width:calc(0)` is dropped and `width:0` is not. A fourth is a value the spec
  10913. * clamps a `calc()` to and rejects bare (see `CLAMPED_VALUE_RANGES`).
  10914. * @param {string} fragment one printed component
  10915. * @param {string} property the lowercased property it belongs to
  10916. * @returns {string} the bare value, or the fragment as it was
  10917. */
  10918. const _unwrapCalc = (fragment, property) => {
  10919. const match = _LONE_CALC_RE.exec(fragment);
  10920. if (match === null) return fragment;
  10921. // Where the engine takes no `calc()`, the bare value is the one it reads —
  10922. // so unwrapping would switch on a declaration it had thrown away.
  10923. if (CALC_REJECTING_PROPERTIES.has(property)) return fragment;
  10924. const number = match[1];
  10925. if (
  10926. number.charCodeAt(0) === 0x2d &&
  10927. !NEGATIVE_ACCEPTING_PROPERTIES.has(property)
  10928. ) {
  10929. return fragment;
  10930. }
  10931. const unit = match[2] === undefined ? "" : match[2];
  10932. // The fold left the `calc()` because the value is clamped inside one and not
  10933. // outside; taking the parentheses off here would undo that.
  10934. if (_losesClamp(property, number, unit)) return fragment;
  10935. if (unit === "" && Number(number) === 0) return fragment;
  10936. if (unit === "" && number.includes(".") && INTEGER_PROPERTIES.has(property)) {
  10937. return fragment;
  10938. }
  10939. return number + unit;
  10940. };
  10941. /**
  10942. * Whether a `(…)` block is one of `@scope`'s two selector lists — its scope root
  10943. * or its limit — rather than a query condition. Both sit directly in the
  10944. * at-rule's prelude, so the immediate parent settles it.
  10945. * @param {CssPath} path the accessor positioned on the `(…)` block
  10946. * @returns {boolean} true inside a `@scope` prelude
  10947. */
  10948. const _inScopePrelude = (path) => {
  10949. const parent = path.parent;
  10950. return (
  10951. parent !== null &&
  10952. path.type(parent) === T_AT_RULE &&
  10953. equalsLowerCase(path.name(parent), "scope")
  10954. );
  10955. };
  10956. /**
  10957. * Whether a string token closed with its own quote rather than running to EOF.
  10958. * §4.3.5 ends an unterminated string at EOF, so its text has no closing quote
  10959. * and `slice(1, -1)` would drop a real character (`_minifyString` and
  10960. * `_minifyUrlFunction` refuse to rewrite one for the same reason).
  10961. * @param {string} text a string token's text
  10962. * @returns {boolean} true when the string is closed
  10963. */
  10964. const _isClosedString = (text) => {
  10965. const quote = text.charCodeAt(0);
  10966. const n = text.length;
  10967. if (n < 2 || text.charCodeAt(n - 1) !== quote) return false;
  10968. for (let i = 1; i < n - 1; i++) {
  10969. if (text.charCodeAt(i) !== CC_REVERSE_SOLIDUS) continue;
  10970. // The escape swallows what follows, the final quote included (`"a\"`).
  10971. if (i + 1 === n - 1) return false;
  10972. i++;
  10973. }
  10974. return true;
  10975. };
  10976. /**
  10977. * Print an attribute selector's children: the separators go (the grammar allows
  10978. * whitespace anywhere inside `[…]`, and `_join` still parts two fragments that
  10979. * would fuse — `[a=b i]`), and a quoted value that is also a bare identifier
  10980. * loses its quotes. Unquoting starts after the `=` delim, so the `[…]`'s name
  10981. * side is never touched. A separator right after a kept string stays: `[a="b" i]`
  10982. * is what every engine is exercised on.
  10983. * @param {CssPath} path the accessor positioned on the `[…]` block
  10984. * @param {ComponentValue[]} children the block's children
  10985. * @param {PrintContext} writer the print context (children's printed text)
  10986. * @returns {string[]} the fragments to join
  10987. */
  10988. const _printAttributeSelector = (path, children, writer) => {
  10989. /** @type {string[]} */
  10990. const parts = [];
  10991. let afterEquals = false;
  10992. let afterString = false;
  10993. let afterBar = false;
  10994. for (let i = 0; i < children.length; i++) {
  10995. const child = children[i];
  10996. const type = path.type(child);
  10997. if (type === T_WHITESPACE) {
  10998. // Not after the `|` a namespace is parted from its attribute by: the
  10999. // two are one token sequence there, so whitespace between them is what
  11000. // makes the selector invalid. Measured in headless Chromium: `[| a]` is
  11001. // dropped and `[ |a]` is not.
  11002. if (afterString || afterBar) parts.push(_SEP);
  11003. continue;
  11004. }
  11005. const text = writer.get(child);
  11006. afterString = false;
  11007. afterBar = !afterEquals && type === T_DELIM && text === "|";
  11008. if (!afterEquals) {
  11009. if (type === T_DELIM && text === "=") afterEquals = true;
  11010. parts.push(text);
  11011. } else if (
  11012. _transforms.normalizeQuotes &&
  11013. type === T_STRING &&
  11014. _isClosedString(path.source(child))
  11015. ) {
  11016. const body = text.slice(1, -1);
  11017. if (_isBareIdent(body)) {
  11018. parts.push(body);
  11019. } else {
  11020. parts.push(text);
  11021. afterString = true;
  11022. }
  11023. } else {
  11024. parts.push(text);
  11025. }
  11026. }
  11027. return parts;
  11028. };
  11029. /**
  11030. * A selector list is a set: the same selectors in any order match the same
  11031. * elements at the same specificity, so a repeat inside it says nothing and one
  11032. * canonical order lets equal lists compress alike. Only ever the author's own
  11033. * list — a join seam concatenates, so a run of them stays linear.
  11034. * @param {string} prelude the printed selector list
  11035. * @returns {string} it, or `prelude` when nothing there is shorter
  11036. */
  11037. const _canonicalSelectorList = (prelude) => {
  11038. if (!_transforms.shortenSelectors) return prelude;
  11039. // A list needs a comma; the great majority of preludes are one selector, and
  11040. // without one there is nothing to split, deduplicate or order.
  11041. if (!prelude.includes(",")) return prelude;
  11042. const list = _splitSelectorList(prelude);
  11043. if (list.length < 2) return prelude;
  11044. // One pass answers both questions the common case turns on — every selector
  11045. // distinct, and already in order — so the list is walked once rather than
  11046. // twice before being handed back untouched.
  11047. const seen = new Set();
  11048. let sorted = true;
  11049. for (let i = 0; i < list.length; i++) {
  11050. seen.add(list[i]);
  11051. if (i !== 0 && list[i - 1] > list[i]) sorted = false;
  11052. }
  11053. if (seen.size === list.length && sorted) return prelude;
  11054. return [...seen].sort().join(",");
  11055. };
  11056. /**
  11057. * A rule's prelude: everything before its `{`, which depends on nothing but the
  11058. * prelude itself. Split out of the printer so a rule whose block is streamed can
  11059. * print its opener when the block opens, long before the block's children exist.
  11060. * @param {CssPath} path the accessor positioned on the rule
  11061. * @param {PrintContext} writer the print context
  11062. * @param {boolean} minify whether printing minified
  11063. * @param {string[]=} outParts filled with the prelude's tokens, for prefixing
  11064. * @returns {string} the rule's prelude text
  11065. */
  11066. const _rulePrelude = (path, writer, minify, outParts) => {
  11067. const parts = outParts || [];
  11068. // Fold the `@name` in first so the join keeps the separator before the
  11069. // prelude's first token (e.g. `@media screen`, unmerged).
  11070. if (path.type() === T_AT_RULE) {
  11071. const name = path.name();
  11072. // `@charset` is the one at-keyword read as bytes rather than matched
  11073. // (CSS Syntax 3 §3.2), so lowercasing `@CHARSET` would turn a rule the
  11074. // engine drops into one that sets the sheet's encoding.
  11075. const lower = minify && !equalsLowerCase(name, "charset");
  11076. parts.push(`@${lower ? asciiLowerCaseName(name) : name}`);
  11077. }
  11078. _appendChildTexts(path.node, writer, parts);
  11079. // `@media`/`@container` only: the two preludes whose `(…)` is a media
  11080. // feature, so an `and` of two of them is an interval.
  11081. if (minify && path.type() === T_AT_RULE) {
  11082. // One read of the name for both tests: each is a slice of the source.
  11083. const name = path.name();
  11084. const media = equalsLowerCase(name, "media");
  11085. if (media || equalsLowerCase(name, "container")) {
  11086. // Only `@media`'s top level is media types and the keywords between
  11087. // them; `@container`'s leads with a container name, which is the
  11088. // author's own and case-sensitive.
  11089. if (media) _lowercaseConditionParts(parts);
  11090. if (_rangeSpellingAllowed) _collapseRangeInterval(parts);
  11091. }
  11092. }
  11093. if (minify && path.type() === T_QUALIFIED_RULE) {
  11094. _foldPseudoNames(parts);
  11095. _dropImpliedUniversalSelector(parts);
  11096. }
  11097. // Only a qualified rule's prelude is a selector — trim combinator spaces
  11098. // there; an at-rule prelude (media / container query) keeps its spacing.
  11099. const prelude = _join(
  11100. parts,
  11101. !minify,
  11102. path.type() === T_QUALIFIED_RULE ? _TRIM_COMBINATORS : _TRIM_NOTHING
  11103. );
  11104. if (minify && path.type() === T_QUALIFIED_RULE) {
  11105. // A keyframe selector, not a selector list: CSS Animations 1 §4 gives
  11106. // `from` and `to` as `0%` and `100%`, and each pair has a shorter half.
  11107. const parent = path.parent;
  11108. if (
  11109. _transforms.shortenSelectors &&
  11110. parent !== null &&
  11111. path.type(parent) === T_AT_RULE &&
  11112. KEYFRAMES_AT_RULE_RE.test(path.name(parent))
  11113. ) {
  11114. return _splitSelectorList(prelude)
  11115. .map((one) =>
  11116. equalsLowerCase(one, "from")
  11117. ? "0%"
  11118. : _KEYFRAME_FULL_RE.test(one)
  11119. ? "to"
  11120. : one
  11121. )
  11122. .join(",");
  11123. }
  11124. return _canonicalSelectorList(prelude);
  11125. }
  11126. return prelude;
  11127. };
  11128. /**
  11129. * Whether text ends in an escape the input ran out of — a `\` no character
  11130. * follows. The tokenizer already read it as one, so the raw text is not what the
  11131. * token holds, and writing it out would escape whatever the printer puts next.
  11132. * @param {string} text raw source text
  11133. * @returns {boolean} true when the last `\` has nothing to escape
  11134. */
  11135. const _endsInLoneEscape = (text) => {
  11136. let at = text.length;
  11137. while (at > 0 && text.charCodeAt(at - 1) === CC_REVERSE_SOLIDUS) at--;
  11138. return (text.length - at) % 2 === 1;
  11139. };
  11140. /**
  11141. * Put one declaration list together: the list-wide transforms (merge a family's
  11142. * longhands into their shorthand, drop what a later declaration supersedes, add
  11143. * and drop vendor prefixes, join adjacent rules) and the body text they make.
  11144. * A rule's block and a whole `block-contents` parse are the same production, so
  11145. * both go through here — what differs is only what is written around the body.
  11146. * @param {CssPath} path the walk's accessors
  11147. * @param {Declaration[]} decls the list's declarations
  11148. * @param {Rule[] | null} rules the rules interleaved with them, if any
  11149. * @param {PrintContext} writer the print context holding each node's text
  11150. * @param {boolean} minify whether to run the transforms
  11151. * @param {string} nl what prefixes each item: nothing minifying, a line break beautifying (declarations end in `;`, rules don't)
  11152. * @param {Node | null} owner the block this is the body of, or null at top level
  11153. * @returns {{ body: string, items: Node[], texts: string[], superseded: Set<number> | null, droppedPrefix: Set<number> | null, addedPrefix: Map<number, string> | null, deadPrefixed: Set<Node> | null, spans: RuleSpan[] | null }} the body, and what went into it
  11154. */
  11155. const _composeBlockBody = (path, decls, rules, writer, minify, nl, owner) => {
  11156. // Merge the split declarations + child rules back into source order
  11157. // (§5.4.5 loses their interleaving; reordering would change the cascade).
  11158. /** @type {Node[]} */
  11159. let items = decls;
  11160. if (rules !== null && rules.length !== 0) {
  11161. items =
  11162. decls.length === 0
  11163. ? rules
  11164. : [...decls, ...rules].sort((a, b) => path.start(a) - path.start(b));
  11165. }
  11166. // A family's longhands are their shorthand, so they print as it — four
  11167. // sides or corners, the two a pair shorthand sets, or the slots of an
  11168. // order-free one. Earlier tables win, though no property is in two of
  11169. // them.
  11170. const mergeable = minify;
  11171. /** @type {Map<Node, string>[]} */
  11172. const merges = [];
  11173. if (mergeable && decls.length >= 2) {
  11174. // One pass over the names for all three tables: which declaration wrote
  11175. // each property, which was written twice, and how many are a longhand
  11176. // any shorthand could gather. A shorthand needs two of those, so a
  11177. // block holding fewer cannot write one whatever the tables say — which
  11178. // is almost every block, and what keeps this off them.
  11179. /** @type {Map<string, number>} */
  11180. const at = new Map();
  11181. /** @type {Set<string>} */
  11182. const repeated = new Set();
  11183. let mergeableNames = 0;
  11184. for (let i = 0; i < decls.length; i++) {
  11185. const property = toLowerCaseIfNeeded(path.name(decls[i]));
  11186. if (at.has(property)) repeated.add(property);
  11187. else if (MERGE_LONGHANDS.has(property)) mergeableNames++;
  11188. at.set(property, i);
  11189. }
  11190. if (mergeableNames >= 2) {
  11191. // Child rules before each declaration: a family whose first and last
  11192. // count the same has none between them for the merge to step over.
  11193. /** @type {Uint32Array | null} */
  11194. let rulesBefore = null;
  11195. if (items !== decls) {
  11196. rulesBefore = new Uint32Array(decls.length);
  11197. let seen = 0;
  11198. let next = 0;
  11199. for (let i = 0; i < items.length; i++) {
  11200. if (next < decls.length && items[i] === decls[next]) {
  11201. rulesBefore[next++] = seen;
  11202. } else {
  11203. seen++;
  11204. }
  11205. }
  11206. }
  11207. for (const [table, mode] of MERGE_TABLES) {
  11208. const merged = _mergeBoxLonghands(
  11209. path,
  11210. decls,
  11211. writer,
  11212. table,
  11213. mode,
  11214. at,
  11215. repeated,
  11216. rulesBefore
  11217. );
  11218. if (merged !== null) merges.push(merged);
  11219. }
  11220. }
  11221. }
  11222. /** @type {string[]} */
  11223. const texts = [];
  11224. for (const item of items) {
  11225. /** @type {string | undefined} */
  11226. let replacement;
  11227. for (const merged of merges) {
  11228. replacement = merged.get(item);
  11229. if (replacement !== undefined) break;
  11230. }
  11231. texts.push(replacement === undefined ? writer.get(item) : replacement);
  11232. }
  11233. // The block's own children have all printed, so their sibling state is
  11234. // final: which of them a twin has already made dead, and which are still
  11235. // waiting for one. A rule that may yet be taken back must stay one rule,
  11236. // so it is kept out of a join.
  11237. const childScope =
  11238. _seenPrefixRules === null ? undefined : _seenPrefixRules.get(owner);
  11239. if (minify) {
  11240. _mergeAdjacentRules(
  11241. items,
  11242. texts,
  11243. childScope === undefined || childScope.pending === null
  11244. ? null
  11245. : new Set(childScope.pending.values())
  11246. );
  11247. _mergeNamedLayerBlocks(texts);
  11248. }
  11249. // Only the last of a set of declarations of one property can be read, so
  11250. // the earlier ones carry nothing — whatever stands between them. Identical
  11251. // text always resolves that way; a differently spelled value only where
  11252. // the later one writes every name the earlier does, since otherwise the
  11253. // earlier is its fallback — dropping those is how esbuild and
  11254. // lightningcss lose a `color(display-p3 …)` pair.
  11255. // TODO offer the wider "drop whatever a later declaration overrides"
  11256. // behind an option, as csso and cssnano expose theirs. It needs support
  11257. // data per keyword: a bare ident may be invalid, or newer than the value
  11258. // it stands after, and neither supersedes anything.
  11259. /** @type {Set<number> | null} */
  11260. let superseded = null;
  11261. // One item can supersede nothing, and most rules hold one or none.
  11262. if (minify && _transforms.removeDeadRules && items.length > 1) {
  11263. superseded = new Set();
  11264. /** @type {Map<string, number>} */
  11265. const seen = new Map();
  11266. /** @type {Map<string, number[]>} */
  11267. const wrote = new Map();
  11268. /** @type {Map<string, number> | null} */
  11269. let seenRules = null;
  11270. for (let i = 0; i < items.length; i++) {
  11271. // A nested rule parts the declarations around it into their own
  11272. // rules, so the one before it is read at its own place in the
  11273. // cascade rather than resolving to the one after.
  11274. if (path.type(items[i]) !== T_DECLARATION) {
  11275. seen.clear();
  11276. wrote.clear();
  11277. const rule = texts[i];
  11278. // An identical later sibling writes the same declarations to the
  11279. // same elements and wins the tie, so this one is read for nothing
  11280. // — whatever stands between them, which can only lose to the later
  11281. // one wherever it would have beaten this.
  11282. if (rule.length !== 0 && !POSITIONAL_AT_RULE_RE.test(rule)) {
  11283. if (seenRules === null) seenRules = new Map();
  11284. const before = seenRules.get(rule);
  11285. if (before !== undefined) superseded.add(before);
  11286. seenRules.set(rule, i);
  11287. }
  11288. continue;
  11289. }
  11290. const text = texts[i];
  11291. const at = seen.get(text);
  11292. if (at !== undefined) superseded.add(at);
  11293. seen.set(text, i);
  11294. const colon = text.indexOf(":");
  11295. // A custom property's value is a token stream nothing here reads, so
  11296. // what an engine makes of it is not knowable from its spelling.
  11297. if (colon <= 0 || text.charCodeAt(1) === CC_HYPHEN_MINUS) continue;
  11298. const property = _printedProperty(text, colon);
  11299. if (!CUSTOM_IDENT_LIST_PROPERTIES.has(property)) continue;
  11300. let earlier = wrote.get(property);
  11301. if (earlier === undefined) {
  11302. earlier = [];
  11303. wrote.set(property, earlier);
  11304. } else {
  11305. const value = _printedValue(text, colon);
  11306. const important = path.important(items[i]);
  11307. for (let j = earlier.length - 1; j >= 0; j--) {
  11308. const before = earlier[j];
  11309. // An `!important` earlier one still wins over a later plain
  11310. // declaration, which leaves the later dead rather than it.
  11311. if (!important && path.important(items[before])) continue;
  11312. const beforeText = texts[before];
  11313. if (
  11314. !_coveredByLater(
  11315. value,
  11316. _printedValue(beforeText, beforeText.indexOf(":"))
  11317. )
  11318. ) {
  11319. continue;
  11320. }
  11321. superseded.add(before);
  11322. earlier.splice(j, 1);
  11323. }
  11324. }
  11325. earlier.push(i);
  11326. }
  11327. // After the supersedes: a declaration a later one already made dead is
  11328. // no shorthand to fold into, and folding never makes one dead.
  11329. if (_transforms.mergeLonghands) {
  11330. _foldFollowingLonghands(path, items, texts, superseded);
  11331. }
  11332. }
  11333. // Vendor prefixes: drop a prefixed declaration no target needs (its
  11334. // unprefixed sibling already covers them), and before an unprefixed one
  11335. // add the prefixes a target still needs. Off with no target list.
  11336. /** @type {Set<number> | null} */
  11337. let droppedPrefix = null;
  11338. /** @type {Map<number, string> | null} */
  11339. let addedPrefix = null;
  11340. if (_prefixingOn) {
  11341. // One pass reads each declaration's property, and only a block writing
  11342. // a property some engine spells with a prefix — few do — is walked
  11343. // again to decide what to add or drop.
  11344. /** @type {string[]} */
  11345. const properties = [];
  11346. // The value only where its property has vendor spellings for one, which
  11347. // is what keeps the read off every other declaration.
  11348. /** @type {string[]} */
  11349. const values = [];
  11350. let prefixable = false;
  11351. for (let i = 0; i < items.length; i++) {
  11352. const text = texts[i];
  11353. if (path.type(items[i]) !== T_DECLARATION || text.length === 0) {
  11354. properties.push("");
  11355. values.push("");
  11356. continue;
  11357. }
  11358. // The name is read only where its shape says it could matter, which
  11359. // is what keeps this off the declarations — almost all of them — that
  11360. // no engine ever spelled another way.
  11361. const colon = text.indexOf(":");
  11362. if (!_mayPrefixDeclaration(text, colon)) {
  11363. properties.push("");
  11364. values.push("");
  11365. continue;
  11366. }
  11367. const property = _printedProperty(text, colon);
  11368. properties.push(property);
  11369. const valueTable = PREFIXED_VALUES.get(property);
  11370. values.push(valueTable === undefined ? "" : _printedValue(text, colon));
  11371. if (
  11372. valueTable !== undefined ||
  11373. PREFIXED_PROPERTIES.has(property) ||
  11374. (property.charCodeAt(0) === CC_HYPHEN_MINUS &&
  11375. VENDOR_PREFIX.test(property))
  11376. ) {
  11377. prefixable = true;
  11378. }
  11379. }
  11380. if (prefixable) {
  11381. /** @type {Set<string>} */
  11382. const present = new Set();
  11383. for (let i = 0; i < properties.length; i++) {
  11384. if (properties[i].length === 0) continue;
  11385. present.add(properties[i]);
  11386. // A value is only ever asked for against its own property, so the
  11387. // two share one set.
  11388. if (values[i].length !== 0) {
  11389. present.add(`${properties[i]}\0${values[i]}`);
  11390. }
  11391. }
  11392. for (let i = 0; i < properties.length; i++) {
  11393. const property = properties[i];
  11394. if (property.length === 0) continue;
  11395. let add = "";
  11396. const value = values[i];
  11397. if (value.length !== 0) {
  11398. const valueTable = /** @type {Map<string, [string, number][]>} */ (
  11399. PREFIXED_VALUES.get(property)
  11400. );
  11401. const keyword = _prefixedValueKeyword(valueTable, value);
  11402. if (keyword !== undefined) {
  11403. // A vendor-spelled value, dead once the keyword it stands for
  11404. // is written for this property too.
  11405. if (
  11406. present.has(`${property}\0${keyword}`) &&
  11407. _prefixRemovable(valueTable, keyword, value)
  11408. ) {
  11409. if (droppedPrefix === null) droppedPrefix = new Set();
  11410. droppedPrefix.add(i);
  11411. continue;
  11412. }
  11413. } else {
  11414. const spellings = _neededPrefixes(valueTable, value);
  11415. if (spellings !== null) {
  11416. const colon = property.length;
  11417. for (const spelling of spellings) {
  11418. if (present.has(`${property}\0${spelling}`)) continue;
  11419. add += `${
  11420. nl +
  11421. texts[i].slice(0, colon + 1) +
  11422. spelling +
  11423. texts[i].slice(colon + 1 + value.length)
  11424. }`;
  11425. }
  11426. }
  11427. }
  11428. }
  11429. const base =
  11430. property.charCodeAt(0) === CC_HYPHEN_MINUS
  11431. ? _prefixedPropertyName(property)
  11432. : undefined;
  11433. if (base !== undefined) {
  11434. if (
  11435. present.has(base) &&
  11436. _prefixRemovable(PREFIXED_PROPERTIES, base, property)
  11437. ) {
  11438. if (droppedPrefix === null) droppedPrefix = new Set();
  11439. droppedPrefix.add(i);
  11440. continue;
  11441. }
  11442. } else {
  11443. const needed = _neededPrefixes(PREFIXED_PROPERTIES, property);
  11444. if (needed !== null) {
  11445. for (const spelling of needed) {
  11446. if (present.has(spelling)) continue;
  11447. const copy = _prefixedDeclaration(
  11448. spelling,
  11449. texts[i],
  11450. property.length
  11451. );
  11452. if (copy.length !== 0) add += nl + copy;
  11453. }
  11454. }
  11455. }
  11456. if (add.length !== 0) {
  11457. if (addedPrefix === null) addedPrefix = new Map();
  11458. addedPrefix.set(i, add);
  11459. }
  11460. }
  11461. }
  11462. }
  11463. // Every child has printed by now, so the sibling set they shared goes.
  11464. if (_seenPrefixRules !== null && owner !== null) {
  11465. _seenPrefixRules.delete(owner);
  11466. }
  11467. // A nested prefixed rule an unprefixed twin has since made dead weight:
  11468. // the twin could not take it back out of the output, since the text was
  11469. // still on its way here, so it is left out as the body is put together.
  11470. const deadPrefixed = childScope === undefined ? null : childScope.dead;
  11471. let body = "";
  11472. // Where each rule this block carries lands in it, so the copy that makes
  11473. // one dead can find it without the sheet being read a second time. A
  11474. // nested block's own spans move in with its text, its prelude joining
  11475. // their keys — by the top level each key names a rule and its conditions.
  11476. /** @type {RuleSpan[] | null} */
  11477. let spans = null;
  11478. // One per child with a body, in the order they finished — which is what
  11479. // `rules` holds, so there is nothing to count. Nothing reads them while
  11480. // not minifying, when no block records any.
  11481. const bodies = minify && rules !== null ? rules.length : 0;
  11482. const childSpans =
  11483. bodies === 0 ? _NO_BLOCK_SPANS : _blockSpans.splice(-bodies, bodies);
  11484. let atChild = 0;
  11485. for (let i = 0; i < texts.length; i++) {
  11486. const isBlock = bodies !== 0 && path.type(items[i]) !== T_DECLARATION;
  11487. const mine = isBlock ? childSpans[atChild++] : undefined;
  11488. if (droppedPrefix !== null && droppedPrefix.has(i)) continue;
  11489. const text = texts[i];
  11490. if (text.length === 0) continue;
  11491. if (superseded !== null && superseded.has(i)) continue;
  11492. if (deadPrefixed !== null && deadPrefixed.has(items[i])) continue;
  11493. if (addedPrefix !== null) {
  11494. const add = addedPrefix.get(i);
  11495. if (add !== undefined) body += add;
  11496. }
  11497. body += nl;
  11498. const at = body.length;
  11499. body += text;
  11500. if (minify && isBlock) {
  11501. if (spans === null) spans = [];
  11502. _collectRuleSpans(spans, items[i], text, at, path, mine);
  11503. }
  11504. }
  11505. // Drop the redundant `;` a following `}` — or, at top level, the end of the
  11506. // input — makes so, when minifying.
  11507. if (minify) {
  11508. while (
  11509. body.length !== 0 &&
  11510. body.charCodeAt(body.length - 1) === CC_SEMICOLON
  11511. ) {
  11512. body = body.slice(0, -1);
  11513. }
  11514. }
  11515. return {
  11516. body,
  11517. items,
  11518. texts,
  11519. superseded,
  11520. droppedPrefix,
  11521. addedPrefix,
  11522. deadPrefixed,
  11523. spans
  11524. };
  11525. };
  11526. /**
  11527. * The default CSS node printer — passed to the `SourceProcessor` and fired once a
  11528. * node's visitors and children are done (a developer could supply their own).
  11529. * It takes the same `path` a visitor gets plus the print context as its `writer`,
  11530. * and knows nothing of the walk: it switches on `path.type()`, reads the node's
  11531. * name / children through `path`, pulls its children's already-printed text from
  11532. * `writer.get`, composes this node's text (with the CSS-local `_join` / `_SEP` /
  11533. * spacing) and **returns** it. Structure keeps it safe: a declaration's
  11534. * `name:value` drops the space around `:` a flat token stream could not, and
  11535. * custom-property (`--*`) values stay verbatim. When minifying it also applies the
  11536. * safe value transforms (number normalization, hex / rgb() color minification,
  11537. * `cubic-bezier()` / `steps()` easing keywords, string / attribute-selector /
  11538. * `url()` quote normalization, identifier escape shortening, `{1,4}` box and
  11539. * `flex` shorthand collapsing), drops the
  11540. * whitespace a query condition does not need, and drops rules whose block ends
  11541. * up empty — each value-identical, so meaning never changes.
  11542. * @experimental exposed as `webpack.css.syntax.printer`; unstable API
  11543. * @param {CssPath} path the accessor positioned on the finished node
  11544. * @param {PrintContext} writer the print context (children's printed text)
  11545. * @returns {string} the node's serialized text
  11546. */
  11547. const printer = (path, writer) => {
  11548. const minify = writer.options.mode === "minify";
  11549. // `""` minifying / `" "` beautifying (a "soft" space, e.g. after a `:`).
  11550. const soft = minify ? "" : " ";
  11551. switch (path.type()) {
  11552. case T_WHITESPACE:
  11553. return _SEP;
  11554. // Only a newline makes a string bad, and the tokenizer leaves it unconsumed
  11555. // — so carry it, or the string closes and the declaration an engine threw
  11556. // away runs.
  11557. case T_BAD_STRING:
  11558. return `${path.source()}\n`;
  11559. case T_NUMBER:
  11560. case T_PERCENTAGE:
  11561. case T_DIMENSION: {
  11562. const raw = path.source();
  11563. // Only declaration values: a prelude number is An+B (`2n+1`) or similar,
  11564. // where stripping a `+` breaks the selector.
  11565. return minify && path.inValue() ? _normalizeNumericToken(raw) : raw;
  11566. }
  11567. case T_FUNCTION: {
  11568. const name = path.name();
  11569. // A container style query asks whether a custom property's value is the
  11570. // one written here, and that comparison reads the token stream as
  11571. // written — so a squeezed whitespace run inside one asks a different
  11572. // question (CSS Conditional 5 §5). Nothing in it is rewritten.
  11573. if (
  11574. minify &&
  11575. !path.inValue() &&
  11576. _inMediaConditionPrelude &&
  11577. equalsLowerCase(name, "style")
  11578. ) {
  11579. const written = path.source();
  11580. // Unclosed at EOF, the source carries no `)` for the walk to have
  11581. // closed — printed as usual so the parenthesis is written back.
  11582. if (written.endsWith(")")) {
  11583. // Written whole, comments included, so the queued copies are claimed
  11584. // here rather than flushed a second time after the rule.
  11585. writer.takeInserts(path.start(), path.end());
  11586. return written;
  11587. }
  11588. }
  11589. let inner;
  11590. if (path.childCount() === 1) {
  11591. // Half of all functions take one argument, which `_join` would hand
  11592. // straight back — and a trim only ever parts two of them, so no
  11593. // universal a compound implies can stand beside anything here either.
  11594. const only = writer.get(path.childAt(path.node, 0));
  11595. inner = only === _SEP ? "" : only;
  11596. } else {
  11597. // A selector function's argument is a selector, so its combinators need
  11598. // no whitespace — the same trim the enclosing prelude already gets. A
  11599. // `@supports` condition is the syntax being tested rather than applied,
  11600. // and an engine hands it back as written, so it keeps its whitespace.
  11601. const trim =
  11602. _mathFunctionDepth !== 0
  11603. ? _TRIM_MATH
  11604. : !path.inValue() &&
  11605. !_inSupportsPrelude &&
  11606. SELECTOR_FUNCTIONS.has(name.toLowerCase())
  11607. ? _TRIM_COMBINATORS
  11608. : _TRIM_NOTHING;
  11609. /** @type {string[]} */
  11610. const parts = [];
  11611. _appendChildTexts(path.node, writer, parts);
  11612. // A selector function's argument is a selector like the prelude's, so
  11613. // the universal a compound implies says nothing there either.
  11614. if (minify && trim === _TRIM_COMBINATORS) {
  11615. _foldPseudoNames(parts);
  11616. _dropImpliedUniversalSelector(parts);
  11617. }
  11618. inner = _join(parts, !minify, minify ? trim : _TRIM_NOTHING);
  11619. // Chrome throws out `attr( name unit )` where `attr( name unit)`
  11620. // parses, so the space before the `)` decides whether the declaration
  11621. // runs — `_join` drops a trailing one like any other.
  11622. if (
  11623. minify &&
  11624. parts[parts.length - 1] === _SEP &&
  11625. equalsLowerCase(name, "attr")
  11626. ) {
  11627. inner += " ";
  11628. }
  11629. }
  11630. if (!minify) return `${name}(${inner})`;
  11631. // Function names match ASCII case-insensitively, so one lowercase name
  11632. // is what every table below is keyed by; the eleven transforms spelled
  11633. // with a capital are printed the way everything else writes them.
  11634. const fn = asciiLowerCaseName(name);
  11635. // As with a unit: a name already lowercase is its own answer, and only a
  11636. // shouted one is looked up. The lookup key still folds inside a
  11637. // substituted value; only the printed spelling stays as written.
  11638. const out = _inSubstitutedValue
  11639. ? name
  11640. : fn === name
  11641. ? fn
  11642. : CANONICAL_NAMES.get(fn) || fn;
  11643. // An+B in a selector: `2n+1` is what `odd` names, and shorter written so.
  11644. if (!path.inValue() && NTH_PSEUDO_FUNCTIONS.has(fn)) {
  11645. return _minifyAnPlusB(out, inner);
  11646. }
  11647. // A color function (rgb/rgba) collapses to the shortest color; anything
  11648. // else keeps its `name(args)` form.
  11649. // A color inside a `@supports` condition is the syntax being tested — the
  11650. // engines that read `#0000` are not the ones that read `rgba(0,0,0,0)`,
  11651. // so rewriting it asks a different question (esbuild, clean-css and
  11652. // lightningcss leave the condition alone too).
  11653. // A gradient stop outside sRGB is handed back as written rather than
  11654. // computed, so converting one moves the value the CSSOM reports — and
  11655. // where the gradient names the space its stops interpolate in, it moves
  11656. // the ramp itself. The legacy sRGB forms all compute to `rgb(…)`
  11657. // whichever way they are spelled, so those still fold.
  11658. const color = _inSupportsPrelude
  11659. ? null
  11660. : _minifyColorFunction(fn, inner, _hexAlphaAllowed) ||
  11661. (_inGradient
  11662. ? null
  11663. : _minifyPolarColorFunction(fn, inner, _hexAlphaAllowed));
  11664. if (
  11665. color !== null &&
  11666. (color !== "transparent" || !_inTapHighlightColor())
  11667. ) {
  11668. return color === "transparent" && _hexAlphaAllowed ? "#0000" : color;
  11669. }
  11670. // A math function over constants of one unit is that one value, and it
  11671. // is written back as a `calc()` whatever it was: the parentheses have to
  11672. // stay (dropping them turns a clamped negative into a parse error and a
  11673. // rounded fraction into one), and only the declaration printer — which
  11674. // knows the property — takes them off.
  11675. if (!_inSupportsPrelude && !_inCustomProperty) {
  11676. const term = _foldMathFunction(fn, inner);
  11677. if (term !== null) {
  11678. // A term that means the same bare needs no parentheses anywhere, so
  11679. // it is written as itself; the rest keep a `calc()` for the
  11680. // declaration printer to judge against the property.
  11681. const shape =
  11682. _mathFunctionDepth > 1 ? _NESTED_TERM_RE : _BARE_TERM_RE;
  11683. const folded =
  11684. shape.test(term) &&
  11685. !_inCalcRejectingProperty() &&
  11686. !_foldLosesClamp(term)
  11687. ? term
  11688. : `calc(${term})`;
  11689. // A division can land on a value needing every digit of a double,
  11690. // which is longer than the expression that produced it.
  11691. if (folded.length < fn.length + inner.length + 2) return folded;
  11692. }
  11693. const reduced = _reduceMathArguments(fn, inner);
  11694. if (reduced !== null) return `${fn}(${reduced})`;
  11695. }
  11696. // An easing function is only ever a value; `url()` is also an at-rule
  11697. // prelude's (`@import url("a.css")`).
  11698. const easing =
  11699. path.inValue() && !_inCustomProperty
  11700. ? _minifyEasingFunction(fn, inner)
  11701. : null;
  11702. if (easing !== null) return easing;
  11703. // `translateX(v)` is `translate(v)` and `skewX(a)` is `skew(a)` — the
  11704. // second component defaults to 0. Only for one plain component: a
  11705. // substitution could expand to two, which the one-axis spelling rejects
  11706. // and the pair would accept, reviving a declaration the browser drops.
  11707. const oneAxis = _transforms.reduceFunctions
  11708. ? X_AXIS_TRANSFORMS.get(fn)
  11709. : undefined;
  11710. if (
  11711. oneAxis !== undefined &&
  11712. path.inValue() &&
  11713. !_inSubstitutedValue &&
  11714. !inner.includes(",") &&
  11715. !_hasSubstitution(inner)
  11716. ) {
  11717. return `${oneAxis}(${inner})`;
  11718. }
  11719. if (path.inValue() && !_inSubstitutedValue) {
  11720. // An argument that is the amount an omitted one means says nothing,
  11721. // whether or not the zero still carries the unit its grammar drops.
  11722. const omitted = _transforms.reduceFunctions
  11723. ? FILTER_FUNCTION_OMITTED.get(fn)
  11724. : undefined;
  11725. if (omitted !== undefined) {
  11726. const bare = _dropCallZeroUnit(fn, inner);
  11727. if (bare === omitted || (omitted === "1" && bare === "100%")) {
  11728. return `${out}()`;
  11729. }
  11730. }
  11731. const reduced = _reduceTransformFunctionDeep(fn, inner);
  11732. if (reduced !== null) return reduced;
  11733. // The stops are folded over what dropping the direction leaves, a
  11734. // gradient carrying both otherwise keeping whichever ran second.
  11735. const gradient = _dropDefaultGradientDirection(fn, inner);
  11736. const stops = _foldGradientStops(
  11737. fn,
  11738. gradient === null ? inner : gradient,
  11739. _doublePositionAllowed
  11740. );
  11741. if (stops !== null) return `${out}(${stops})`;
  11742. if (gradient !== null) return `${out}(${gradient})`;
  11743. }
  11744. // A zero angle needs no unit where the grammar names `<zero>` beside
  11745. // `<angle>` (CSS Transforms 2, Filter Effects 1).
  11746. if (
  11747. path.inValue() &&
  11748. !_inSubstitutedValue &&
  11749. _transforms.shortenNumbers &&
  11750. ZERO_ANGLE_FUNCTIONS.has(fn)
  11751. ) {
  11752. const bare = inner.replace(_ZERO_ANGLE_ARGUMENT_RE, "$10");
  11753. if (bare !== inner) return `${out}(${bare})`;
  11754. }
  11755. const url = _inSubstitutedValue ? null : _minifyUrlFunction(fn, inner);
  11756. if (url !== null) return url;
  11757. return `${out}(${inner})`;
  11758. }
  11759. case T_URL: {
  11760. const raw = path.source();
  11761. // Closed at EOF: write the `)` back so the url stops where it stopped for
  11762. // the tokenizer rather than swallowing what the printer emits next.
  11763. if (_isUnterminatedUrl(raw)) return _terminate(raw, ")", "\uFFFD");
  11764. // The tokenizer already trimmed the url-token's content, so the padding in
  11765. // `url( a.png )` carries nothing.
  11766. if (!minify) return raw;
  11767. const open = raw.indexOf("(");
  11768. // `url` matches ASCII case-insensitively like any other function name.
  11769. // Read in place: cutting the name out to fold it would allocate on every
  11770. // url, and every url but a shouted one folds to itself. Inside a
  11771. // substituted value the spelling is the author's, so nothing is read.
  11772. let head = null;
  11773. if (!_inSubstitutedValue) {
  11774. for (let i = 0; i < open; i++) {
  11775. const c = raw.charCodeAt(i);
  11776. if (c >= CC_UPPER_A && c <= CC_UPPER_Z) {
  11777. head = asciiLowerCaseName(raw.slice(0, open + 1));
  11778. break;
  11779. }
  11780. }
  11781. }
  11782. const percent = raw.includes("%");
  11783. // Ahead of the padding fast path below: a base64 `data:` url is
  11784. // whitespace-free and `%`-free, so it would take it and never be offered.
  11785. if (_deferEmbeddedSource !== undefined) {
  11786. const opener = head === null ? raw.slice(0, open + 1) : head;
  11787. const deferred = _deferDataUrl(
  11788. path.value(),
  11789. (url) => `${opener}${_serializeUrl(url, '"')})`,
  11790. raw
  11791. );
  11792. if (deferred !== null) return deferred;
  11793. }
  11794. if (_renderEmbeddedSource !== undefined) {
  11795. const renderedUrl = _renderDataUrl(path.value());
  11796. if (renderedUrl !== null) {
  11797. const name = head === null ? raw.slice(0, open + 1) : head;
  11798. return `${name}${_serializeUrl(renderedUrl, '"')})`;
  11799. }
  11800. }
  11801. // Padding only exists when the content is whitespace-bounded; checking
  11802. // that costs two char reads, reading the content costs a slice.
  11803. if (
  11804. !percent &&
  11805. !_isWhiteSpace(raw.charCodeAt(open + 1)) &&
  11806. !_isWhiteSpace(raw.charCodeAt(raw.length - 2))
  11807. ) {
  11808. return head === null ? raw : head + raw.slice(open + 1);
  11809. }
  11810. if (head === null) head = raw.slice(0, open + 1);
  11811. const value = path.value();
  11812. // A url-token already carrying an escape is one this printer did not
  11813. // write, so its `\%` is not read as the start of a percent-escape.
  11814. return `${head}${
  11815. percent && !value.includes("\\")
  11816. ? _decodePercentEscapes(value, true, "")
  11817. : value
  11818. })`;
  11819. }
  11820. case T_STRING: {
  11821. const raw = path.source();
  11822. // A custom property hands its string back as written, quotes included.
  11823. if (!minify || _inCustomProperty) {
  11824. // Closed at EOF: write the quote back, for the same reason as `url()`.
  11825. return _isClosedString(raw) ? raw : _terminate(raw, raw[0], "");
  11826. }
  11827. // `<family-name>` is `<string> | <custom-ident>+`, so a family whose text
  11828. // is a run of identifiers means the same unquoted, two bytes shorter. An
  11829. // unterminated string is no family name, so it skips to the repair below.
  11830. if (
  11831. _isClosedString(raw) &&
  11832. !_inSupportsPrelude &&
  11833. !_inSubstitutedValue &&
  11834. _inFontFamily() &&
  11835. _isLoneFamilyName(path)
  11836. ) {
  11837. const unquoted = _unquoteFontFamily(raw);
  11838. if (unquoted !== null) return unquoted;
  11839. }
  11840. const text = _minifyString(raw);
  11841. // Closed at EOF: write the quote back, for the same reason as `url()`.
  11842. return _isClosedString(text) ? text : _terminate(text, text[0], "");
  11843. }
  11844. case T_IDENT: {
  11845. const raw = path.source();
  11846. if (!minify) return raw;
  11847. // `transparent` is `#0000` where the target reads a hex alpha — the same
  11848. // color, six bytes shorter. The tap-highlight guard still holds: that
  11849. // WebKit bug is about the keyword's *value*, whichever way it is spelled.
  11850. if (
  11851. path.inValue() &&
  11852. !_inSubstitutedValue &&
  11853. _hexAlphaAllowed &&
  11854. _transforms.shortenColors &&
  11855. equalsLowerCase(raw, "transparent") &&
  11856. !_inTapHighlightColor()
  11857. ) {
  11858. return "#0000";
  11859. }
  11860. if (
  11861. path.inValue() &&
  11862. !_inCustomProperty &&
  11863. !_inSupportsPrelude &&
  11864. !_inSubstitutedValue
  11865. ) {
  11866. // One fold for both questions below. `toLowerCaseIfNeeded` hands back
  11867. // the string it was given where nothing changes, so a value written
  11868. // in one case answers the second by identity and is never walked a
  11869. // second time — which is every identifier in a stylesheet a build
  11870. // step wrote.
  11871. const lowered = toLowerCaseIfNeeded(raw);
  11872. // A named color where the property takes nothing else an identifier
  11873. // could be: `white` is `#fff`, and the engine computes both to
  11874. // `rgb(255, 255, 255)`. Elsewhere an identifier may be the author's
  11875. // own name.
  11876. if (_transforms.shortenColors) {
  11877. const shorter = COLOR_NAME_TO_SHORTEST.get(lowered);
  11878. if (shorter !== undefined && _inColorOnlyProperty()) return shorter;
  11879. }
  11880. // A property whose value is keywords alone — or a color, which is
  11881. // keywords and numbers — names nothing of the author's, so an
  11882. // identifier standing directly in one of its values is a keyword and
  11883. // matches ASCII case-insensitively. Directly only: a call's arguments
  11884. // are read against the function's own grammar, where a name may be
  11885. // the author's.
  11886. if (
  11887. lowered !== raw &&
  11888. path.parent === _valueDeclaration &&
  11889. _inKeywordOnlyValue()
  11890. ) {
  11891. const folded = asciiLowerCaseName(raw);
  11892. if (folded !== raw) return folded;
  11893. }
  11894. }
  11895. return _minifyIdentEscapes(raw);
  11896. }
  11897. case T_SIMPLE_BLOCK: {
  11898. const open = path.blockToken();
  11899. const close = open === "(" ? ")" : open === "[" ? "]" : "}";
  11900. // Outside a declaration value a `[…]` is an attribute selector, whose
  11901. // quoted value may be a bare identifier, and a `(…)` is a query condition,
  11902. // whose comparisons need no whitespace. Inside one they are a grid
  11903. // line-name list and a `calc()` sub-expression, where neither holds.
  11904. const structural = minify && !path.inValue();
  11905. /** @type {string[]} */
  11906. let parts;
  11907. if (structural && open === "[") {
  11908. parts = _printAttributeSelector(path, path.children(), writer);
  11909. } else {
  11910. // One array, rather than a child list and a mapped copy of it.
  11911. parts = [];
  11912. _appendChildTexts(path.node, writer, parts);
  11913. }
  11914. if (structural && open === "(" && _inMediaConditionPrelude) {
  11915. _lowercaseConditionParts(parts);
  11916. if (_rangeSpellingAllowed) _useRangeSpelling(parts);
  11917. }
  11918. let trim =
  11919. minify && _mathFunctionDepth !== 0 ? _TRIM_MATH : _TRIM_NOTHING;
  11920. if (structural && open === "(") {
  11921. // `@scope`'s parentheses hold selector lists, so a `:` there starts a
  11922. // pseudo-class and the whitespace before it is a descendant combinator
  11923. // — `@scope (div :hover)` is not `@scope (div:hover)`.
  11924. trim = _inScopePrelude(path) ? _TRIM_COMBINATORS : _TRIM_CONDITIONS;
  11925. if (trim === _TRIM_COMBINATORS) _foldPseudoNames(parts);
  11926. }
  11927. const inner = _join(parts, !minify, trim);
  11928. return `${open}${inner}${close}`;
  11929. }
  11930. case T_DECLARATION: {
  11931. const name = path.name();
  11932. // Counted rather than listed: a declaration's value is one component 92
  11933. // times in 100, and the list would be built only to be indexed twice.
  11934. const count = path.childCount();
  11935. // `@property`'s `initial-value` is read against the sibling `syntax`
  11936. // descriptor, so it is as opaque as a custom property's own value.
  11937. const custom =
  11938. name.startsWith("--") ||
  11939. (_inPropertyRule && equalsLowerCase(name, "initial-value")) ||
  11940. // `@function`'s `result` is the token stream the call substitutes, so
  11941. // it is opaque the same way — and empty when the function returns the
  11942. // guaranteed-invalid value.
  11943. (_inFunctionRule && equalsLowerCase(name, "result")) ||
  11944. // A `{}` block standing as the whole value is a token stream the
  11945. // engine holds as written too — no grammar reads it, so a rewrite of
  11946. // it builds a different CSSOM from the same document.
  11947. (count === 1 &&
  11948. path.type(path.childAt(path.node, 0)) === T_SIMPLE_BLOCK &&
  11949. path.blockToken(path.childAt(path.node, 0)) === "{");
  11950. // A value-less declaration is invalid, so it is already ignored — except
  11951. // on a custom property, where the empty value is the guaranteed-invalid
  11952. // one a `var()` fallback reads.
  11953. if (minify && _transforms.removeDeadRules && count === 0 && !custom) {
  11954. return "";
  11955. }
  11956. // The name folded to lowercase, where the value below needed it; `name`
  11957. // itself where it did not, which is what the printed name reads as
  11958. // "nothing to fold".
  11959. let lowered = name;
  11960. let value = "";
  11961. if (count !== 0) {
  11962. // Property names match ASCII case-insensitively; a custom property's
  11963. // does not, and skipping it also skips the lowercasing.
  11964. // One fold of the name for both jobs: which value rules apply, and how
  11965. // the name is printed. `toLowerCaseIfNeeded` hands back the string it
  11966. // was given where nothing changes, so an already-lowercase name says
  11967. // so by identity and needs no second walk.
  11968. lowered = custom ? name : toLowerCaseIfNeeded(name);
  11969. const property = custom ? name : _standardSpelling(lowered);
  11970. if (custom) {
  11971. // `getPropertyValue()` hands this text back, so a rewritten token
  11972. // would be a different CSSOM; only the boundaries between them go.
  11973. const from = path.start(path.childAt(path.node, 0));
  11974. const to = path.end(path.childAt(path.node, count - 1));
  11975. if (minify) {
  11976. value = _customPropertyValue(
  11977. path,
  11978. path.children(),
  11979. writer,
  11980. from,
  11981. to
  11982. );
  11983. } else {
  11984. // Straight from source, so the kept comments in it are already
  11985. // there — claim them, or the writer emits them a second time
  11986. // ahead of the next top-level node.
  11987. writer.takeInserts(from, to);
  11988. value = _input.slice(from, to);
  11989. }
  11990. } else if (property === "unicode-range") {
  11991. // `U+…` tokenizes as numbers, so the generic numeric normalization
  11992. // would corrupt it; each range is shortened as the urange it is.
  11993. let raw = "";
  11994. for (let at = 0; at < count; at++) {
  11995. raw += path.source(path.childAt(path.node, at));
  11996. }
  11997. value = minify
  11998. ? raw
  11999. .split(",")
  12000. .map((one) => _minifyUnicodeRange(one.trim()))
  12001. .join(",")
  12002. : raw;
  12003. } else {
  12004. // Value hashes were already shortened by the hash printer (they print
  12005. // in a value context); just join the children's printed text.
  12006. // A shorthand whose value repeats what the notation already implies
  12007. // collapses; anything else prints as its children joined.
  12008. const shorthand = minify
  12009. ? _collapseShorthand(path, property, path.node, writer)
  12010. : null;
  12011. if (shorthand === null && count === 1) {
  12012. // What a value is 98 times in 100, and `_join` hands a lone
  12013. // fragment straight back — so it is rewritten without an array.
  12014. // §5.4.6 step 7 pops a trailing whitespace token, so a value's
  12015. // lone child is never the one `_join` would answer `""` for.
  12016. value = _valueFragment(
  12017. writer.get(path.childAt(path.node, 0)),
  12018. property,
  12019. minify
  12020. );
  12021. } else {
  12022. /** @type {string[]} */
  12023. let fragments;
  12024. if (shorthand === null) {
  12025. fragments = [];
  12026. _appendChildTexts(path.node, writer, fragments);
  12027. } else {
  12028. fragments = shorthand;
  12029. }
  12030. // In place: a `map` per transform would allocate an array each.
  12031. for (let i = 0; i < fragments.length; i++) {
  12032. fragments[i] = _valueFragment(fragments[i], property, minify);
  12033. }
  12034. // A substituted value is handed back as written, and the string
  12035. // transforms below read space-separated components, so only a
  12036. // value neither covers loses the separators its tokens do not
  12037. // need.
  12038. const separatorsOnly =
  12039. minify &&
  12040. !AUTO_SECOND_VALUE_PROPERTIES.has(property) &&
  12041. !ALPHA_VALUE_PROPERTIES.has(property) &&
  12042. !RATIO_PROPERTIES.has(property) &&
  12043. !_hasSubstitutionInSpan(
  12044. path.start(path.node),
  12045. path.end(path.node)
  12046. );
  12047. value = _join(
  12048. fragments,
  12049. !minify,
  12050. separatorsOnly ? _TRIM_SEPARATORS : _TRIM_NOTHING
  12051. );
  12052. }
  12053. if (minify) {
  12054. value = _dropDefaultSecondValue(property, value);
  12055. value = _numberAlphaValue(property, value);
  12056. value = _dropRatioDenominator(property, value);
  12057. }
  12058. }
  12059. }
  12060. const important = path.important() ? `${soft}!important` : "";
  12061. // Property names match ASCII case-insensitively; a custom property's
  12062. // does not, which is what `custom` already stands for — nor does an
  12063. // `@font-feature-values` sub-rule's, which names a feature value.
  12064. const printedName =
  12065. minify && !custom && !_inFeatureValuesRule && lowered !== name
  12066. ? asciiLowerCaseName(name)
  12067. : name;
  12068. return `${printedName}:${soft}${value}${important};`;
  12069. }
  12070. case T_AT_RULE:
  12071. case T_QUALIFIED_RULE: {
  12072. // Prefixing reads the prelude's tokens, not its joined text.
  12073. const preludeParts = _prefixingOn
  12074. ? /** @type {string[]} */ ([])
  12075. : undefined;
  12076. const prelude = _rulePrelude(path, writer, minify, preludeParts);
  12077. const decls = path.declarations();
  12078. // A qualified rule always has a block; an at-rule has one only when its
  12079. // declaration list is non-null — else it is `@…;`.
  12080. if (decls === null) {
  12081. if (minify) _blockSpans.push(_NO_BLOCK_ENTRY);
  12082. return `${prelude};`;
  12083. }
  12084. const rules = path.childRules();
  12085. // `nl` prefixes each item: nothing minifying, a line break beautifying
  12086. // (declarations end in `;`, rules don't).
  12087. const nl = minify ? "" : "\n";
  12088. const {
  12089. body,
  12090. items,
  12091. texts,
  12092. superseded,
  12093. droppedPrefix,
  12094. addedPrefix,
  12095. deadPrefixed,
  12096. spans
  12097. } = _composeBlockBody(
  12098. path,
  12099. decls,
  12100. rules,
  12101. writer,
  12102. minify,
  12103. nl,
  12104. _currentNode
  12105. );
  12106. if (
  12107. minify && // An empty rule paints nothing, so dropping it leaves the cascade as it
  12108. // was — but only where the block itself carries no meaning (see
  12109. // `DROPPABLE_WHEN_EMPTY_AT_RULES`).
  12110. body.length === 0 &&
  12111. // See `_streamClose`: a rule taken out while a `@namespace` after it
  12112. // could still be read would move one up into a live position.
  12113. !(path.parent === null && _namespacePrologueOpen) &&
  12114. (path.type() === T_QUALIFIED_RULE ||
  12115. DROPPABLE_WHEN_EMPTY_AT_RULES.has(path.name().toLowerCase()))
  12116. ) {
  12117. _blockSpans.push(_NO_BLOCK_ENTRY);
  12118. return "";
  12119. }
  12120. const text = `${prelude}${soft}{${body}${nl}}`;
  12121. // Prefix copies turn one rule into several, so a rewritten rule is not
  12122. // offered for joining — its text no longer describes a single block.
  12123. // Each block's rules are their own siblings, so a nested `@keyframes` or
  12124. // pseudo pairs with the twin in its own scope and never one outside it.
  12125. // Declarations are prefixed above.
  12126. let out = text;
  12127. if (_prefixingOn) {
  12128. // Read and clear here, so the lookahead is the one rule the writer
  12129. // holds — which only a top-level rule ever is — and never a rule
  12130. // further back.
  12131. const parent = path.parent;
  12132. const top = parent === null;
  12133. const scope = _prefixScope(parent);
  12134. out =
  12135. path.type() === T_AT_RULE
  12136. ? _prefixAtRule(path, text, prelude, scope, top)
  12137. : _prefixQualifiedRule(
  12138. text,
  12139. /** @type {string[]} */ (preludeParts),
  12140. soft,
  12141. body,
  12142. scope,
  12143. top
  12144. );
  12145. }
  12146. // Pushed whatever it holds: the parent takes one off for every child
  12147. // with a body, so a block that carries no rule still has to be there.
  12148. // A rewritten rule records no nesting — those spans name `text`, not
  12149. // `out` — but it is still the one rule its own text says it is.
  12150. // Only a block that recorded something needs an entry of its own; a leaf
  12151. // rule is the shared one. Nothing is pushed at all while not minifying,
  12152. // where nothing reads them.
  12153. if (minify) {
  12154. _blockSpans.push(
  12155. spans === null || out !== text
  12156. ? path.type() === T_QUALIFIED_RULE
  12157. ? _NO_BLOCK_ENTRY_QUALIFIED
  12158. : _NO_BLOCK_ENTRY
  12159. : {
  12160. bodyAt: prelude.length + soft.length + 1,
  12161. prelude,
  12162. keyPrelude: _openerKey(`${prelude}{`),
  12163. qualified: path.type() === T_QUALIFIED_RULE,
  12164. spans
  12165. }
  12166. );
  12167. }
  12168. if (out !== text) return out;
  12169. // Where the block is what a sibling is compared against, remember the
  12170. // rule so the parent parts it without re-scanning for the `{`. An
  12171. // at-rule remembers the entries its block is made of too, so a sibling
  12172. // block's rules can join the ones they come to stand beside.
  12173. if (
  12174. minify &&
  12175. path.type() === T_AT_RULE &&
  12176. MERGEABLE_AT_RULES.has(path.name().toLowerCase())
  12177. ) {
  12178. /** @type {RuleEntry[]} */
  12179. const children = [];
  12180. for (let i = 0; i < texts.length; i++) {
  12181. if (texts[i].length === 0) continue;
  12182. if (superseded !== null && superseded.has(i)) continue;
  12183. // The entries have to spell the block this rule printed, prefixes
  12184. // and all: a join rebuilds the body from them, and one built from
  12185. // the unprefixed texts would put back what was dropped and lose
  12186. // what was added.
  12187. if (droppedPrefix !== null && droppedPrefix.has(i)) continue;
  12188. if (deadPrefixed !== null && deadPrefixed.has(items[i])) continue;
  12189. const added = addedPrefix === null ? undefined : addedPrefix.get(i);
  12190. children.push(
  12191. added === undefined
  12192. ? _ruleEntryOf(items[i], texts[i])
  12193. : _opaqueEntry(added + texts[i])
  12194. );
  12195. }
  12196. let head = "";
  12197. for (let i = 0; i < children.length - 1; i++) head += children[i].text;
  12198. _ruleEntry.set(_currentNode, {
  12199. text,
  12200. prelude: prelude.length,
  12201. atRule: true,
  12202. plain: false,
  12203. listable: LIST_NO,
  12204. listKind: LIST_KIND_SELECTOR,
  12205. children,
  12206. head
  12207. });
  12208. }
  12209. if (minify && path.type() === T_QUALIFIED_RULE) {
  12210. // Only a block of declarations lends its selectors to another's list: a
  12211. // nested rule's `&` stands for the whole list, and `:is(…)` takes the
  12212. // specificity of its most specific selector, so joining the preludes
  12213. // would move what the nested rules beat.
  12214. const plain = rules === null || rules.length === 0;
  12215. const parent = path.parent;
  12216. // Which shape a join would read the prelude with — the parent is gone by
  12217. // then. A keyframe selector is a list of its own shape, which
  12218. // `_rulePrelude` has already rewritten `from` in.
  12219. let listKind = LIST_KIND_SELECTOR;
  12220. if (parent !== null) {
  12221. const parentType = path.type(parent);
  12222. if (parentType === T_QUALIFIED_RULE) {
  12223. listKind = LIST_KIND_NESTED;
  12224. } else if (
  12225. parentType === T_AT_RULE &&
  12226. KEYFRAMES_AT_RULE_RE.test(path.name(parent))
  12227. ) {
  12228. listKind = LIST_KIND_KEYFRAME;
  12229. }
  12230. }
  12231. _ruleEntry.set(_currentNode, {
  12232. text,
  12233. prelude: prelude.length,
  12234. atRule: false,
  12235. plain,
  12236. listable: plain ? LIST_UNKNOWN : LIST_NO,
  12237. listKind,
  12238. children: null,
  12239. head: ""
  12240. });
  12241. }
  12242. return text;
  12243. }
  12244. case T_RAW:
  12245. // Off-spec passthrough (see `NodeType.Raw`). It sits in a declaration
  12246. // list, so it carries the `;` separating it from the next item (stripped
  12247. // again before a `}`).
  12248. // Its parent counts it among its children, so it leaves the entry saying
  12249. // it carries no rule — without one, the rules after it read the next's.
  12250. if (minify) _blockSpans.push(_NO_BLOCK_ENTRY);
  12251. {
  12252. const raw = path.source();
  12253. // A comment the source never closed swallows the separator written
  12254. // after it, so the next parse reads a longer one. §4.3.2, via the parse.
  12255. const open =
  12256. _openCommentStart !== -1 &&
  12257. _input.length - raw.length <= _openCommentStart &&
  12258. _input.endsWith(raw);
  12259. return `${raw}${open ? "*/" : ""};`;
  12260. }
  12261. case T_HASH: {
  12262. const raw = path.source();
  12263. if (!minify) return raw;
  12264. // A hash is a hex color only in a value (`color:#abc`), an id in a selector
  12265. // (`#Abc{}`, `:not(#Abc)`) — ids are case-sensitive, so shorten only the
  12266. // former. Works at any value depth (e.g. inside a gradient). An id still
  12267. // gets its escapes shortened; the `#` is held back so the name's first code
  12268. // point is judged as first.
  12269. if (!path.inValue()) return `#${_minifyIdentEscapes(raw.slice(1))}`;
  12270. // Inside a function, only known color functions take color hashes —
  12271. // and a substitution's fallback, which is not the function's own
  12272. // argument but the property's value, so a hash there is as much a color
  12273. // as one written in place. `paint()` is the exception among them: its
  12274. // arguments reach a worklet rather than a declaration.
  12275. const parent = path.parent;
  12276. if (parent !== null && path.type(parent) === T_FUNCTION) {
  12277. const fn = path.name(parent).toLowerCase();
  12278. if (
  12279. !COLOR_ARGUMENT_FUNCTIONS.has(fn) &&
  12280. !(SUBSTITUTION_FUNCTIONS.has(fn) && fn !== "paint")
  12281. ) {
  12282. return raw;
  12283. }
  12284. }
  12285. const short = _minifyHash(raw);
  12286. return short === null ? raw : short;
  12287. }
  12288. default:
  12289. // Any remaining leaf token (ident, string, url, delim, …) prints verbatim
  12290. // from its source slice.
  12291. return path.source();
  12292. }
  12293. };
  12294. /**
  12295. * The generic visitor coordinator (`util/SourceProcessor`) bound to the CSS
  12296. * `grammar`. All configuration is per `process` call. `process(src, { minimize:
  12297. * true })` returns `{ code, map }` — the safely-minified serialization (built by
  12298. * the same walk that fires visitors) and its source map; without `minimize` it
  12299. * just walks and returns `undefined`. Babel-style usage:
  12300. *
  12301. * ```
  12302. * new SourceProcessor().use({ [NodeType.AtRule]: (path) => {} }).process(source, { skip });
  12303. * ```
  12304. * @experimental exposed as `webpack.css.syntax.SourceProcessor`; unstable API
  12305. * @extends {GenericSourceProcessor<CssPath, Node, CssProcessOptions>}
  12306. */
  12307. class SourceProcessor extends GenericSourceProcessor {
  12308. constructor() {
  12309. super(grammar, printer);
  12310. }
  12311. }
  12312. /**
  12313. * Build a `SkipOptions.types` set (drop these component-value node types from
  12314. * value / function-arg lists) from a list of `NodeType`s. Preludes are separate
  12315. * (`SkipOptions.selectorPrelude` / `atRulePrelude`). The caller owns the safety
  12316. * contract: only pass types nothing reads in the intended parse. Two
  12317. * grammar-internal caveats beyond consumer needs: dropping both `Delim` and
  12318. * `Ident` loses `!important` detection, and dropping `SimpleBlock` loses the
  12319. * custom-property `{}`-value check (and its subtree). Precompute once per
  12320. * configuration and reuse across parses.
  12321. * @param {number[]} nodeTypes component-value node types to drop
  12322. * @returns {Uint8Array} skip-types set indexed by `NodeType`
  12323. */
  12324. const buildSkipSet = (nodeTypes) => {
  12325. const set = new Uint8Array(32);
  12326. for (let i = 0; i < nodeTypes.length; i++) set[nodeTypes[i]] = 1;
  12327. return set;
  12328. };
  12329. /* eslint-disable jsdoc/require-template -- `A` below is the accessor const, not a type parameter */
  12330. /**
  12331. * The CSS path (Babel's `path` shape): the AST accessor with the walk's
  12332. * current position on it — the single argument every visitor receives.
  12333. * @typedef {typeof A} CssPath
  12334. */
  12335. /* eslint-enable jsdoc/require-template */
  12336. /**
  12337. * Append the printed text of every child of `n` to `out`, in order — read where
  12338. * the child list lies rather than materialized into a list of its own first.
  12339. * @param {Node} n the node
  12340. * @param {PrintContext} writer the print context (children's printed text)
  12341. * @param {string[]} out the array to append to
  12342. */
  12343. const _appendChildTexts = (n, writer, out) => {
  12344. const i = _nodeIndex(n);
  12345. const start = _listStarts[i];
  12346. const len = _listLens[i];
  12347. for (let k = 0; k < len; k++) {
  12348. out.push(writer.get(_nodeRef(_flat[start + k])));
  12349. }
  12350. };
  12351. // A fresh (safely retainable) array view of a node's flat content span —
  12352. // visitors that read `A.children` / `A.prelude` may keep the result.
  12353. /** @type {(n: Node) => Node[]} */
  12354. const _materializeList = (n) => {
  12355. const i = _nodeIndex(n);
  12356. const start = _listStarts[i];
  12357. const len = _listLens[i];
  12358. /** @type {Node[]} */
  12359. const out = [];
  12360. for (let k = 0; k < len; k++) out.push(_nodeRef(_flat[start + k]));
  12361. return out;
  12362. };
  12363. // Babel's `path.skip()`, children-only: set by `A.skipChildren()` during an
  12364. // `enter` dispatch, consumed by the walk.
  12365. let _walkSkip = false;
  12366. // The walk's current position (`A.node` / `A.parent` read these; module-level
  12367. // so the accessor methods' defaults avoid self-referential `this` typing).
  12368. /** @type {Node} */
  12369. let _currentNode = /** @type {Node} */ (/** @type {unknown} */ (0));
  12370. /** @type {Node | null} */
  12371. let _currentParent = null;
  12372. // Index of the current node within its sibling list (a rule body's declarations
  12373. // and child rules are separate lists, so each indexes from 0 independently).
  12374. let _currentIndex = 0;
  12375. // Set by the walk while inside a declaration's value (`A.inValue()`): a hash there
  12376. // is a color, elsewhere (a selector prelude) it is an id — the color-safety seam.
  12377. let _inValue = false;
  12378. // The declaration `_inKeywordOnlyValue` last answered for, and its answer: one
  12379. // value's identifiers all ask it, and the read is a pointer compare after the
  12380. // first. Cleared with the rest of the parse state, so no node outlives it.
  12381. /** @type {Node | null} */
  12382. let _keywordOnlyFor = null;
  12383. let _keywordOnly = false;
  12384. // Set by the walk while inside a `@supports` prelude: the declaration there is
  12385. // the subject of a feature test, so a value rewrite would change the question.
  12386. let _inSupportsPrelude = false;
  12387. // Set by the walk while inside a `@media` / `@container` prelude, where a `(…)`
  12388. // is a media feature rather than a declaration or a selector list.
  12389. let _inMediaConditionPrelude = false;
  12390. // Set by the walk while inside an `@property` body, where `initial-value` is
  12391. // typed by the sibling `syntax` descriptor rather than by any grammar webpack
  12392. // reads — so no rewrite of it can be known to still match.
  12393. let _inPropertyRule = false;
  12394. // Set by the walk while inside an `@function` body, where `result` carries the
  12395. // token stream the call substitutes — as opaque as a custom property's value,
  12396. // and empty on purpose when the function returns the guaranteed-invalid one.
  12397. let _inFunctionRule = false;
  12398. // Set by the walk while inside an `@font-feature-values` body, where each
  12399. // sub-rule's declaration names are `<custom-ident>` feature values — so they are
  12400. // case-sensitive, and two spellings of one name are two distinct entries.
  12401. let _inFeatureValuesRule = false;
  12402. // Set by the walk while inside a custom property's value. Its tokens are
  12403. // squeezed, but a rewrite that restates a value in another notation is held
  12404. // back: `getPropertyValue()` hands this text back, so a color stays as written.
  12405. let _inCustomProperty = false;
  12406. // Off, that verbatim rule stands; on, a custom property's tokens print like any
  12407. // other value's and `getPropertyValue()` hands back the rewritten text.
  12408. let _rewriteCustomProperties = false;
  12409. let _inSubstitutedValue = false;
  12410. // Whether the value being printed sits inside a gradient. A stop in a space
  12411. // other than sRGB is echoed there rather than computed, and where the gradient
  12412. // names the space its stops interpolate in, converting one through sRGB maps it
  12413. // back as a different point — so the ramp is not the one the source paints.
  12414. let _inGradient = false;
  12415. // How many math functions the walk is inside. Non-zero is where `*` and `/` are
  12416. // operators that need no whitespace rather than value separators; above one is
  12417. // where a folded term is an operand of an outer expression rather than a value.
  12418. let _mathFunctionDepth = 0;
  12419. // How many stepped-value functions the walk is inside. Everything below one
  12420. // keeps the unit it was written with, folds included.
  12421. let _steppedFunctionDepth = 0;
  12422. // Whether a length may be rewritten into a shorter unit it is exactly equal in.
  12423. // Off by default: the rewrite is sound — CSS Values 4 fixes the absolute units
  12424. // against each other, so `1pc` is `16px` on every medium — but it fires ~10
  12425. // times in all of Bootstrap and costs bytes as often as it saves them once the
  12426. // asset is compressed. `cssnano` disables the same rewrite in its default
  12427. // preset. Time (`ms` <-> `s`) is not gated: it is uncontested and every
  12428. // minifier does it.
  12429. let _convertLengthUnits = false;
  12430. // Which rewrites this print makes. One object rather than a flag apiece: it is
  12431. // read straight off the options, and a fixed shape keeps each read monomorphic.
  12432. /** @type {Required<CssTransformOptions>} */
  12433. let _transforms = _DEFAULT_TRANSFORMS;
  12434. // Which comments this print keeps, with a pattern already compiled to the
  12435. // predicate it stands for — `true` every one, `false` none, `"some"` the ones
  12436. // that carry something (see `_isKeptComment`).
  12437. /** @type {boolean | "some" | ((comment: string) => boolean)} */
  12438. let _commentsKept = "some";
  12439. /** @type {Map<string, [string, number]>} */
  12440. let _unitScale = ABSOLUTE_UNIT_SCALE;
  12441. // Where a `block-contents` print holds its top-level nodes instead of emitting
  12442. // them one by one: that production is a declaration list, and the list-wide
  12443. // transforms need the whole list. Null for a stylesheet, which streams.
  12444. /** @type {(Rule | Declaration)[] | null} */
  12445. let _blockContentsNodes = null;
  12446. // The caller's renderer for source this stylesheet embeds, set per print.
  12447. /** @type {EmbeddedSourceRenderer | undefined} */
  12448. let _renderEmbeddedSource;
  12449. // Where an asynchronous caller's embedded sources are recorded instead of
  12450. // rendered, set per print. Each entry carries the text to print once the answer
  12451. // is in, so the `url()` around it is spelled from what it actually holds.
  12452. /** @type {DeferredEmbeddedSource[] | undefined} */
  12453. let _deferEmbeddedSource;
  12454. const _CSS_STRING_ESCAPE_RE = /[\\\n]/g;
  12455. /**
  12456. * Spell a rebuilt URL so it parses back to itself: as a url-token when it can
  12457. * be one, otherwise quoted with the delimiter and its escapes written out.
  12458. * @param {string} url the URL to spell
  12459. * @param {string} mark the quote to use when it needs one
  12460. * @returns {string} the `url()` argument
  12461. */
  12462. const _serializeUrl = (url, mark) => {
  12463. // Both spellings below the quoted one are the unquoting `transforms.quotes`
  12464. // names, so off they are not this printer's to pick: a rendered payload has
  12465. // no authored quoting left to keep, and the quotes carry any of it.
  12466. if (_transforms.normalizeQuotes) {
  12467. if (!_UNQUOTABLE_URL_RE.test(url)) return url;
  12468. const escaped = _escapeUrlBody(url);
  12469. if (escaped !== null) return escaped;
  12470. }
  12471. return (
  12472. mark +
  12473. url.replace(_CSS_STRING_ESCAPE_RE, "\\$&").split(mark).join(`\\${mark}`) +
  12474. mark
  12475. );
  12476. };
  12477. /**
  12478. * Record a url's `data:` payload for an asynchronous caller and print the
  12479. * marker standing in for it, or `null` when there is nothing to offer. `build`
  12480. * spells the whole `url()` from the answer, so its quoting is decided by what
  12481. * the payload turns out to be rather than by what it was.
  12482. * @param {string} body the url body, unquoted and unescaped
  12483. * @param {(url: string) => string} build the `url()` text around a rebuilt URL
  12484. * @param {string} fallback the text to print when the caller declines
  12485. * @returns {string | null} the marker to print, or null when nothing is offered
  12486. */
  12487. const _deferDataUrl = (body, build, fallback) => {
  12488. const read = _readDataUrl(body);
  12489. if (read === null) return null;
  12490. const holes = /** @type {DeferredEmbeddedSource[]} */ (_deferEmbeddedSource);
  12491. const id = holes.length;
  12492. holes.push({
  12493. type: read.type,
  12494. hostType: CSS_TYPE,
  12495. source: read.payload,
  12496. build: (rendered) =>
  12497. typeof rendered !== "string" || rendered === read.payload
  12498. ? fallback
  12499. : build(buildDataURI(read.parsed, rendered))
  12500. });
  12501. return deferredWrite(id);
  12502. };
  12503. /**
  12504. * Read a url's `data:` payload out, for a caller that will render it. `null`
  12505. * when there is nothing to offer — not a data URL, a media type naming no
  12506. * language webpack knows, or a payload that would not round-trip.
  12507. * @param {string} url the url body, unquoted and unescaped
  12508. * @returns {{ parsed: import("../util/dataURL").ParsedDataURI, type: string, payload: string } | null} what it holds, or null
  12509. */
  12510. const _readDataUrl = (url) => {
  12511. // Char-code gate so the dominant non-`data:` url costs one read.
  12512. if ((url.charCodeAt(0) | 0x20) !== CC_LOWER_D) return null;
  12513. // A CSS escape is still written here — the quoted form is percent-decoded,
  12514. // not unescaped — so the payload would be read with its backslashes in it.
  12515. if (url.includes("\\")) return null;
  12516. const parsed = parseDataURI(url);
  12517. if (parsed === null) return null;
  12518. const type = languageOfMediaType(parsed.mediaType);
  12519. if (type === undefined) return null;
  12520. const payload = decodeDataURIPayload(parsed);
  12521. if (payload === null || payload === "") return null;
  12522. return { parsed, type, payload };
  12523. };
  12524. /**
  12525. * Offer a url's `data:` payload to the renderer, and rebuild the URL around
  12526. * what comes back. `null` when nothing should change — not a data URL, a media
  12527. * type naming no language webpack knows, a payload that would not round-trip,
  12528. * or a renderer that declined.
  12529. * @param {string} url the url body, unquoted and unescaped
  12530. * @returns {string | null} the rebuilt URL, or null to emit the original
  12531. */
  12532. const _renderDataUrl = (url) => {
  12533. if (_renderEmbeddedSource === undefined) return null;
  12534. const read = _readDataUrl(url);
  12535. if (read === null) return null;
  12536. let rendered;
  12537. try {
  12538. rendered = _renderEmbeddedSource(read.payload, {
  12539. type: read.type,
  12540. hostType: CSS_TYPE
  12541. });
  12542. } catch (_err) {
  12543. return null;
  12544. }
  12545. // Anything but text is a renderer that did not answer, not a payload to write.
  12546. if (typeof rendered !== "string" || rendered === read.payload) return null;
  12547. return buildDataURI(read.parsed, rendered);
  12548. };
  12549. // What the target can read, from `output.environment` — resolved once per run
  12550. // rather than per color. Unset means it can: only browserslist reports otherwise.
  12551. let _hexAlphaAllowed = true;
  12552. let _doublePositionAllowed = true;
  12553. // CSS Position 3 added `inset` long after the four longhands it merges, CSS 1.
  12554. let _insetShorthandAllowed = true;
  12555. let _rangeSpellingAllowed = true;
  12556. // CSS Box Alignment 3's `place-*`, newer than the `align-*` / `justify-*` pairs.
  12557. let _placeShorthandAllowed = true;
  12558. // Set when the source names one at all, so a stylesheet without any pays a
  12559. // single scan and keeps nothing.
  12560. // Could name `@namespace`: the spelling, or any escaped at-keyword only decoding
  12561. // tells apart. Over-wide on purpose — a false positive keeps an empty rule.
  12562. const NAMESPACE_AT_RULE_RE = /@namespace|@[\w-]*\\/i;
  12563. // Whether a top-level `@namespace` could still be read here. Only a rule the
  12564. // engine keeps closes the run one may stand in (CSS Namespaces 3 §3.1) — an
  12565. // unknown at-rule is thrown away and does not — so only a qualified rule, which
  12566. // every engine keeps, closes it. Empty or not, a rule dropped while this is open
  12567. // would carry a dead `@namespace` after it back to the head and bring it to life.
  12568. let _namespacePrologueOpen = false;
  12569. // Whether the target reads `overflow`'s two-value form (`overflow:auto hidden`),
  12570. // which is newer than the longhands it merges.
  12571. let _overflowTwoValuesAllowed = true;
  12572. // The declaration whose value is being walked, for the printer's property-scoped
  12573. // guards; only tracked while printing, and read lazily (naming it costs a slice).
  12574. /** @type {Node | null} */
  12575. let _valueDeclaration = null;
  12576. // AST field-access seam. Every AST-node field read by `CssParser` goes through
  12577. // one of these accessors (`n` is an integer node id into the columns), so
  12578. // the storage stays behind the accessor without any consumer edit. `value` is
  12579. // the leaf-token string; container child lists are `children` / `prelude` /
  12580. // `declarations` / `childRules`.
  12581. const A = {
  12582. // === path position (rebound by the walk before every visitor call) ===
  12583. /**
  12584. * @returns {Node} current node — only valid during a visitor callback
  12585. */
  12586. get node() {
  12587. return _currentNode;
  12588. },
  12589. /**
  12590. * @returns {Node | null} enclosing node (null = a top-level node)
  12591. */
  12592. get parent() {
  12593. return _currentParent;
  12594. },
  12595. /**
  12596. * @returns {number} index of the current node within its sibling list (0 for a top-level node) — only valid during a visitor callback
  12597. */
  12598. get index() {
  12599. return _currentIndex;
  12600. },
  12601. /** Stop the walk descending into the current node (enter only). */
  12602. skipChildren() {
  12603. _walkSkip = true;
  12604. },
  12605. /**
  12606. * @returns {boolean} true when the current node is inside a declaration's value
  12607. * (a hash there is a color); false in a selector prelude (a hash is an id)
  12608. */
  12609. inValue() {
  12610. return _inValue;
  12611. },
  12612. // === field reads — `n` defaults to the current node ===
  12613. /**
  12614. * @param {Node=} n node
  12615. * @returns {number} node type
  12616. */
  12617. type(n = _currentNode) {
  12618. return _types[_nodeIndex(n)];
  12619. },
  12620. /**
  12621. * @param {Node=} n node
  12622. * @returns {number} start offset
  12623. */
  12624. start(n = _currentNode) {
  12625. return _starts[_nodeIndex(n)];
  12626. },
  12627. /**
  12628. * @param {Node=} n node
  12629. * @returns {number} end offset
  12630. */
  12631. end(n = _currentNode) {
  12632. return _ends[_nodeIndex(n)];
  12633. },
  12634. /**
  12635. * @param {Node=} n node
  12636. * @returns {[number, number]} start / end offsets
  12637. */
  12638. range(n = _currentNode) {
  12639. const i = _nodeIndex(n);
  12640. return [_starts[i], _ends[i]];
  12641. },
  12642. /**
  12643. * @param {Node=} n node
  12644. * @returns {{ start: { line: number, column: number }, end: { line: number, column: number } }} source location
  12645. */
  12646. loc(n = _currentNode) {
  12647. const i = _nodeIndex(n);
  12648. const lc = _locConverter;
  12649. const s = lc.get(_starts[i]);
  12650. const sl = s.line;
  12651. const sc = s.column;
  12652. const e = lc.get(_ends[i]);
  12653. return {
  12654. start: { line: sl, column: sc },
  12655. end: { line: e.line, column: e.column }
  12656. };
  12657. },
  12658. /**
  12659. * @param {Node=} n node
  12660. * @returns {string} the node's source slice — as written, but for an escape
  12661. * the input ran out of, which is the character it names rather than the `\`
  12662. * that spells it (§4.3.5 inside a string, §4.3.7 anywhere else)
  12663. */
  12664. source(n = _currentNode) {
  12665. const i = _nodeIndex(n);
  12666. const end = _ends[i];
  12667. const text = _input.slice(_starts[i], end);
  12668. if (end !== _input.length || !_endsInLoneEscape(text)) return text;
  12669. // The escape the input ran out of names nothing inside a string (§4.3.5)
  12670. // and the replacement character anywhere else (§4.3.7) — either way not
  12671. // the `\` the source shows, which would escape what the printer adds next.
  12672. return _types[i] === T_STRING
  12673. ? text.slice(0, -1)
  12674. : `${text.slice(0, -1)}\uFFFD`;
  12675. },
  12676. /**
  12677. * @param {Node=} n node
  12678. * @returns {string} raw token value
  12679. */
  12680. value(n = _currentNode) {
  12681. return _valueOf(_nodeIndex(n));
  12682. },
  12683. /**
  12684. * @param {Node=} n node
  12685. * @returns {string} unescaped token value
  12686. */
  12687. unescaped(n = _currentNode) {
  12688. const i = _nodeIndex(n);
  12689. const v = _valueOf(i);
  12690. if (_types[i] !== T_STRING) return unescapeIdentifier(v);
  12691. // §4.3.5 returns the token at end of input too, and that one has no closing
  12692. // quote to take off — nor does one an odd run of backslashes escaped.
  12693. let end = v.length;
  12694. if (end > 1 && v.charCodeAt(end - 1) === v.charCodeAt(0)) {
  12695. let slashes = 0;
  12696. while (end - 2 - slashes >= 1 && v.charCodeAt(end - 2 - slashes) === 92) {
  12697. slashes++;
  12698. }
  12699. if (slashes % 2 === 0) end--;
  12700. }
  12701. return unescapeIdentifier(v.slice(1, end));
  12702. },
  12703. /**
  12704. * @param {Node=} n node
  12705. * @returns {string} hash / numeric type flag
  12706. */
  12707. typeFlag(n = _currentNode) {
  12708. const i = _nodeIndex(n);
  12709. if (_types[i] === T_HASH) {
  12710. const input = _input;
  12711. const p = _starts[i] + 1;
  12712. return _ifThreeCodePointsWouldStartAnIdentSequence(
  12713. input,
  12714. p,
  12715. input.charCodeAt(p),
  12716. input.charCodeAt(p + 1),
  12717. input.charCodeAt(p + 2)
  12718. )
  12719. ? "id"
  12720. : "unrestricted";
  12721. }
  12722. const v = _valueOf(i);
  12723. return _typeFlagOf(
  12724. _types[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
  12725. );
  12726. },
  12727. /**
  12728. * @param {Node=} n node
  12729. * @returns {number} url content start offset
  12730. */
  12731. contentStart(n = _currentNode) {
  12732. return _aux0[_nodeIndex(n)];
  12733. },
  12734. /**
  12735. * @param {Node=} n node
  12736. * @returns {number} url content end offset
  12737. */
  12738. contentEnd(n = _currentNode) {
  12739. return _aux1[_nodeIndex(n)];
  12740. },
  12741. /**
  12742. * @param {Node=} n node
  12743. * @returns {string} rule / declaration / function name
  12744. */
  12745. name(n = _currentNode) {
  12746. const i = _nodeIndex(n);
  12747. return _types[i] === T_AT_RULE
  12748. ? _input.slice(_starts[i] + 1, _aux0[i])
  12749. : _input.slice(_starts[i], _aux0[i]);
  12750. },
  12751. /**
  12752. * @param {Node=} n node
  12753. * @returns {number} name start offset
  12754. */
  12755. nameStart(n = _currentNode) {
  12756. return _starts[_nodeIndex(n)];
  12757. },
  12758. /**
  12759. * @param {Node=} n node
  12760. * @returns {number} name end offset
  12761. */
  12762. nameEnd(n = _currentNode) {
  12763. return _aux0[_nodeIndex(n)];
  12764. },
  12765. /**
  12766. * @param {Node=} n node
  12767. * @returns {string} unescaped name
  12768. */
  12769. unescapedName(n = _currentNode) {
  12770. return unescapeIdentifier(A.name(n));
  12771. },
  12772. /**
  12773. * @param {Node=} n node
  12774. * @returns {ComponentValue[]} function / block children
  12775. */
  12776. children(n = _currentNode) {
  12777. return /** @type {ComponentValue[]} */ (_materializeList(n));
  12778. },
  12779. /**
  12780. * @param {Node=} n node
  12781. * @returns {ComponentValue[]} rule prelude
  12782. */
  12783. prelude(n = _currentNode) {
  12784. return /** @type {ComponentValue[]} */ (_materializeList(n));
  12785. },
  12786. /**
  12787. * @param {Node=} n node
  12788. * @returns {number} number of children (value / prelude) without materializing the list
  12789. */
  12790. childCount(n = _currentNode) {
  12791. return _listLens[_nodeIndex(n)];
  12792. },
  12793. /**
  12794. * @param {Node} n node
  12795. * @param {number} i child index (`0 <= i < childCount(n)`)
  12796. * @returns {ComponentValue} i-th child (value / prelude) without materializing the list
  12797. */
  12798. childAt(n, i) {
  12799. return /** @type {ComponentValue} */ (
  12800. _nodeRef(_flat[_listStarts[_nodeIndex(n)] + i])
  12801. );
  12802. },
  12803. /**
  12804. * A block big enough to stream hands its children to the visitors as each one
  12805. * finishes rather than collecting them, so both lists read as an empty block
  12806. * on it — `null`, which means no block at all, is still only for the `@…;`
  12807. * forms. Read a block's children from the walk, not from here.
  12808. * @param {Node=} n node
  12809. * @returns {Declaration[] | null} block declarations
  12810. */
  12811. declarations(n = _currentNode) {
  12812. // `_makeContainer` only populates the body slot for rules; 0 means no
  12813. // block, normalized to `null` to keep the documented contract.
  12814. const bi = _bodyIdx[_nodeIndex(n)];
  12815. return bi === 0 ? null : /** @type {Declaration[]} */ (_declBodies[bi - 1]);
  12816. },
  12817. /**
  12818. * Reads as an empty block on a streamed rule; see {@link declarations}.
  12819. * @param {Node=} n node
  12820. * @returns {Rule[] | null} block child rules
  12821. */
  12822. childRules(n = _currentNode) {
  12823. const bi = _bodyIdx[_nodeIndex(n)];
  12824. return bi === 0 ? null : /** @type {Rule[]} */ (_ruleBodies[bi - 1]);
  12825. },
  12826. /**
  12827. * @param {Node=} n node
  12828. * @returns {number} block start offset
  12829. */
  12830. blockStart(n = _currentNode) {
  12831. return _aux1[_nodeIndex(n)];
  12832. },
  12833. /**
  12834. * @param {Node=} n node
  12835. * @returns {number} block end offset
  12836. */
  12837. blockEnd(n = _currentNode) {
  12838. const i = _nodeIndex(n);
  12839. return _aux1[i] !== -1 ? _ends[i] : -1;
  12840. },
  12841. /**
  12842. * @param {Node=} n node
  12843. * @returns {boolean} `!important` flag
  12844. */
  12845. important(n = _currentNode) {
  12846. return (_flags[_nodeIndex(n)] & 1) !== 0;
  12847. },
  12848. /**
  12849. * @param {Node=} n node
  12850. * @returns {SimpleBlockToken} block opening token
  12851. */
  12852. blockToken(n = _currentNode) {
  12853. return /** @type {SimpleBlockToken} */ (_input[_starts[_nodeIndex(n)]]);
  12854. },
  12855. // Writers — `CssParser` rewrites a rule's end / block-end when it folds an
  12856. // inline ICSS `:import` / `:export` body into a single dependency. A block
  12857. // rule's `blockEnd` is its `end`, so `setBlockEnd` writes the same `end` slot.
  12858. /**
  12859. * @param {Node} n node
  12860. * @param {number} v new end offset
  12861. */
  12862. setEnd(n, v) {
  12863. _ends[_nodeIndex(n)] = v;
  12864. },
  12865. /**
  12866. * @param {Node} n node
  12867. * @param {number} v new block end offset
  12868. */
  12869. setBlockEnd(n, v) {
  12870. _ends[_nodeIndex(n)] = v;
  12871. }
  12872. };
  12873. // The AST node shapes (`Node`, `Token`, and the container typedefs) are types
  12874. // only — nodes are integer ids into the store, surfaced by the `A` visitor
  12875. // accessor and the `parseA*` readers. Runtime exports: the `A` accessor, the
  12876. // full CSS-Syntax-3 §5.3 `parseA*` entry-point surface, the `TokenStream` (so
  12877. // callers can pass a pre-built stream to any `parseA*`), and the `escape` /
  12878. // `unescapeIdentifier` string utils.
  12879. /**
  12880. * Note where one child of a block lands in it. A qualified rule is one rule
  12881. * whatever it nests, so it is a span of its own; an at-rule is a condition its
  12882. * body is read under, so its own spans move in, its prelude joining their keys.
  12883. * @param {RuleSpan[]} spans the block's spans, added to
  12884. * @param {Node} item the child
  12885. * @param {string} text the child's printed text
  12886. * @param {number} at where the text lands in the block's body
  12887. * @param {CssPath} path the accessor
  12888. * @param {BlockSpans=} inner what the child recorded, when it has a body
  12889. * @returns {void}
  12890. */
  12891. const _collectRuleSpans = (spans, item, text, at, path, inner) => {
  12892. // A qualified rule is one rule, and each rule it nests is another read under
  12893. // it — the parent first, so a cut that takes the whole rule is seen before
  12894. // anything inside it.
  12895. if (path.type(item) === T_QUALIFIED_RULE) {
  12896. spans.push({ scope: _rootScope(), key: text, at, len: text.length });
  12897. }
  12898. if (inner === undefined) return;
  12899. const base = at + inner.bodyAt;
  12900. for (const span of inner.spans) {
  12901. spans.push({
  12902. scope: _enclosingRuleScope(span.scope, inner.keyPrelude),
  12903. key: span.key,
  12904. at: base + span.at,
  12905. len: span.len
  12906. });
  12907. }
  12908. };
  12909. module.exports.A = A;
  12910. module.exports.EMBEDDED_LANGUAGES = EMBEDDED_LANGUAGES;
  12911. module.exports.NodeType = NodeType;
  12912. // Every language `renderEmbeddedSource` can be offered from a stylesheet: a
  12913. // `data:` payload names one, so this is what its media type can name.
  12914. module.exports.SourceProcessor = SourceProcessor;
  12915. module.exports.TT_AT_KEYWORD = TT_AT_KEYWORD;
  12916. module.exports.TT_BAD_STRING_TOKEN = TT_BAD_STRING_TOKEN;
  12917. module.exports.TT_BAD_URL_TOKEN = TT_BAD_URL_TOKEN;
  12918. module.exports.TT_CDC = TT_CDC;
  12919. module.exports.TT_CDO = TT_CDO;
  12920. module.exports.TT_COLON = TT_COLON;
  12921. module.exports.TT_COMMA = TT_COMMA;
  12922. module.exports.TT_COMMENT = TT_COMMENT;
  12923. module.exports.TT_DELIM = TT_DELIM;
  12924. module.exports.TT_DIMENSION = TT_DIMENSION;
  12925. module.exports.TT_EOF = TT_EOF;
  12926. module.exports.TT_FUNCTION = TT_FUNCTION;
  12927. module.exports.TT_HASH = TT_HASH;
  12928. module.exports.TT_IDENTIFIER = TT_IDENTIFIER;
  12929. module.exports.TT_LEFT_CURLY_BRACKET = TT_LEFT_CURLY_BRACKET;
  12930. module.exports.TT_LEFT_PARENTHESIS = TT_LEFT_PARENTHESIS;
  12931. module.exports.TT_LEFT_SQUARE_BRACKET = TT_LEFT_SQUARE_BRACKET;
  12932. module.exports.TT_NUMBER = TT_NUMBER;
  12933. module.exports.TT_PERCENTAGE = TT_PERCENTAGE;
  12934. module.exports.TT_RIGHT_CURLY_BRACKET = TT_RIGHT_CURLY_BRACKET;
  12935. module.exports.TT_RIGHT_PARENTHESIS = TT_RIGHT_PARENTHESIS;
  12936. module.exports.TT_RIGHT_SQUARE_BRACKET = TT_RIGHT_SQUARE_BRACKET;
  12937. module.exports.TT_SEMICOLON = TT_SEMICOLON;
  12938. module.exports.TT_STRING = TT_STRING;
  12939. module.exports.TT_URL = TT_URL;
  12940. module.exports.TT_WHITESPACE = TT_WHITESPACE;
  12941. module.exports.TokenStream = TokenStream;
  12942. module.exports.askEmbeddedRenderer = askEmbeddedRenderer;
  12943. module.exports.buildSkipSet = buildSkipSet;
  12944. module.exports.collectEmbeddedDiagnostics = collectEmbeddedDiagnostics;
  12945. module.exports.embeddedText = embeddedText;
  12946. module.exports.equalsLowerCase = equalsLowerCase;
  12947. module.exports.escapeIdentifier = escapeIdentifier;
  12948. module.exports.isDashedIdentifier = isDashedIdentifier;
  12949. // CSS Syntax §4.2 "whitespace" (space / tab / newline / CR / FF) — the
  12950. // tokenizer's whitespace class, exported under the spec's name.
  12951. module.exports.isWhitespace = _isWhiteSpace;
  12952. module.exports.normalizeUrl = normalizeUrl;
  12953. module.exports.parseABlocksContents = parseABlocksContents;
  12954. module.exports.parseACommaSeparatedListOfComponentValues =
  12955. parseACommaSeparatedListOfComponentValues;
  12956. module.exports.parseAComponentValue = parseAComponentValue;
  12957. module.exports.parseADeclaration = parseADeclaration;
  12958. module.exports.parseAListOfComponentValues = parseAListOfComponentValues;
  12959. module.exports.parseARule = parseARule;
  12960. module.exports.parseAStylesheet = parseAStylesheet;
  12961. module.exports.parseAStylesheetsContents = parseAStylesheetsContents;
  12962. module.exports.pickTransforms = pickTransforms;
  12963. module.exports.printer = printer;
  12964. module.exports.rangeEquals = rangeEquals;
  12965. module.exports.rangeEqualsLowerCase = rangeEqualsLowerCase;
  12966. module.exports.readToken = readToken;
  12967. module.exports.skipEscape = skipEscape;
  12968. module.exports.toLowerCaseIfNeeded = toLowerCaseIfNeeded;
  12969. module.exports.unescapeIdentifier = unescapeIdentifier;