parserlib.js 157 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720
  1. /*
  2. Modded by tophf <github.com/tophf>
  3. ========== Original disclaimer:
  4. Parser-Lib
  5. Copyright (c) 2009-2016 Nicholas C. Zakas. All rights reserved.
  6. Permission is hereby granted, free of charge, to any person obtaining a copy
  7. of this software and associated documentation files (the "Software"), to deal
  8. in the Software without restriction, including without limitation the rights
  9. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. copies of the Software, and to permit persons to whom the Software is
  11. furnished to do so, subject to the following conditions:
  12. The above copyright notice and this permission notice shall be included in
  13. all copies or substantial portions of the Software.
  14. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. THE SOFTWARE.
  21. */
  22. 'use strict';
  23. /* eslint-disable class-methods-use-this */
  24. self.parserlib = (() => {
  25. //#region Properties
  26. // Global keywords that can be set for any property are conveniently listed in `all` prop:
  27. // https://drafts.csswg.org/css-cascade/#all-shorthand
  28. const GlobalKeywords = ['initial', 'inherit', 'revert', 'unset'];
  29. const isGlobalKeyword = RegExp.prototype.test.bind(
  30. new RegExp(`^(${GlobalKeywords.join('|')})$`, 'i'));
  31. const Properties = {
  32. 'accent-color': 'auto | <color>',
  33. 'align-items': 'normal | stretch | <baseline-position> | [ <overflow-position>? <self-position> ]',
  34. 'align-content': '<align-content>',
  35. 'align-self': '<align-self>',
  36. 'all': GlobalKeywords.join(' | '),
  37. 'alignment-adjust': 'auto | baseline | before-edge | text-before-edge | middle | central | ' +
  38. 'after-edge | text-after-edge | ideographic | alphabetic | hanging | ' +
  39. 'mathematical | <len-pct>',
  40. 'alignment-baseline': 'auto | baseline | use-script | before-edge | text-before-edge | ' +
  41. 'after-edge | text-after-edge | central | middle | ideographic | alphabetic | ' +
  42. 'hanging | mathematical',
  43. 'animation': '[ <time> || <single-timing-function> || <time> || [ infinite | <number> ] || ' +
  44. '<single-animation-direction> || <single-animation-fill-mode> || ' +
  45. '[ running | paused ] || [ none | <ident> | <string> ] ]#',
  46. 'animation-delay': '<time>#',
  47. 'animation-direction': '<single-animation-direction>#',
  48. 'animation-duration': '<time>#',
  49. 'animation-fill-mode': '<single-animation-fill-mode>#',
  50. 'animation-iteration-count': '[ <number> | infinite ]#',
  51. 'animation-name': '[ none | <single-animation-name> ]#',
  52. 'animation-play-state': '[ running | paused ]#',
  53. 'animation-timing-function': '<single-timing-function>#',
  54. 'appearance': 'none | auto',
  55. '-moz-appearance':
  56. 'none | button | button-arrow-down | button-arrow-next | button-arrow-previous | ' +
  57. 'button-arrow-up | button-bevel | button-focus | caret | checkbox | checkbox-container | ' +
  58. 'checkbox-label | checkmenuitem | dualbutton | groupbox | listbox | listitem | ' +
  59. 'menuarrow | menubar | menucheckbox | menuimage | menuitem | menuitemtext | menulist | ' +
  60. 'menulist-button | menulist-text | menulist-textfield | menupopup | menuradio | ' +
  61. 'menuseparator | meterbar | meterchunk | progressbar | progressbar-vertical | ' +
  62. 'progresschunk | progresschunk-vertical | radio | radio-container | radio-label | ' +
  63. 'radiomenuitem | range | range-thumb | resizer | resizerpanel | scale-horizontal | ' +
  64. 'scalethumbend | scalethumb-horizontal | scalethumbstart | scalethumbtick | ' +
  65. 'scalethumb-vertical | scale-vertical | scrollbarbutton-down | scrollbarbutton-left | ' +
  66. 'scrollbarbutton-right | scrollbarbutton-up | scrollbarthumb-horizontal | ' +
  67. 'scrollbarthumb-vertical | scrollbartrack-horizontal | scrollbartrack-vertical | ' +
  68. 'searchfield | separator | sheet | spinner | spinner-downbutton | spinner-textfield | ' +
  69. 'spinner-upbutton | splitter | statusbar | statusbarpanel | tab | tabpanel | tabpanels | ' +
  70. 'tab-scroll-arrow-back | tab-scroll-arrow-forward | textfield | textfield-multiline | ' +
  71. 'toolbar | toolbarbutton | toolbarbutton-dropdown | toolbargripper | toolbox | tooltip | ' +
  72. 'treeheader | treeheadercell | treeheadersortarrow | treeitem | treeline | treetwisty | ' +
  73. 'treetwistyopen | treeview | -moz-mac-unified-toolbar | -moz-win-borderless-glass | ' +
  74. '-moz-win-browsertabbar-toolbox | -moz-win-communicationstext | ' +
  75. '-moz-win-communications-toolbox | -moz-win-exclude-glass | -moz-win-glass | ' +
  76. '-moz-win-mediatext | -moz-win-media-toolbox | -moz-window-button-box | ' +
  77. '-moz-window-button-box-maximized | -moz-window-button-close | ' +
  78. '-moz-window-button-maximize | -moz-window-button-minimize | -moz-window-button-restore | ' +
  79. '-moz-window-frame-bottom | -moz-window-frame-left | -moz-window-frame-right | ' +
  80. '-moz-window-titlebar | -moz-window-titlebar-maximized',
  81. '-ms-appearance':
  82. 'none | icon | window | desktop | workspace | document | tooltip | dialog | button | ' +
  83. 'push-button | hyperlink | radio | radio-button | checkbox | menu-item | tab | menu | ' +
  84. 'menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | ' +
  85. 'outline-tree | range | field | combo-box | signature | password | normal',
  86. '-webkit-appearance':
  87. 'auto | none | button | button-bevel | caps-lock-indicator | caret | checkbox | ' +
  88. 'default-button | listbox | listitem | media-fullscreen-button | media-mute-button | ' +
  89. 'media-play-button | media-seek-back-button | media-seek-forward-button | media-slider | ' +
  90. 'media-sliderthumb | menulist | menulist-button | menulist-text | menulist-textfield | ' +
  91. 'push-button | radio | searchfield | searchfield-cancel-button | searchfield-decoration | ' +
  92. 'searchfield-results-button | searchfield-results-decoration | slider-horizontal | ' +
  93. 'slider-vertical | sliderthumb-horizontal | sliderthumb-vertical | square-button | ' +
  94. 'textarea | textfield | scrollbarbutton-down | scrollbarbutton-left | ' +
  95. 'scrollbarbutton-right | scrollbarbutton-up | scrollbargripper-horizontal | ' +
  96. 'scrollbargripper-vertical | scrollbarthumb-horizontal | scrollbarthumb-vertical | ' +
  97. 'scrollbartrack-horizontal | scrollbartrack-vertical',
  98. '-o-appearance':
  99. 'none | window | desktop | workspace | document | tooltip | dialog | button | ' +
  100. 'push-button | hyperlink | radio | radio-button | checkbox | menu-item | tab | menu | ' +
  101. 'menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | ' +
  102. 'outline-tree | range | field | combo-box | signature | password | normal',
  103. 'aspect-ratio': 'auto || [ <num0+> / <num0+> ]',
  104. 'azimuth': '<azimuth>',
  105. 'backdrop-filter': '<filter-function-list> | none',
  106. 'backface-visibility': 'visible | hidden',
  107. 'background': '[ <bg-layer> , ]* <final-bg-layer>',
  108. 'background-attachment': '<attachment>#',
  109. 'background-blend-mode': '<blend-mode>',
  110. 'background-clip': '[ <box> | text ]#',
  111. 'background-color': '<color>',
  112. 'background-image': '<bg-image>#',
  113. 'background-origin': '<box>#',
  114. 'background-position': '<bg-position>#',
  115. 'background-position-x': '[ center | [ left | right ]? <len-pct>? ]#',
  116. 'background-position-y': '[ center | [ top | bottom ]? <len-pct>? ]#',
  117. 'background-repeat': '<repeat-style>#',
  118. 'background-size': '<bg-size>#',
  119. 'baseline-shift': 'baseline | sub | super | <len-pct>',
  120. 'behavior': 1,
  121. 'binding': 1,
  122. 'bleed': '<length>',
  123. 'block-size': '<width>',
  124. 'bookmark-label': '<content-list>',
  125. 'bookmark-level': 'none | <integer>',
  126. 'bookmark-state': 'open | closed',
  127. 'bookmark-target': 'none | <uri>',
  128. 'border-boundary': 'none | parent | display',
  129. 'border-collapse': 'collapse | separate',
  130. 'border-image': '[ none | <image> ] || <border-image-slice> ' +
  131. '[ / <border-image-width> | / <border-image-width>? / <border-image-outset> ]? || ' +
  132. '<border-image-repeat>',
  133. 'border-image-outset': '<border-image-outset>',
  134. 'border-image-repeat': '<border-image-repeat>',
  135. 'border-image-slice': '<border-image-slice>',
  136. 'border-image-source': '<image> | none',
  137. 'border-image-width': '<border-image-width>',
  138. 'border-spacing': '<length>{1,2}',
  139. 'border-bottom-left-radius': '<len-pct>{1,2}',
  140. 'border-bottom-right-radius': '<len-pct>{1,2}',
  141. 'border-end-end-radius': '<len-pct>{1,2}',
  142. 'border-end-start-radius': '<len-pct>{1,2}',
  143. 'border-radius': '<border-radius>',
  144. 'border-start-end-radius': '<len-pct>{1,2}',
  145. 'border-start-start-radius': '<len-pct>{1,2}',
  146. 'border-top-left-radius': '<len-pct>{1,2}',
  147. 'border-top-right-radius': '<len-pct>{1,2}',
  148. 'bottom': '<width>',
  149. 'box-decoration-break': 'slice | clone',
  150. 'box-shadow': '<box-shadow>',
  151. 'box-sizing': 'content-box | border-box',
  152. 'break-after': 'auto | always | avoid | left | right | page | column | avoid-page | avoid-column',
  153. 'break-before': 'auto | always | avoid | left | right | page | column | avoid-page | avoid-column',
  154. 'break-inside': 'auto | avoid | avoid-page | avoid-column',
  155. '-moz-box-align': 1,
  156. '-moz-box-decoration-break': 1,
  157. '-moz-box-direction': 1,
  158. '-moz-box-flex': 1,
  159. '-moz-box-flex-group': 1,
  160. '-moz-box-lines': 1,
  161. '-moz-box-ordinal-group': 1,
  162. '-moz-box-orient': 1,
  163. '-moz-box-pack': 1,
  164. '-o-box-decoration-break': 1,
  165. '-webkit-box-align': 1,
  166. '-webkit-box-decoration-break': 1,
  167. '-webkit-box-direction': 1,
  168. '-webkit-box-flex': 1,
  169. '-webkit-box-flex-group': 1,
  170. '-webkit-box-lines': 1,
  171. '-webkit-box-ordinal-group': 1,
  172. '-webkit-box-orient': 1,
  173. '-webkit-box-pack': 1,
  174. 'caret-color': 'auto | <color>',
  175. 'caption-side': 'top | bottom | inline-start | inline-end',
  176. 'clear': 'none | right | left | both | inline-start | inline-end',
  177. 'clip': 'rect( [ <length> | auto ]#{4} ) | auto',
  178. 'clip-path': '<clip-source> | <clip-path> | none',
  179. 'clip-rule': 'nonzero | evenodd',
  180. 'color': '<color>',
  181. 'color-adjust': 'economy | exact',
  182. 'color-interpolation': 'auto | sRGB | linearRGB',
  183. 'color-interpolation-filters': 'auto | sRGB | linearRGB',
  184. 'color-profile': 1,
  185. 'color-rendering': 'auto | optimizeSpeed | optimizeQuality',
  186. 'color-scheme': 'normal | [ light | dark ]+',
  187. 'column-count': '<integer> | auto',
  188. 'column-fill': 'auto | balance',
  189. 'column-gap': '<column-gap>',
  190. 'column-rule': '<border-shorthand>',
  191. 'column-rule-color': '<color>',
  192. 'column-rule-style': '<border-style>',
  193. 'column-rule-width': '<border-width>',
  194. 'column-span': 'none | all',
  195. 'column-width': '<length> | auto',
  196. 'columns': 1,
  197. 'contain': 'none | strict | content | [ size || layout || style || paint ]',
  198. 'contain-intrinsic-size': 'none | <length>{1,2}',
  199. 'content': 'normal | none | <content-list> [ / <string> ]?',
  200. 'content-visibility': 'visible | auto | hidden',
  201. 'counter-increment': '<counter>',
  202. 'counter-reset': '<counter>',
  203. 'counter-set': '<counter>',
  204. 'cue': 'cue-after | cue-before',
  205. 'cue-after': 1,
  206. 'cue-before': 1,
  207. 'cursor': '[ <uri> [ <number> <number> ]? , ]* ' +
  208. '[ auto | default | none | context-menu | help | pointer | progress | wait | ' +
  209. 'cell | crosshair | text | vertical-text | alias | copy | move | no-drop | ' +
  210. 'not-allowed | grab | grabbing | e-resize | n-resize | ne-resize | nw-resize | ' +
  211. 's-resize | se-resize | sw-resize | w-resize | ew-resize | ns-resize | ' +
  212. 'nesw-resize | nwse-resize | col-resize | row-resize | all-scroll | ' +
  213. 'zoom-in | zoom-out ]',
  214. 'direction': 'ltr | rtl',
  215. 'display': '[ <display-outside> || <display-inside> ] | ' +
  216. '<display-listitem> | <display-internal> | <display-box> | <display-legacy> | ' +
  217. // deprecated and nonstandard
  218. '-webkit-box | -webkit-inline-box | -ms-flexbox',
  219. 'dominant-baseline': 'auto | use-script | no-change | reset-size | ideographic | alphabetic | ' +
  220. 'hanging | mathematical | central | middle | text-after-edge | text-before-edge',
  221. 'drop-initial-after-adjust': 'central | middle | after-edge | text-after-edge | ideographic | ' +
  222. 'alphabetic | mathematical | <len-pct>',
  223. 'drop-initial-after-align': 'baseline | use-script | before-edge | text-before-edge | ' +
  224. 'after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | ' +
  225. 'mathematical',
  226. 'drop-initial-before-adjust': 'before-edge | text-before-edge | central | middle | ' +
  227. 'hanging | mathematical | <len-pct>',
  228. 'drop-initial-before-align': 'caps-height | baseline | use-script | before-edge | ' +
  229. 'text-before-edge | after-edge | text-after-edge | central | middle | ideographic | ' +
  230. 'alphabetic | hanging | mathematical',
  231. 'drop-initial-size': 'auto | line | <len-pct>',
  232. 'drop-initial-value': '<integer>',
  233. 'elevation': '<angle> | below | level | above | higher | lower',
  234. 'empty-cells': 'show | hide',
  235. 'enable-background': 1,
  236. 'fill': '<paint>',
  237. 'fill-opacity': '<opacity-value>',
  238. 'fill-rule': 'nonzero | evenodd',
  239. 'filter': '<filter-function-list> | <ie-function> | none',
  240. 'fit': 'fill | hidden | meet | slice',
  241. 'fit-position': 1,
  242. 'flex': '<flex-shorthand>',
  243. 'flex-basis': '<width>',
  244. 'flex-direction': 'row | row-reverse | column | column-reverse',
  245. 'flex-flow': '<flex-direction> || <flex-wrap>',
  246. 'flex-grow': '<number>',
  247. 'flex-shrink': '<number>',
  248. 'flex-wrap': 'nowrap | wrap | wrap-reverse',
  249. 'float': 'left | right | none | inline-start | inline-end',
  250. 'float-offset': 1,
  251. 'flood-color': 1,
  252. 'flood-opacity': '<opacity-value>',
  253. // matching no-pct first because Matcher doesn't retry for a longer match in nested definitions
  254. 'font': '<font-short-tweak-no-pct>? <font-short-core> | ' +
  255. '[ <font-short-tweak-no-pct> || <pct> ]? <font-short-core> | ' +
  256. 'caption | icon | menu | message-box | small-caption | status-bar',
  257. 'font-family': '<font-family>',
  258. 'font-feature-settings': '<feature-tag-value># | normal',
  259. 'font-kerning': 'auto | normal | none',
  260. 'font-language-override': 'normal | <string>',
  261. 'font-optical-sizing': 'auto | none',
  262. 'font-palette': 'none | normal | light | dark | <ident>',
  263. 'font-size': '<font-size>',
  264. 'font-size-adjust': '<number> | none',
  265. 'font-stretch': '<font-stretch>',
  266. 'font-style': '<font-style>',
  267. 'font-synthesis': 'none | [ weight || style ]',
  268. 'font-synthesis-style': 'auto | none',
  269. 'font-synthesis-weight': 'auto | none',
  270. 'font-synthesis-small-caps': 'auto | none',
  271. 'font-variant': '<font-variant>',
  272. 'font-variant-alternates': '<font-variant-alternates> | normal',
  273. 'font-variant-caps': '<font-variant-caps> | normal',
  274. 'font-variant-east-asian': '<font-variant-east-asian> | normal',
  275. 'font-variant-emoji': 'auto | text | emoji | unicode',
  276. 'font-variant-ligatures': '<font-variant-ligatures> | normal | none',
  277. 'font-variant-numeric': '<font-variant-numeric> | normal',
  278. 'font-variant-position': 'normal | sub | super',
  279. 'font-variation-settings': 'normal | [ <string> <number> ]#',
  280. 'font-weight': '<font-weight>',
  281. 'forced-color-adjust': 'auto | none',
  282. '-ms-flex-align': 1,
  283. '-ms-flex-order': 1,
  284. '-ms-flex-pack': 1,
  285. 'gap': '<row-gap> <column-gap>?',
  286. 'glyph-orientation-horizontal': '<glyph-angle>',
  287. 'glyph-orientation-vertical': 'auto | <glyph-angle>',
  288. 'grid': '<grid-template> | <grid-template-rows> / [ auto-flow && dense? ] <grid-auto-columns>? | ' +
  289. '[ auto-flow && dense? ] <grid-auto-rows>? / <grid-template-columns>',
  290. 'grid-area': '<grid-line> [ / <grid-line> ]{0,3}',
  291. 'grid-auto-columns': '<grid-auto-columns>',
  292. 'grid-auto-flow': '[ row | column ] || dense',
  293. 'grid-auto-rows': '<grid-auto-rows>',
  294. 'grid-column': '<grid-line> [ / <grid-line> ]?',
  295. 'grid-column-start': '<grid-line>',
  296. 'grid-column-end': '<grid-line>',
  297. 'grid-row': '<grid-line> [ / <grid-line> ]?',
  298. 'grid-row-start': '<grid-line>',
  299. 'grid-row-end': '<grid-line>',
  300. 'grid-template': 'none | [ <grid-template-rows> / <grid-template-columns> ] | ' +
  301. '[ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]?',
  302. 'grid-template-areas': 'none | <string>+',
  303. 'grid-template-columns': '<grid-template-columns>',
  304. 'grid-template-rows': '<grid-template-rows>',
  305. 'grid-row-gap': '<row-gap>',
  306. 'grid-column-gap': '<column-gap>',
  307. 'grid-gap': '<row-gap> <column-gap>?',
  308. 'hanging-punctuation': 'none | [ first || [ force-end | allow-end ] || last ]',
  309. 'height': 'auto | <width-height>',
  310. 'hyphenate-after': '<integer> | auto',
  311. 'hyphenate-before': '<integer> | auto',
  312. 'hyphenate-character': '<string> | auto',
  313. 'hyphenate-lines': 'no-limit | <integer>',
  314. 'hyphenate-resource': 1,
  315. 'hyphens': 'none | manual | auto',
  316. 'icon': 1,
  317. 'image-orientation': 'from-image | none | [ <angle> || flip ]',
  318. 'image-rendering': 'auto | smooth | high-quality | crisp-edges | pixelated | ' +
  319. 'optimizeSpeed | optimizeQuality',
  320. 'image-resolution': 1,
  321. 'ime-mode': 'auto | normal | active | inactive | disabled',
  322. 'inline-box-align': 'last | <integer>',
  323. 'inline-size': '<width>',
  324. 'inset': '<width>{1,4}',
  325. 'inset-block': '<width>{1,2}',
  326. 'inset-block-end': '<width>',
  327. 'inset-block-start': '<width>',
  328. 'inset-inline': '<width>{1,2}',
  329. 'inset-inline-end': '<width>',
  330. 'inset-inline-start': '<width>',
  331. 'isolation': 'auto | isolate',
  332. 'justify-content': '<justify-content>',
  333. 'justify-items': 'normal | stretch | <baseline-position> | ' +
  334. '[ <overflow-position>? <self-position> ] | ' +
  335. '[ legacy || [ left | right | center ] ]',
  336. 'justify-self': '<justify-self>',
  337. 'kerning': 'auto | <length>',
  338. 'left': '<width>',
  339. 'letter-spacing': '<length> | normal',
  340. 'line-height': '<line-height>',
  341. 'line-break': 'auto | loose | normal | strict | anywhere',
  342. 'line-stacking': 1,
  343. 'line-stacking-ruby': 'exclude-ruby | include-ruby',
  344. 'line-stacking-shift': 'consider-shifts | disregard-shifts',
  345. 'line-stacking-strategy': 'inline-line-height | block-line-height | max-height | grid-height',
  346. 'list-style': 1,
  347. 'list-style-image': '<uri> | none',
  348. 'list-style-position': 'inside | outside',
  349. 'list-style-type': '<string> | disc | circle | square | decimal | decimal-leading-zero | ' +
  350. 'lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | ' +
  351. 'georgian | lower-alpha | upper-alpha | none',
  352. 'margin': '<width>{1,4}',
  353. 'margin-bottom': '<width>',
  354. 'margin-left': '<width>',
  355. 'margin-right': '<width>',
  356. 'margin-top': '<width>',
  357. 'margin-block': '<width>{1,2}',
  358. 'margin-block-end': '<width>',
  359. 'margin-block-start': '<width>',
  360. 'margin-inline': '<width>{1,2}',
  361. 'margin-inline-end': '<width>',
  362. 'margin-inline-start': '<width>',
  363. 'mark': 1,
  364. 'mark-after': 1,
  365. 'mark-before': 1,
  366. 'marker': 1,
  367. 'marker-end': 1,
  368. 'marker-mid': 1,
  369. 'marker-start': 1,
  370. 'marks': 1,
  371. 'marquee-direction': 1,
  372. 'marquee-play-count': 1,
  373. 'marquee-speed': 1,
  374. 'marquee-style': 1,
  375. 'mask': 1,
  376. 'mask-image': '[ none | <image> | <uri> ]#',
  377. 'max-height': 'none | <width-height>',
  378. 'max-width': 'none | <width-height>',
  379. 'min-height': 'auto | <width-height>',
  380. 'min-width': 'auto | <width-height>',
  381. 'max-block-size': '<len-pct> | none',
  382. 'max-inline-size': '<len-pct> | none',
  383. 'min-block-size': '<len-pct>',
  384. 'min-inline-size': '<len-pct>',
  385. 'mix-blend-mode': '<blend-mode>',
  386. 'move-to': 1,
  387. 'nav-down': 1,
  388. 'nav-index': 1,
  389. 'nav-left': 1,
  390. 'nav-right': 1,
  391. 'nav-up': 1,
  392. 'object-fit': 'fill | contain | cover | none | scale-down',
  393. 'object-position': '<position>',
  394. 'opacity': '<opacity-value> | <pct>',
  395. 'order': '<integer>',
  396. 'orphans': '<integer>',
  397. 'outline': '[ <color> | invert ] || [ auto | <border-style> ] || <border-width>',
  398. 'outline-color': '<color> | invert',
  399. 'outline-offset': '<length>',
  400. 'outline-style': '<border-style> | auto',
  401. 'outline-width': '<border-width>',
  402. 'overflow': '<overflow>{1,2}',
  403. 'overflow-anchor': 'auto | none',
  404. 'overflow-block': '<overflow>',
  405. 'overflow-clip-margin': '<len0+>',
  406. 'overflow-inline': '<overflow>',
  407. 'overflow-style': 1,
  408. 'overflow-wrap': 'normal | break-word | anywhere',
  409. 'overflow-x': '<overflow>',
  410. 'overflow-y': '<overflow>',
  411. 'overscroll-behavior': '<overscroll>{1,2}',
  412. 'overscroll-behavior-block': '<overscroll>',
  413. 'overscroll-behavior-inline': '<overscroll>',
  414. 'overscroll-behavior-x': '<overscroll>',
  415. 'overscroll-behavior-y': '<overscroll>',
  416. 'padding': '<len-pct0+>{1,4}',
  417. 'padding-block': '<len-pct0+>{1,2}',
  418. 'padding-block-end': '<len-pct0+>',
  419. 'padding-block-start': '<len-pct0+>',
  420. 'padding-bottom': '<len-pct0+>',
  421. 'padding-inline': '<len-pct0+>{1,2}',
  422. 'padding-inline-end': '<len-pct0+>',
  423. 'padding-inline-start': '<len-pct0+>',
  424. 'padding-left': '<len-pct0+>',
  425. 'padding-right': '<len-pct0+>',
  426. 'padding-top': '<len-pct0+>',
  427. 'page': 1,
  428. 'page-break-after': 'auto | always | avoid | left | right | recto | verso',
  429. 'page-break-before': 'auto | always | avoid | left | right | recto | verso',
  430. 'page-break-inside': 'auto | avoid',
  431. 'page-policy': 1,
  432. 'pause': 1,
  433. 'pause-after': 1,
  434. 'pause-before': 1,
  435. 'perspective': 'none | <len0+>',
  436. 'perspective-origin': '<position>',
  437. 'phonemes': 1,
  438. 'pitch': 1,
  439. 'pitch-range': 1,
  440. 'place-content': '<align-content> <justify-content>?',
  441. 'place-items': '[ normal | stretch | <baseline-position> | <self-position> ] ' +
  442. '[ normal | stretch | <baseline-position> | <self-position> ]?',
  443. 'place-self': '<align-self> <justify-self>?',
  444. 'play-during': 1,
  445. 'pointer-events': 'auto | none | visiblePainted | visibleFill | visibleStroke | visible | ' +
  446. 'painted | fill | stroke | all',
  447. 'position': 'static | relative | absolute | fixed | sticky | -webkit-sticky',
  448. 'presentation-level': 1,
  449. 'punctuation-trim': 1,
  450. 'quotes': 1,
  451. 'rendering-intent': 1,
  452. 'resize': 'none | both | horizontal | vertical | block | inline',
  453. 'rest': 1,
  454. 'rest-after': 1,
  455. 'rest-before': 1,
  456. 'richness': 1,
  457. 'right': '<width>',
  458. 'rotate': 'none | [ x | y | z | <number>{3} ]? && <angle>',
  459. 'rotation': 1,
  460. 'rotation-point': 1,
  461. 'row-gap': '<row-gap>',
  462. 'ruby-align': 1,
  463. 'ruby-overhang': 1,
  464. 'ruby-position': 1,
  465. 'ruby-span': 1,
  466. 'scale': 'none | <num-pct>{1,3}',
  467. 'scroll-behavior': 'auto | smooth',
  468. 'scroll-margin': '<length>{1,4}',
  469. 'scroll-margin-bottom': '<length>',
  470. 'scroll-margin-left': '<length>',
  471. 'scroll-margin-right': '<length>',
  472. 'scroll-margin-top': '<length>',
  473. 'scroll-margin-block': '<length>{1,2}',
  474. 'scroll-margin-block-end': '<length>',
  475. 'scroll-margin-block-start': '<length>',
  476. 'scroll-margin-inline': '<length>{1,2}',
  477. 'scroll-margin-inline-end': '<length>',
  478. 'scroll-margin-inline-start': '<length>',
  479. 'scroll-padding': '<width>{1,4}',
  480. 'scroll-padding-left': '<width>',
  481. 'scroll-padding-right': '<width>',
  482. 'scroll-padding-top': '<width>',
  483. 'scroll-padding-bottom': '<width>',
  484. 'scroll-padding-block': '<width>{1,2}',
  485. 'scroll-padding-block-end': '<width>',
  486. 'scroll-padding-block-start': '<width>',
  487. 'scroll-padding-inline': '<width>{1,2}',
  488. 'scroll-padding-inline-end': '<width>',
  489. 'scroll-padding-inline-start': '<width>',
  490. 'scroll-snap-align': '[ none | start | end | center ]{1,2}',
  491. 'scroll-snap-stop': 'normal | always',
  492. 'scroll-snap-type': 'none | [ x | y | block | inline | both ] [ mandatory | proximity ]?',
  493. 'scrollbar-color': 'auto | dark | light | <color>{2}',
  494. 'scrollbar-width': 'auto | thin | none',
  495. 'shape-inside': 'auto | outside-shape | [ <basic-shape> || shape-box ] | <image> | display',
  496. 'shape-rendering': 'auto | optimizeSpeed | crispEdges | geometricPrecision',
  497. 'size': 1,
  498. 'speak': 'normal | none | spell-out',
  499. 'speak-header': 'once | always',
  500. 'speak-numeral': 'digits | continuous',
  501. 'speak-punctuation': 'code | none',
  502. 'speech-rate': 1,
  503. 'stop-color': 1,
  504. 'stop-opacity': '<opacity-value>',
  505. 'stress': 1,
  506. 'string-set': 1,
  507. 'stroke': '<paint>',
  508. 'stroke-dasharray': 'none | <dasharray>',
  509. 'stroke-dashoffset': '<len-pct> | <number>',
  510. 'stroke-linecap': 'butt | round | square',
  511. 'stroke-linejoin': 'miter | miter-clip | round | bevel | arcs',
  512. 'stroke-miterlimit': '<num0+>',
  513. 'stroke-opacity': '<opacity-value>',
  514. 'stroke-width': '<len-pct> | <number>',
  515. 'table-layout': 'auto | fixed',
  516. 'tab-size': '<number> | <length>',
  517. 'target': 1,
  518. 'target-name': 1,
  519. 'target-new': 1,
  520. 'target-position': 1,
  521. 'text-align': '<text-align> | justify-all',
  522. 'text-align-all': '<text-align>',
  523. 'text-align-last': '<text-align> | auto',
  524. 'text-anchor': 'start | middle | end',
  525. 'text-decoration': '<text-decoration-line> || <text-decoration-style> || <color>',
  526. 'text-decoration-color': '<color>',
  527. 'text-decoration-line': '<text-decoration-line>',
  528. 'text-decoration-skip': 'none | ' +
  529. '[ objects || [ spaces | [ leading-spaces || trailing-spaces ] ] || edges || box-decoration ]',
  530. 'text-decoration-style': '<text-decoration-style>',
  531. 'text-emphasis': '<text-emphasis-style> || <color>',
  532. 'text-emphasis-style': '<text-emphasis-style>',
  533. 'text-emphasis-position': '[ over | under ] && [ right | left ]?',
  534. 'text-height': 1,
  535. 'text-indent': '<len-pct> && hanging? && each-line?',
  536. 'text-justify': 'auto | none | inter-word | inter-character',
  537. 'text-outline': 1,
  538. 'text-overflow': 'clip | ellipsis',
  539. 'text-rendering': 'auto | optimizeSpeed | optimizeLegibility | geometricPrecision',
  540. 'text-shadow': 'none | [ <color>? && <length>{2,3} ]#',
  541. 'text-transform': 'none | [ capitalize | uppercase | lowercase ] || full-width || full-size-kana',
  542. 'text-underline-position': 'auto | [ under || [ left | right ] ]',
  543. 'text-wrap': 'normal | none | avoid',
  544. 'top': '<width>',
  545. 'touch-action': 'auto | none | ' +
  546. 'pan-x | pan-y | pan-left | pan-right | pan-up | pan-down | manipulation',
  547. 'transform': 'none | <transform-function>+',
  548. 'transform-box': 'border-box | fill-box | view-box',
  549. 'transform-origin': '<transform-origin>',
  550. 'transform-style': 'flat | preserve-3d',
  551. 'transition': '<transition>#',
  552. 'transition-delay': '<time>#',
  553. 'transition-duration': '<time>#',
  554. 'transition-property': 'none | [ all | <ident> ]#',
  555. 'transition-timing-function': '<single-timing-function>#',
  556. 'translate': 'none | <len-pct> [ <len-pct> <length>? ]?',
  557. 'unicode-range': '<unicode-range>#',
  558. 'unicode-bidi': 'normal | embed | isolate | bidi-override | isolate-override | plaintext',
  559. 'user-modify': 'read-only | read-write | write-only',
  560. 'user-select': 'auto | text | none | contain | all',
  561. 'vertical-align': 'auto | use-script | baseline | sub | super | top | text-top | ' +
  562. 'central | middle | bottom | text-bottom | <len-pct>',
  563. 'visibility': 'visible | hidden | collapse',
  564. 'voice-balance': 1,
  565. 'voice-duration': 1,
  566. 'voice-family': 1,
  567. 'voice-pitch': 1,
  568. 'voice-pitch-range': 1,
  569. 'voice-rate': 1,
  570. 'voice-stress': 1,
  571. 'voice-volume': 1,
  572. 'volume': 1,
  573. 'white-space': 'normal | pre | nowrap | pre-wrap | break-spaces | pre-line',
  574. 'white-space-collapse': 1,
  575. 'widows': '<integer>',
  576. 'width': 'auto | <width-height>',
  577. 'will-change': '<will-change>',
  578. 'word-break': 'normal | keep-all | break-all | break-word',
  579. 'word-spacing': '<length> | normal',
  580. 'word-wrap': 'normal | break-word | anywhere',
  581. 'writing-mode': 'horizontal-tb | vertical-rl | vertical-lr | ' +
  582. 'lr-tb | rl-tb | tb-rl | bt-rl | tb-lr | bt-lr | lr-bt | rl-bt | lr | rl | tb',
  583. 'z-index': '<integer> | auto',
  584. 'zoom': '<number> | <pct> | normal',
  585. // nonstandard https://compat.spec.whatwg.org/
  586. '-webkit-box-reflect': '[ above | below | right | left ]? <length>? <image>?',
  587. '-webkit-text-fill-color': '<color>',
  588. '-webkit-text-stroke': '<border-width> || <color>',
  589. '-webkit-text-stroke-color': '<color>',
  590. '-webkit-text-stroke-width': '<border-width>',
  591. };
  592. const ScopedProperties = {
  593. '@font-face': Object.assign({
  594. 'ascent-override': '[ normal | <pct0+> ]{1,2}',
  595. 'descent-override': '[ normal | <pct0+> ]{1,2}',
  596. 'font-display': 'auto | block | swap | fallback | optional',
  597. 'font-stretch': 'auto | <font-stretch>{1,2}',
  598. 'font-style': 'auto | normal | italic | oblique <angle>{0,2}',
  599. 'font-weight': 'auto | [ normal | bold | <num1-1000> ]{1,2}',
  600. 'line-gap-override': '[ normal | <pct0+> ]{1,2}',
  601. 'size-adjust': '<pct0+>',
  602. 'src': '[ url() [ format( <string># ) ]? | local( <family-name> ) ]#',
  603. }, ...[
  604. 'font-family',
  605. 'font-size',
  606. 'font-variant',
  607. 'font-variation-settings',
  608. 'unicode-range',
  609. ].map(p => ({[p]: Properties[p]}))),
  610. };
  611. for (const [k, reps] of Object.entries({
  612. 'border': '{1,4}',
  613. 'border-bottom': '',
  614. 'border-left': '',
  615. 'border-right': '',
  616. 'border-top': '',
  617. 'border-block': '{1,2}',
  618. 'border-block-end': '',
  619. 'border-block-start': '',
  620. 'border-inline': '{1,2}',
  621. 'border-inline-end': '',
  622. 'border-inline-start': '',
  623. })) {
  624. Properties[k] = '<border-shorthand>';
  625. Properties[`${k}-color`] = '<color>' + reps;
  626. Properties[`${k}-style`] = '<border-style>' + reps;
  627. Properties[`${k}-width`] = '<border-width>' + reps;
  628. }
  629. //#endregion
  630. //#region Types
  631. const TYPES = /** @namespace Parser */ {
  632. DEFAULT_TYPE: 0,
  633. COMBINATOR_TYPE: 1,
  634. MEDIA_FEATURE_TYPE: 2,
  635. MEDIA_QUERY_TYPE: 3,
  636. PROPERTY_NAME_TYPE: 4,
  637. PROPERTY_VALUE_TYPE: 5,
  638. PROPERTY_VALUE_PART_TYPE: 6,
  639. SELECTOR_TYPE: 7,
  640. SELECTOR_PART_TYPE: 8,
  641. SELECTOR_SUB_PART_TYPE: 9,
  642. };
  643. const UNITS = {
  644. em: 'length',
  645. rem: 'length',
  646. ex: 'length',
  647. px: 'length',
  648. cm: 'length',
  649. mm: 'length',
  650. in: 'length',
  651. pt: 'length',
  652. pc: 'length',
  653. ch: 'length',
  654. vh: 'length',
  655. vw: 'length',
  656. vmax: 'length',
  657. vmin: 'length',
  658. fr: 'length',
  659. q: 'length',
  660. deg: 'angle',
  661. rad: 'angle',
  662. grad: 'angle',
  663. turn: 'angle',
  664. ms: 'time',
  665. s: 'time',
  666. hz: 'frequency',
  667. khz: 'frequency',
  668. dpi: 'resolution',
  669. dpcm: 'resolution',
  670. dppx: 'resolution',
  671. x: 'resolution',
  672. };
  673. // Sticky `y` flag must be used in expressions used with peekTest and readMatch
  674. const rxIdentStart = /[-\\_a-zA-Z\u00A0-\uFFFF]/u;
  675. const rxNameChar = /[-\\_\da-zA-Z\u00A0-\uFFFF]/u;
  676. const rxNameCharNoEsc = /[-_\da-zA-Z\u00A0-\uFFFF]+/yu; // must not match \\
  677. const rxUnquotedUrlCharNoEsc = /[-!#$%&*-[\]-~\u00A0-\uFFFF]+/yu; // must not match \\
  678. const rxVendorPrefix = /^-(webkit|moz|ms|o)-(.+)/i;
  679. const rxCalc = /^(?:-(webkit|moz|ms|o)-)?(calc|min|max|clamp)\(/i;
  680. const lowercaseCache = new Map();
  681. //#endregion
  682. //#region ValidationTypes - definitions
  683. /** Allowed syntax: text, |, <syntax>, func() */
  684. const VTSimple = {
  685. '<absolute-size>': 'xx-small | x-small | small | medium | large | x-large | xx-large',
  686. '<animateable-feature>': 'scroll-position | contents | <animateable-feature-name>',
  687. '<animateable-feature-name>': p => vtIsIdent(p) && !isGlobalKeyword(p) &&
  688. !/^(will-change|auto|scroll-position|contents)$/i.test(p),
  689. '<angle>': p => p.type === 'angle' || p.isCalc,
  690. '<angle-or-0>': p => p.text === '0' || p.type === 'angle' || p.isCalc,
  691. '<attr>': vtIsAttr,
  692. '<attachment>': 'scroll | fixed | local',
  693. '<bg-image>': '<image> | none',
  694. '<blend-mode>': 'normal | multiply | screen | overlay | darken | lighten | color-dodge | ' +
  695. 'color-burn | hard-light | soft-light | difference | exclusion | hue | ' +
  696. 'saturation | color | luminosity',
  697. '<border-style>': 'none | ' +
  698. 'hidden | dotted | dashed | solid | double | groove | ridge | inset | outset',
  699. '<border-width>': '<length> | thin | medium | thick',
  700. '<box>': 'padding-box | border-box | content-box',
  701. '<clip-source>': '<uri>',
  702. '<column-gap>': 'normal | <len-pct>',
  703. '<content-distribution>': 'space-between | space-around | space-evenly | stretch',
  704. '<content-position>': 'center | start | end | flex-start | flex-end',
  705. '<display-box>': 'contents | none',
  706. '<display-inside>': 'flow | flow-root | table | flex | grid | ruby',
  707. '<display-internal>': 'table-row-group | table-header-group | table-footer-group | ' +
  708. 'table-row | table-cell | table-column-group | table-column | table-caption | ' +
  709. 'ruby-base | ruby-text | ruby-base-container | ruby-text-container',
  710. '<display-legacy>': 'inline-block | inline-table | inline-flex | inline-grid',
  711. '<display-outside>': 'block | inline | run-in',
  712. '<feature-tag-value>': p => p.type === 'function' && /^[A-Z0-9]{4}$/i.test(p),
  713. '<flex>': p => p.type === 'grid' && p.value >= 0 || p.isCalc,
  714. '<flex-basis>': '<width>',
  715. '<flex-direction>': 'row | row-reverse | column | column-reverse',
  716. '<flex-grow>': '<number>',
  717. '<flex-shrink>': '<number>',
  718. '<flex-wrap>': 'nowrap | wrap | wrap-reverse',
  719. '<font-size>': '<absolute-size> | <relative-size> | <len-pct0+>',
  720. '<font-stretch>': '<font-stretch-named> | <pct>',
  721. '<font-stretch-named>': 'normal | ultra-condensed | extra-condensed | condensed | ' +
  722. 'semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded',
  723. '<font-variant-caps>':
  724. 'small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps',
  725. '<font-variant-css21>': 'normal | small-caps',
  726. '<font-weight>': 'normal | bold | bolder | lighter | <num1-1000>',
  727. '<generic-family>': 'serif | sans-serif | cursive | fantasy | monospace | system-ui | ' +
  728. 'emoji | math | fangsong | ui-serif | ui-sans-serif | ui-monospace | ui-rounded',
  729. '<geometry-box>': '<shape-box> | fill-box | stroke-box | view-box',
  730. '<glyph-angle>': p => p.type === 'angle' && p.units === 'deg',
  731. '<gradient>': 'radial-gradient() | linear-gradient() | conic-gradient() | gradient() | ' +
  732. 'repeating-radial-gradient() | repeating-linear-gradient() | repeating-conic-gradient() | ' +
  733. 'repeating-gradient()',
  734. '<hex-color>': p => p.tokenType === Tokens.HASH, //eslint-disable-line no-use-before-define
  735. '<icccolor>': 'cielab() | cielch() | cielchab() | icc-color() | icc-named-color()',
  736. '<ident>': vtIsIdent,
  737. '<ident-for-grid>': p => vtIsIdent(p) && !isGlobalKeyword(p.value) &&
  738. !/^(span|auto|default)$/i.test(p.value),
  739. '<ident-not-generic-family>': p => vtIsIdent(p) && !VTSimple['<generic-family>'](p),
  740. '<ident-not-none>': p => vtIsIdent(p) && !lowerCmp(p.value, 'none'),
  741. '<ie-function>': p => p.tokenType === Tokens.IE_FUNCTION, //eslint-disable-line no-use-before-define
  742. '<image>': '<uri> | <gradient> | cross-fade()',
  743. '<inflexible-breadth>': '<len-pct> | min-content | max-content | auto',
  744. '<integer>': p => p.isInt || p.isCalc,
  745. '<length>': vtIsLength,
  746. '<len0+>': p =>
  747. p.value >= 0 && vtIsLength(p) || p.isCalc,
  748. '<len-pct>': p => vtIsLength(p) || vtIsPct(p),
  749. '<len-pct0+>': p =>
  750. p.value >= 0 && (p.type === 'percentage' || vtIsLength(p)) || p.isCalc,
  751. '<line>': p => p.isInt,
  752. '<line-height>': '<number> | <len-pct> | normal',
  753. '<line-names>': p =>
  754. p.tokenType === Tokens.LBRACKET && // eslint-disable-line no-use-before-define
  755. p.text.endsWith(']') && (
  756. !p.expr ||
  757. !p.expr.parts.length ||
  758. p.expr.parts.every(VTSimple['<ident-for-grid>'], VTSimple)
  759. ),
  760. //eslint-disable-next-line no-use-before-define
  761. '<named-color>': p => p.text in Colors || ColorsLC.has(lower(p.text)),
  762. '<number>': p => p.type === 'number' || p.isCalc,
  763. '<num0+>': p =>
  764. p.value >= 0 && p.type === 'number' || p.isCalc,
  765. '<num1-1000>': p => (p.type === 'number' && p.value >= 1 && p.value <= 1000) || p.isCalc,
  766. '<num-pct>': p => p.type === 'number' || p.type === 'percentage' || p.isCalc,
  767. '<num-pct0+>': p =>
  768. p.value >= 0 && (p.type === 'number' || p.type === 'percentage') || p.isCalc,
  769. '<opacity-value>': p => p.type === 'number' && p.value >= 0 && p.value <= 1 || p.isCalc,
  770. '<overflow>': 'visible | hidden | clip | scroll | auto',
  771. '<overflow-position>': 'unsafe | safe',
  772. '<pct>': vtIsPct,
  773. '<pct0+>': p =>
  774. p.value >= 0 && p.type === 'percentage' || p.isCalc,
  775. '<integer1+>': p => p.isInt && p.value > 0 || p.isCalc,
  776. '<relative-size>': 'smaller | larger',
  777. '<row-gap>': '<column-gap>',
  778. '<self-position>': 'center | start | end | self-start | self-end | flex-start | flex-end',
  779. '<shape-box>': '<box> | margin-box',
  780. '<single-animation-direction>': 'normal | reverse | alternate | alternate-reverse',
  781. '<single-animation-fill-mode>': 'none | forwards | backwards | both',
  782. '<single-animation-name>': p => vtIsIdent(p) && !isGlobalKeyword(p) &&
  783. /^-?[a-z_][-a-z0-9_]+$/i.test(p),
  784. '<string>': p => p.type === 'string',
  785. '<text-align>': 'start | end | left | right | center | justify | match-parent',
  786. '<text-decoration-style>': 'solid | double | dotted | dashed | wavy',
  787. '<time>': p => p.type === 'time',
  788. '<track-breadth>': '<len-pct> | <flex> | min-content | max-content | auto',
  789. '<unicode-range>': p => /^U\+[0-9a-f?]{1,6}(-[0-9a-f?]{1,6})?\s*$/i.test(p),
  790. '<unit>': p => p.text === '%' || p in UNITS || lower(p) in UNITS,
  791. '<uri>': p => p.type === 'uri',
  792. '<width>': p => vtIsLength(p) || vtIsPct(p) || lowerCmp(p.text, 'auto'),
  793. };
  794. const VTComplex = {
  795. '<align-content>': 'normal | <baseline-position> | <content-distribution> | ' +
  796. '<overflow-position>? <content-position>',
  797. '<align-self>':
  798. 'auto | normal | stretch | <baseline-position> | <overflow-position>? <self-position>',
  799. '<auto-repeat>':
  800. 'repeat( [ auto-fill | auto-fit ] , [ <line-names>? <fixed-size> ]+ <line-names>? )',
  801. '<auto-track-list>':
  802. '[ <line-names>? [ <fixed-size> | <fixed-repeat> ] ]* <line-names>? <auto-repeat> ' +
  803. '[ <line-names>? [ <fixed-size> | <fixed-repeat> ] ]* <line-names>?',
  804. '<azimuth>':
  805. '<angle> | [ [ left-side | far-left | left | center-left | center | center-right | ' +
  806. 'right | far-right | right-side ] || behind ] | leftwards | rightwards',
  807. '<baseline-position>': '[ first | last ]? baseline',
  808. '<basic-shape>':
  809. 'inset( <len-pct>{1,4} [ round <border-radius> ]? ) | ' +
  810. 'circle( [ <len-pct> | closest-side | farthest-side ]? [ at <position> ]? ) | ' +
  811. 'ellipse( [ [ <len-pct> | closest-side | farthest-side ]{2} ]? [ at <position> ]? ) | ' +
  812. 'path( [ [ nonzero | evenodd ] , ]? <string> ) | ' +
  813. 'polygon( [ [ nonzero | evenodd | inherit ] , ]? [ <len-pct> <len-pct> ]# )',
  814. '<bg-layer>':
  815. '<bg-image> || <bg-position> [ / <bg-size> ]? || <repeat-style> || <attachment> || <box>{1,2}',
  816. '<bg-position>':
  817. '[ center | [ left | right ] <len-pct>? ] && [ center | [ top | bottom ] <len-pct>? ] | ' +
  818. '[ left | center | right | <len-pct> ] [ top | center | bottom | <len-pct> ] | ' +
  819. '[ left | center | right | top | bottom | <len-pct> ]',
  820. '<bg-size>': '[ <len-pct> | auto ]{1,2} | cover | contain',
  821. '<border-image-outset>': '[ <length> | <number> ]{1,4}',
  822. '<border-image-repeat>': '[ stretch | repeat | round | space ]{1,2}',
  823. '<border-image-slice>': Matcher =>
  824. // [<number> | <pct>]{1,4} && fill?
  825. // but 'fill' can appear between any of the numbers
  826. Matcher.many(
  827. [true],
  828. Matcher.parse('<num-pct0+>'),
  829. Matcher.parse('<num-pct0+>'),
  830. Matcher.parse('<num-pct0+>'),
  831. Matcher.parse('<num-pct0+>'),
  832. 'fill'),
  833. '<border-image-width>': '[ <len-pct> | <number> | auto ]{1,4}',
  834. '<border-radius>': '<len-pct0+>{1,4} [ / <len-pct0+>{1,4} ]?',
  835. '<border-shorthand>': '<border-width> || <border-style> || <color>',
  836. '<box-shadow>': 'none | <shadow>#',
  837. '<clip-path>': '<basic-shape> || <geometry-box>',
  838. '<color>': '<hex-color> | <named-color> | rgb( <rgb-color> ) | rgba( <rgb-color> ) | ' +
  839. 'hsl( <hsl-color> ) | hsla( <hsl-color> )',
  840. '<content-list>':
  841. '[ <string> | <image> | <attr> | ' +
  842. 'content( text | before | after | first-letter | marker ) | ' +
  843. 'counter() | counters() | leader() | ' +
  844. '[ open-quote | close-quote | no-open-quote | no-close-quote ] | ' +
  845. '[ target-counter() | target-counters() | target-text() ] ]+',
  846. '<counter>': '[ <ident-not-none> <integer>? ]+ | none',
  847. '<cubic-bezier-timing-function>': 'ease | ease-in | ease-out | ease-in-out | ' +
  848. 'cubic-bezier( <number>#{4} )',
  849. '<dasharray>': Matcher =>
  850. Matcher.parse('<len-pct0+> | <num0+>')
  851. .braces(1, Infinity, '#', Matcher.parse(',').braces(0, 1, '?')),
  852. '<display-listitem>': '<display-outside>? && [ flow | flow-root ]? && list-item',
  853. '<explicit-track-list>': '[ <line-names>? <track-size> ]+ <line-names>?',
  854. '<family-name>': '<string> | <ident-not-generic-family> <ident>*',
  855. // https://drafts.fxtf.org/filter-effects/#supported-filter-functions
  856. // Value may be omitted in which case the default is used
  857. '<filter-function>':
  858. 'blur( <length>? ) | ' +
  859. 'brightness( <num-pct>? ) | ' +
  860. 'contrast( <num-pct>? ) | ' +
  861. 'drop-shadow( [ <length>{2,3} && <color>? ]? ) | ' +
  862. 'grayscale( <num-pct>? ) | ' +
  863. 'hue-rotate( <angle-or-0>? ) | ' +
  864. 'invert( <num-pct>? ) | ' +
  865. 'opacity( <num-pct>? ) | ' +
  866. 'saturate( <num-pct>? ) | ' +
  867. 'sepia( <num-pct>? )',
  868. '<filter-function-list>': '[ <filter-function> | <uri> ]+',
  869. '<final-bg-layer>': '<color> || <bg-image> || <bg-position> [ / <bg-size> ]? || ' +
  870. '<repeat-style> || <attachment> || <box>{1,2}',
  871. '<fixed-repeat>':
  872. 'repeat( [ <integer1+> ] , [ <line-names>? <fixed-size> ]+ <line-names>? )',
  873. '<fixed-size>': '<len-pct> | ' +
  874. 'minmax( <len-pct> , <track-breadth> ) | ' +
  875. 'minmax( <inflexible-breadth> , <len-pct> )',
  876. '<flex-shorthand>': 'none | [ <flex-grow> <flex-shrink>? || <flex-basis> ]',
  877. '<font-family>': '[ <generic-family> | <family-name> ]#',
  878. '<font-style>': 'normal | italic | oblique <angle>?',
  879. '<font-short-core>': '<font-size> [ / <line-height> ]? <font-family>',
  880. '<font-short-tweak-no-pct>':
  881. '<font-style> || <font-variant-css21> || <font-weight> || <font-stretch-named>',
  882. '<font-variant-alternates>': 'stylistic() || historical-forms || styleset() || ' +
  883. 'character-variant() || swash() || ornaments() || annotation()',
  884. '<font-variant-ligatures>': '[ common-ligatures | no-common-ligatures ] || ' +
  885. '[ discretionary-ligatures | no-discretionary-ligatures ] || ' +
  886. '[ historical-ligatures | no-historical-ligatures ] || ' +
  887. '[ contextual | no-contextual ]',
  888. '<font-variant-numeric>': '[ lining-nums | oldstyle-nums ] || ' +
  889. '[ proportional-nums | tabular-nums ] || ' +
  890. '[ diagonal-fractions | stacked-fractions ] || ' +
  891. 'ordinal || slashed-zero',
  892. '<font-variant-east-asian>': '[ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] || ' +
  893. '[ full-width | proportional-width ] || ruby',
  894. '<font-variant>': 'normal | none | [ ' +
  895. '<font-variant-ligatures> || <font-variant-alternates> || ' +
  896. '<font-variant-caps> || <font-variant-numeric> || <font-variant-east-asian> ]',
  897. '<grid-auto-columns>': '<track-size>+',
  898. '<grid-auto-rows>': '<track-size>+',
  899. '<grid-line>': 'auto | [ <integer> && <ident-for-grid>? ] | <ident-for-grid> | ' +
  900. '[ span && [ <integer> || <ident-for-grid> ] ]',
  901. '<grid-template>': 'none | [ <grid-template-rows> / <grid-template-columns> ] | ' +
  902. '[ <line-names>? <string> <track-size>? <line-names>? ]+ ' +
  903. '[ / <explicit-track-list> ]?',
  904. '<grid-template-columns>': 'none | <track-list> | <auto-track-list>',
  905. '<grid-template-rows>': '<grid-template-columns>',
  906. '<hsl-color>': '[ <number> | <angle> ] <pct>{2} [ / <num-pct0+> ]? | ' +
  907. '[ <number> | <angle> ] , <pct>#{2} [ , <num-pct0+> ]?',
  908. '<justify-content>': 'normal | <content-distribution> | ' +
  909. '<overflow-position>? [ <content-position> | left | right ]',
  910. '<justify-self>': 'auto | normal | stretch | <baseline-position> | <overflow-position>? ' +
  911. '[ <self-position> | left | right ]',
  912. '<overscroll>': 'contain | none | auto',
  913. '<paint>': 'none | <color> | <uri> [ none | <color> ]? | context-fill | context-stroke',
  914. // Because our `alt` combinator is ordered, we need to test these
  915. // in order from longest possible match to shortest.
  916. '<position>':
  917. '[ [ left | right ] <len-pct> ] && [ [ top | bottom ] <len-pct> ] | ' +
  918. '[ left | center | right | <len-pct> ] ' +
  919. '[ top | center | bottom | <len-pct> ]? | ' +
  920. '[ left | center | right ] || [ top | center | bottom ]',
  921. '<repeat-style>': 'repeat-x | repeat-y | [ repeat | space | round | no-repeat ]{1,2}',
  922. '<rgb-color>':
  923. '[ <number>{3} | <pct>{3} ] [ / <num-pct0+> ]? | ' +
  924. '[ <number>#{3} | <pct>#{3} ] [ , <num-pct0+> ]?',
  925. '<shadow>': 'inset? && [ <length>{2,4} && <color>? ]',
  926. '<single-timing-function>':
  927. 'linear | <cubic-bezier-timing-function> | <step-timing-function> | frames( <integer> )',
  928. '<step-timing-function>': 'step-start | step-end | ' +
  929. 'steps( <integer> [ , [ jump-start | jump-end | jump-none | jump-both | start | end ] ]? )',
  930. '<text-decoration-line>': 'none | [ underline || overline || line-through || blink ]',
  931. '<text-emphasis-style>': 'none | ' +
  932. '[ [ filled | open ] || [ dot | circle | double-circle | triangle | sesame ] ] | ' +
  933. '<string>',
  934. '<track-list>': '[ <line-names>? [ <track-size> | <track-repeat> ] ]+ <line-names>?',
  935. '<track-repeat>': 'repeat( [ <integer1+> ] , [ <line-names>? <track-size> ]+ <line-names>? )',
  936. '<track-size>': '<track-breadth> | minmax( <inflexible-breadth> , <track-breadth> ) | ' +
  937. 'fit-content( <len-pct> )',
  938. '<transform-function>':
  939. 'matrix( <number>#{6} ) | ' +
  940. 'matrix3d( <number>#{16} ) | ' +
  941. 'perspective( <len0+> | none ) | ' +
  942. 'rotate( <angle-or-0> ) | ' +
  943. 'rotate3d( <number>#{3} , <angle-or-0> ) | ' +
  944. 'rotateX( <angle-or-0> ) | ' +
  945. 'rotateY( <angle-or-0> ) | ' +
  946. 'rotateZ( <angle-or-0> ) | ' +
  947. 'scale( [ <num-pct> ]#{1,2} ) | ' +
  948. 'scale3d( <num-pct>#{3} ) | ' +
  949. 'scaleX( <num-pct> ) | ' +
  950. 'scaleY( <num-pct> ) | ' +
  951. 'scaleZ( <num-pct> ) | ' +
  952. 'skew( <angle-or-0> [ , <angle-or-0> ]? ) | ' +
  953. 'skewX( <angle-or-0> ) | ' +
  954. 'skewY( <angle-or-0> ) | ' +
  955. 'translate( <len-pct>#{1,2} ) | ' +
  956. 'translate3d( <len-pct>#{2} , <length> ) | ' +
  957. 'translateX( <len-pct> ) | ' +
  958. 'translateY( <len-pct> ) | ' +
  959. 'translateZ( <length> )',
  960. '<transform-origin>': '[ left | center | right | <len-pct> ] ' +
  961. '[ top | center | bottom | <len-pct> ] <length>? | ' +
  962. '[ left | center | right | top | bottom | <len-pct> ] | ' +
  963. '[ [ center | left | right ] && [ center | top | bottom ] ] <length>?',
  964. '<transition>': '[ none | [ all | <ident> ]# ] || <time> || <single-timing-function> || <time>',
  965. '<width-height>': '<len-pct> | min-content | max-content | ' +
  966. 'fit-content | fit-content( <len-pct> ) | -moz-available | -webkit-fill-available',
  967. '<will-change>': 'auto | <animateable-feature>#',
  968. };
  969. //#endregion
  970. //#region Colors
  971. const Colors = Object.assign(Object.create(null), {
  972. // 'currentColor' color keyword
  973. // https://www.w3.org/TR/css3-color/#currentcolor
  974. currentColor: '',
  975. transparent: '#0000',
  976. aliceblue: '#f0f8ff',
  977. antiquewhite: '#faebd7',
  978. aqua: '#00ffff',
  979. aquamarine: '#7fffd4',
  980. azure: '#f0ffff',
  981. beige: '#f5f5dc',
  982. bisque: '#ffe4c4',
  983. black: '#000000',
  984. blanchedalmond: '#ffebcd',
  985. blue: '#0000ff',
  986. blueviolet: '#8a2be2',
  987. brown: '#a52a2a',
  988. burlywood: '#deb887',
  989. cadetblue: '#5f9ea0',
  990. chartreuse: '#7fff00',
  991. chocolate: '#d2691e',
  992. coral: '#ff7f50',
  993. cornflowerblue: '#6495ed',
  994. cornsilk: '#fff8dc',
  995. crimson: '#dc143c',
  996. cyan: '#00ffff',
  997. darkblue: '#00008b',
  998. darkcyan: '#008b8b',
  999. darkgoldenrod: '#b8860b',
  1000. darkgray: '#a9a9a9',
  1001. darkgrey: '#a9a9a9',
  1002. darkgreen: '#006400',
  1003. darkkhaki: '#bdb76b',
  1004. darkmagenta: '#8b008b',
  1005. darkolivegreen: '#556b2f',
  1006. darkorange: '#ff8c00',
  1007. darkorchid: '#9932cc',
  1008. darkred: '#8b0000',
  1009. darksalmon: '#e9967a',
  1010. darkseagreen: '#8fbc8f',
  1011. darkslateblue: '#483d8b',
  1012. darkslategray: '#2f4f4f',
  1013. darkslategrey: '#2f4f4f',
  1014. darkturquoise: '#00ced1',
  1015. darkviolet: '#9400d3',
  1016. deeppink: '#ff1493',
  1017. deepskyblue: '#00bfff',
  1018. dimgray: '#696969',
  1019. dimgrey: '#696969',
  1020. dodgerblue: '#1e90ff',
  1021. firebrick: '#b22222',
  1022. floralwhite: '#fffaf0',
  1023. forestgreen: '#228b22',
  1024. fuchsia: '#ff00ff',
  1025. gainsboro: '#dcdcdc',
  1026. ghostwhite: '#f8f8ff',
  1027. gold: '#ffd700',
  1028. goldenrod: '#daa520',
  1029. gray: '#808080',
  1030. grey: '#808080',
  1031. green: '#008000',
  1032. greenyellow: '#adff2f',
  1033. honeydew: '#f0fff0',
  1034. hotpink: '#ff69b4',
  1035. indianred: '#cd5c5c',
  1036. indigo: '#4b0082',
  1037. ivory: '#fffff0',
  1038. khaki: '#f0e68c',
  1039. lavender: '#e6e6fa',
  1040. lavenderblush: '#fff0f5',
  1041. lawngreen: '#7cfc00',
  1042. lemonchiffon: '#fffacd',
  1043. lightblue: '#add8e6',
  1044. lightcoral: '#f08080',
  1045. lightcyan: '#e0ffff',
  1046. lightgoldenrodyellow: '#fafad2',
  1047. lightgray: '#d3d3d3',
  1048. lightgrey: '#d3d3d3',
  1049. lightgreen: '#90ee90',
  1050. lightpink: '#ffb6c1',
  1051. lightsalmon: '#ffa07a',
  1052. lightseagreen: '#20b2aa',
  1053. lightskyblue: '#87cefa',
  1054. lightslategray: '#778899',
  1055. lightslategrey: '#778899',
  1056. lightsteelblue: '#b0c4de',
  1057. lightyellow: '#ffffe0',
  1058. lime: '#00ff00',
  1059. limegreen: '#32cd32',
  1060. linen: '#faf0e6',
  1061. magenta: '#ff00ff',
  1062. maroon: '#800000',
  1063. mediumaquamarine: '#66cdaa',
  1064. mediumblue: '#0000cd',
  1065. mediumorchid: '#ba55d3',
  1066. mediumpurple: '#9370db',
  1067. mediumseagreen: '#3cb371',
  1068. mediumslateblue: '#7b68ee',
  1069. mediumspringgreen: '#00fa9a',
  1070. mediumturquoise: '#48d1cc',
  1071. mediumvioletred: '#c71585',
  1072. midnightblue: '#191970',
  1073. mintcream: '#f5fffa',
  1074. mistyrose: '#ffe4e1',
  1075. moccasin: '#ffe4b5',
  1076. navajowhite: '#ffdead',
  1077. navy: '#000080',
  1078. oldlace: '#fdf5e6',
  1079. olive: '#808000',
  1080. olivedrab: '#6b8e23',
  1081. orange: '#ffa500',
  1082. orangered: '#ff4500',
  1083. orchid: '#da70d6',
  1084. palegoldenrod: '#eee8aa',
  1085. palegreen: '#98fb98',
  1086. paleturquoise: '#afeeee',
  1087. palevioletred: '#db7093',
  1088. papayawhip: '#ffefd5',
  1089. peachpuff: '#ffdab9',
  1090. peru: '#cd853f',
  1091. pink: '#ffc0cb',
  1092. plum: '#dda0dd',
  1093. powderblue: '#b0e0e6',
  1094. purple: '#800080',
  1095. rebeccapurple: '#663399',
  1096. red: '#ff0000',
  1097. rosybrown: '#bc8f8f',
  1098. royalblue: '#4169e1',
  1099. saddlebrown: '#8b4513',
  1100. salmon: '#fa8072',
  1101. sandybrown: '#f4a460',
  1102. seagreen: '#2e8b57',
  1103. seashell: '#fff5ee',
  1104. sienna: '#a0522d',
  1105. silver: '#c0c0c0',
  1106. skyblue: '#87ceeb',
  1107. slateblue: '#6a5acd',
  1108. slategray: '#708090',
  1109. slategrey: '#708090',
  1110. snow: '#fffafa',
  1111. springgreen: '#00ff7f',
  1112. steelblue: '#4682b4',
  1113. tan: '#d2b48c',
  1114. teal: '#008080',
  1115. thistle: '#d8bfd8',
  1116. tomato: '#ff6347',
  1117. turquoise: '#40e0d0',
  1118. violet: '#ee82ee',
  1119. wheat: '#f5deb3',
  1120. white: '#ffffff',
  1121. whitesmoke: '#f5f5f5',
  1122. yellow: '#ffff00',
  1123. yellowgreen: '#9acd32',
  1124. // old = CSS2 system colors: https://www.w3.org/TR/css3-color/#css2-system
  1125. // new = CSS4 system colors: https://drafts.csswg.org/css-color-4/#css-system-colors
  1126. ActiveBorder: '',
  1127. ActiveCaption: '',
  1128. ActiveText: '', // new
  1129. AppWorkspace: '',
  1130. Background: '',
  1131. ButtonBorder: '', // new
  1132. ButtonFace: '', // old+new
  1133. ButtonHighlight: '',
  1134. ButtonShadow: '',
  1135. ButtonText: '', // old+new
  1136. Canvas: '', // new
  1137. CanvasText: '', // new
  1138. CaptionText: '',
  1139. Field: '', // new
  1140. FieldText: '', // new
  1141. GrayText: '', // old+new
  1142. Highlight: '', // old+new
  1143. HighlightText: '', // old+new
  1144. InactiveBorder: '',
  1145. InactiveCaption: '',
  1146. InactiveCaptionText: '',
  1147. InfoBackground: '',
  1148. InfoText: '',
  1149. LinkText: '', // new
  1150. Mark: '', // new
  1151. MarkText: '', // new
  1152. Menu: '',
  1153. MenuText: '',
  1154. Scrollbar: '',
  1155. ThreeDDarkShadow: '',
  1156. ThreeDFace: '',
  1157. ThreeDHighlight: '',
  1158. ThreeDLightShadow: '',
  1159. ThreeDShadow: '',
  1160. VisitedText: '', // new
  1161. Window: '',
  1162. WindowFrame: '',
  1163. WindowText: '',
  1164. });
  1165. const ColorsLC = new Set(Object.keys(Colors).map(lower));
  1166. //#endregion
  1167. //#region Tokens
  1168. /* https://www.w3.org/TR/css3-syntax/#lexical */
  1169. /** @type {Object<string,number|Object>} */
  1170. const Tokens = Object.assign([], {
  1171. EOF: {}, // must be the first token
  1172. }, {
  1173. // HTML-style comments
  1174. CDC: {},
  1175. CDO: {},
  1176. // ignorables
  1177. COMMENT: {hide: true},
  1178. S: {},
  1179. // attribute equality
  1180. DASHMATCH: {text: '|='},
  1181. INCLUDES: {text: '~='},
  1182. PREFIXMATCH: {text: '^='},
  1183. SUBSTRINGMATCH: {text: '*='},
  1184. SUFFIXMATCH: {text: '$='},
  1185. // identifier types
  1186. HASH: {},
  1187. IDENT: {},
  1188. STRING: {},
  1189. // at-keywords
  1190. CHARSET_SYM: {text: '@charset'},
  1191. DOCUMENT_SYM: {text: ['@document', '@-moz-document']},
  1192. FONT_FACE_SYM: {text: '@font-face'},
  1193. IMPORT_SYM: {text: '@import'},
  1194. KEYFRAMES_SYM: {text: ['@keyframes', '@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes']},
  1195. MEDIA_SYM: {text: '@media'},
  1196. NAMESPACE_SYM: {text: '@namespace'},
  1197. PAGE_SYM: {text: '@page'},
  1198. SUPPORTS_SYM: {text: '@supports'},
  1199. UNKNOWN_SYM: {},
  1200. VIEWPORT_SYM: {text: ['@viewport', '@-ms-viewport', '@-o-viewport']},
  1201. // measurements
  1202. ANGLE: {},
  1203. DIMENSION: {},
  1204. FREQ: {},
  1205. LENGTH: {},
  1206. NUMBER: {},
  1207. PERCENTAGE: {},
  1208. TIME: {},
  1209. // functions
  1210. FUNCTION: {},
  1211. URI: {},
  1212. // Unicode ranges
  1213. UNICODE_RANGE: {},
  1214. // invalid string
  1215. INVALID: {},
  1216. // combinators
  1217. COLUMN: {text: '||'},
  1218. COMMA: {text: ','},
  1219. GREATER: {text: '>'},
  1220. PLUS: {text: '+'},
  1221. TILDE: {text: '~'},
  1222. // modifier
  1223. ANY: {text: ['any', '-webkit-any', '-moz-any']},
  1224. IS: {},
  1225. NOT: {},
  1226. WHERE: {},
  1227. // CSS3 Paged Media
  1228. BOTTOMCENTER_SYM: {text: '@bottom-center'},
  1229. BOTTOMLEFTCORNER_SYM: {text: '@bottom-left-corner'},
  1230. BOTTOMLEFT_SYM: {text: '@bottom-left'},
  1231. BOTTOMRIGHTCORNER_SYM: {text: '@bottom-right-corner'},
  1232. BOTTOMRIGHT_SYM: {text: '@bottom-right'},
  1233. LEFTBOTTOM_SYM: {text: '@left-bottom'},
  1234. LEFTMIDDLE_SYM: {text: '@left-middle'},
  1235. LEFTTOP_SYM: {text: '@left-top'},
  1236. RIGHTBOTTOM_SYM: {text: '@right-bottom'},
  1237. RIGHTMIDDLE_SYM: {text: '@right-middle'},
  1238. RIGHTTOP_SYM: {text: '@right-top'},
  1239. TOPCENTER_SYM: {text: '@top-center'},
  1240. TOPLEFTCORNER_SYM: {text: '@top-left-corner'},
  1241. TOPLEFT_SYM: {text: '@top-left'},
  1242. TOPRIGHTCORNER_SYM: {text: '@top-right-corner'},
  1243. TOPRIGHT_SYM: {text: '@top-right'},
  1244. /* CSS3 Media Queries */
  1245. RESOLUTION: {state: 'media'},
  1246. /*
  1247. * The following token names are not defined in any CSS specification.
  1248. */
  1249. CHAR: {},
  1250. COLON: {text: ':'},
  1251. DOT: {text: '.'},
  1252. EQUALS: {text: '='},
  1253. IE_FUNCTION: {},
  1254. IMPORTANT: {},
  1255. LBRACE: {text: '{', endChar: '}'},
  1256. LBRACKET: {text: '[', endChar: ']'},
  1257. LPAREN: {text: '(', endChar: ')'},
  1258. MINUS: {text: '-'},
  1259. PIPE: {text: '|'},
  1260. RBRACE: {text: '}'},
  1261. RBRACKET: {text: ']'},
  1262. RPAREN: {text: ')'},
  1263. SEMICOLON: {text: ';'},
  1264. SLASH: {text: '/'},
  1265. STAR: {text: '*'},
  1266. USO_VAR: {},
  1267. });
  1268. // make Tokens an array of tokens, store the index in original prop, add 'name' to each token
  1269. const typeMap = new Map();
  1270. for (const [k, val] of Object.entries(Tokens)) {
  1271. const index = Tokens[k] = Tokens.length;
  1272. val.name = k;
  1273. Tokens.push(val);
  1274. const {text} = val;
  1275. if (text) {
  1276. for (const item of Array.isArray(text) ? text : [text]) {
  1277. typeMap.set(item, index);
  1278. }
  1279. }
  1280. }
  1281. Tokens.UNKNOWN = -1;
  1282. Tokens.name = index => (Tokens[index] || {}).name;
  1283. Tokens.type = text => typeMap.get(text) || Tokens.UNKNOWN;
  1284. const TT = {
  1285. attrMatch: [
  1286. Tokens.PREFIXMATCH,
  1287. Tokens.SUFFIXMATCH,
  1288. Tokens.SUBSTRINGMATCH,
  1289. Tokens.EQUALS,
  1290. Tokens.INCLUDES,
  1291. Tokens.DASHMATCH,
  1292. ],
  1293. combinator: [
  1294. Tokens.PLUS,
  1295. Tokens.GREATER,
  1296. Tokens.TILDE,
  1297. Tokens.COLUMN,
  1298. ],
  1299. cruft: [
  1300. Tokens.S,
  1301. Tokens.CDO,
  1302. Tokens.CDC,
  1303. ],
  1304. expression: [
  1305. Tokens.PLUS,
  1306. Tokens.MINUS,
  1307. Tokens.DIMENSION,
  1308. Tokens.NUMBER,
  1309. Tokens.STRING,
  1310. Tokens.IDENT,
  1311. Tokens.LENGTH,
  1312. Tokens.FREQ,
  1313. Tokens.ANGLE,
  1314. Tokens.TIME,
  1315. Tokens.RESOLUTION,
  1316. Tokens.SLASH,
  1317. ],
  1318. identString: [
  1319. Tokens.IDENT,
  1320. Tokens.STRING,
  1321. Tokens.USO_VAR,
  1322. ],
  1323. LParenBracket: [
  1324. Tokens.LPAREN,
  1325. Tokens.LBRACKET,
  1326. ],
  1327. LParenBracketBrace: [
  1328. Tokens.LPAREN,
  1329. Tokens.LBRACKET,
  1330. Tokens.LBRACE,
  1331. ],
  1332. margins: [
  1333. Tokens.TOPLEFTCORNER_SYM,
  1334. Tokens.TOPLEFT_SYM,
  1335. Tokens.TOPCENTER_SYM,
  1336. Tokens.TOPRIGHT_SYM,
  1337. Tokens.TOPRIGHTCORNER_SYM,
  1338. Tokens.BOTTOMLEFTCORNER_SYM,
  1339. Tokens.BOTTOMLEFT_SYM,
  1340. Tokens.BOTTOMCENTER_SYM,
  1341. Tokens.BOTTOMRIGHT_SYM,
  1342. Tokens.BOTTOMRIGHTCORNER_SYM,
  1343. Tokens.LEFTTOP_SYM,
  1344. Tokens.LEFTMIDDLE_SYM,
  1345. Tokens.LEFTBOTTOM_SYM,
  1346. Tokens.RIGHTTOP_SYM,
  1347. Tokens.RIGHTMIDDLE_SYM,
  1348. Tokens.RIGHTBOTTOM_SYM,
  1349. ],
  1350. op: [
  1351. Tokens.SLASH,
  1352. Tokens.COMMA,
  1353. ],
  1354. opInFunc: [
  1355. Tokens.SLASH,
  1356. Tokens.COMMA,
  1357. Tokens.PLUS,
  1358. Tokens.STAR,
  1359. Tokens.MINUS,
  1360. ],
  1361. plusMinus: [
  1362. Tokens.MINUS,
  1363. Tokens.PLUS,
  1364. ],
  1365. pseudo: [
  1366. Tokens.FUNCTION,
  1367. Tokens.IDENT,
  1368. ],
  1369. semiS: [
  1370. Tokens.SEMICOLON,
  1371. Tokens.S,
  1372. ],
  1373. stringUri: [
  1374. Tokens.STRING,
  1375. Tokens.URI,
  1376. Tokens.USO_VAR,
  1377. ],
  1378. term: [
  1379. Tokens.NUMBER,
  1380. Tokens.PERCENTAGE,
  1381. Tokens.LENGTH,
  1382. Tokens.ANGLE,
  1383. Tokens.TIME,
  1384. Tokens.DIMENSION,
  1385. Tokens.FREQ,
  1386. Tokens.STRING,
  1387. Tokens.IDENT,
  1388. Tokens.URI,
  1389. Tokens.UNICODE_RANGE,
  1390. Tokens.USO_VAR,
  1391. ],
  1392. usoS: [
  1393. Tokens.USO_VAR,
  1394. Tokens.S,
  1395. ],
  1396. };
  1397. //#endregion
  1398. //#region StringReader
  1399. class StringReader {
  1400. constructor(text) {
  1401. this._input = text.replace(/\r\n?/g, '\n');
  1402. this._line = 1;
  1403. this._col = 1;
  1404. this._cursor = 0;
  1405. }
  1406. eof() {
  1407. return this._cursor >= this._input.length;
  1408. }
  1409. peek(count = 1) {
  1410. return this._input[this._cursor + count - 1] || null;
  1411. }
  1412. peekTest(stickyRx) {
  1413. stickyRx.lastIndex = this._cursor;
  1414. return stickyRx.test(this._input);
  1415. }
  1416. read() {
  1417. const c = this._input[this._cursor];
  1418. if (!c) return null;
  1419. if (c === '\n') {
  1420. this._line++;
  1421. this._col = 1;
  1422. } else {
  1423. this._col++;
  1424. }
  1425. this._cursor++;
  1426. return c;
  1427. }
  1428. mark() {
  1429. this._bookmark = {
  1430. cursor: this._cursor,
  1431. line: this._line,
  1432. col: this._col,
  1433. };
  1434. }
  1435. reset() {
  1436. if (this._bookmark) {
  1437. this._cursor = this._bookmark.cursor;
  1438. this._line = this._bookmark.line;
  1439. this._col = this._bookmark.col;
  1440. delete this._bookmark;
  1441. }
  1442. }
  1443. /**
  1444. * Reads up to and including the given string.
  1445. * @param {String} pattern The string to read.
  1446. * @return {String} The string when it is found.
  1447. * @throws Error when the string pattern is not found.
  1448. */
  1449. readTo(pattern) {
  1450. const i = this._input.indexOf(pattern, this._cursor);
  1451. if (i < 0) throw new Error(`Expected '${pattern}'.`);
  1452. return this.readCount(i - this._cursor + pattern.length);
  1453. }
  1454. /**
  1455. * Reads characters that match either text or a regular expression and returns those characters.
  1456. * If a match is found, the row and column are adjusted.
  1457. * @param {String|RegExp} matcher
  1458. * @return {String} string or null if there was no match.
  1459. */
  1460. readMatch(matcher) {
  1461. if (matcher.sticky) {
  1462. matcher.lastIndex = this._cursor;
  1463. return matcher.test(this._input) ?
  1464. this.readCount(RegExp.lastMatch.length) :
  1465. null;
  1466. }
  1467. if (typeof matcher === 'string') {
  1468. if (this._input[this._cursor] === matcher[0] &&
  1469. this._input.substr(this._cursor, matcher.length) === matcher) {
  1470. return this.readCount(matcher.length);
  1471. }
  1472. } else if (matcher instanceof RegExp) {
  1473. if (matcher.test(this._input.substr(this._cursor))) {
  1474. return this.readCount(RegExp.lastMatch.length);
  1475. }
  1476. }
  1477. return null;
  1478. }
  1479. /**
  1480. * Reads a given number of characters. If the end of the input is reached,
  1481. * it reads only the remaining characters and does not throw an error.
  1482. * @param {int} count The number of characters to read.
  1483. * @return {String} string or null if already at EOF
  1484. */
  1485. readCount(count) {
  1486. const len = this._input.length;
  1487. if (this._cursor >= len) return null;
  1488. if (!count) return '';
  1489. const text = this._input.substr(this._cursor, count);
  1490. this._cursor = Math.min(this._cursor + count, len);
  1491. let prev = -1;
  1492. for (let i = 0; (i = text.indexOf('\n', i)) >= 0; prev = i, i++) this._line++;
  1493. this._col = prev < 0 ? this._col + count : count - prev;
  1494. return text;
  1495. }
  1496. }
  1497. //#endregion
  1498. //#region Matcher
  1499. /**
  1500. * Reuses a Matcher for a ValidationTypes definition string instead of reparsing it.
  1501. * @type {Map<string, Matcher>}
  1502. */
  1503. const matcherCache = new Map();
  1504. /**
  1505. * This class implements a combinator library for matcher functions.
  1506. * https://developer.mozilla.org/docs/Web/CSS/Value_definition_syntax#Component_value_combinators
  1507. */
  1508. class Matcher {
  1509. constructor(matchFunc, toString, options) {
  1510. this.matchFunc = matchFunc;
  1511. /** @type {function(?number):string} */
  1512. this.toString = typeof toString === 'function' ? toString : () => toString;
  1513. /** @type {?Matcher[]} */
  1514. this.options = options;
  1515. }
  1516. /**
  1517. * @param {PropertyValueIterator} e
  1518. * @return {?boolean}
  1519. */
  1520. match(e) {
  1521. e._marks.push(e._i);
  1522. return e.popMark(this.matchFunc(e));
  1523. }
  1524. braces(min, max, marker, sep) {
  1525. return new Matcher(Matcher.funcBraces, Matcher.toStringBraces, {
  1526. min, max, marker,
  1527. sep: sep && Matcher.seq(sep, this),
  1528. embraced: this,
  1529. });
  1530. }
  1531. static parse(str) {
  1532. let m = matcherCache.get(str);
  1533. if (m) return m;
  1534. m = Matcher.doParse(str);
  1535. matcherCache.set(str, m);
  1536. return m;
  1537. }
  1538. /** Simple recursive-descent grammar to build matchers from strings. */
  1539. static doParse(str) {
  1540. const reader = new StringReader(str);
  1541. const result = Matcher.parseGrammar(reader);
  1542. if (!reader.eof()) {
  1543. throw new Error('Internal grammar error. ' +
  1544. `Expected end of string at ${reader._cursor}: ${reader._input}.`);
  1545. }
  1546. return result;
  1547. }
  1548. static cast(m) {
  1549. return m instanceof Matcher ? m : Matcher.parse(m);
  1550. }
  1551. // Matcher for a single type.
  1552. static fromType(type) {
  1553. let m = matcherCache.get(type);
  1554. if (m) return m;
  1555. m = new Matcher(Matcher.funcFromType, type, type);
  1556. matcherCache.set(type, m);
  1557. return m;
  1558. }
  1559. /**
  1560. * @param {string} name - functio name
  1561. * @param {Matcher} body - matcher for function body
  1562. * @returns {Matcher}
  1563. */
  1564. static func(name, body) {
  1565. return new Matcher(Matcher.funcFunc, Matcher.toStringFunc, {name, body});
  1566. }
  1567. // Matcher for one or more juxtaposed words, which all must occur, in the given order.
  1568. static seq(...args) {
  1569. const ms = args.map(Matcher.cast);
  1570. if (ms.length === 1) return ms[0];
  1571. return new Matcher(Matcher.funcSeq, Matcher.toStringSeq, ms);
  1572. }
  1573. // Matcher for one or more alternatives, where exactly one must occur.
  1574. static alt(...args) {
  1575. const ms = args.map(Matcher.cast);
  1576. if (ms.length === 1) return ms[0];
  1577. return new Matcher(Matcher.funcAlt, Matcher.toStringAlt, ms);
  1578. }
  1579. /**
  1580. * Matcher for two or more options: double bar (||) and double ampersand (&&) operators,
  1581. * as well as variants of && where some of the alternatives are optional.
  1582. * This will backtrack through even successful matches to try to
  1583. * maximize the number of items matched.
  1584. */
  1585. static many(required, ...args) {
  1586. const ms = args.map(Matcher.cast);
  1587. const m = new Matcher(Matcher.funcMany, Matcher.toStringMany, ms);
  1588. m.required = required === true ? Array(ms.length).fill(true) : required;
  1589. return m;
  1590. }
  1591. /**************************** matchFunc **********************/
  1592. /**
  1593. * @this {Matcher}
  1594. * @param {PropertyValueIterator} expr
  1595. */
  1596. static funcAlt(expr) {
  1597. return this.options.some(Matcher.invoke, expr);
  1598. }
  1599. /**
  1600. * @this {Matcher}
  1601. * @param {PropertyValueIterator} expr
  1602. */
  1603. static funcBraces(expr) {
  1604. const {min, max, sep, embraced} = this.options;
  1605. let i = 0;
  1606. while (i < max && (i && sep || embraced).match(expr)) {
  1607. i++;
  1608. }
  1609. return i >= min;
  1610. }
  1611. /**
  1612. * @this {Matcher}
  1613. * @param {PropertyValueIterator} expr
  1614. */
  1615. static funcFromType(expr) {
  1616. const part = expr.peek();
  1617. if (!part) return;
  1618. const type = this.options;
  1619. let result, m;
  1620. if (part.isVar) {
  1621. result = true;
  1622. } else if (!type.startsWith('<')) {
  1623. result = vtIsLiteral(type, part);
  1624. } else if ((m = VTSimple[type])) {
  1625. result = m.call(VTSimple, part);
  1626. } else {
  1627. m = VTComplex[type];
  1628. return m instanceof Matcher ?
  1629. m.match(expr) :
  1630. m.call(VTComplex, expr);
  1631. }
  1632. if (!result && expr.tryAttr && part.isAttr) {
  1633. result = vtIsAttr(part);
  1634. }
  1635. if (result) expr.next();
  1636. return result;
  1637. }
  1638. /**
  1639. * @this {Matcher}
  1640. * @param {PropertyValueIterator} expr
  1641. */
  1642. static funcFunc(expr) {
  1643. const p = expr.peek();
  1644. if (p && p.expr && p.tokenType === Tokens.FUNCTION && lowerCmp(p.name, this.options.name)) {
  1645. let res = hasVarParts(p.expr);
  1646. if (!res) {
  1647. const vi = new PropertyValueIterator(p.expr); // eslint-disable-line no-use-before-define
  1648. res = this.options.body.match(vi) && !vi.hasNext;
  1649. }
  1650. return res && expr.next();
  1651. }
  1652. }
  1653. /**
  1654. * @this {PropertyValueIterator}
  1655. * @param {Matcher} m
  1656. */
  1657. static invoke(m) {
  1658. return m.match(this);
  1659. }
  1660. /**
  1661. * @this {Matcher}
  1662. * @param {PropertyValueIterator} expr
  1663. */
  1664. static funcMany(expr) {
  1665. const seen = [];
  1666. const {/** @type {Matcher[]} */options: ms, required} = this;
  1667. let max = 0;
  1668. let pass = 0;
  1669. // If couldn't get a complete match, retrace our steps to make the
  1670. // match with the maximum # of required elements.
  1671. if (!tryMatch(0)) {
  1672. pass++;
  1673. tryMatch(0);
  1674. }
  1675. if (required === false) {
  1676. return max > 0;
  1677. }
  1678. // Use finer-grained specification of which matchers are required.
  1679. for (let i = 0; i < ms.length; i++) {
  1680. if (required[i] && !seen[i]) {
  1681. return false;
  1682. }
  1683. }
  1684. return true;
  1685. function tryMatch(matchCount) {
  1686. for (let i = 0; i < ms.length; i++) {
  1687. if (seen[i]) continue;
  1688. expr.mark();
  1689. if (!ms[i].matchFunc(expr)) {
  1690. expr.popMark(true);
  1691. continue;
  1692. }
  1693. seen[i] = true;
  1694. // Increase matchCount if this was a required element
  1695. // (or if all the elements are optional)
  1696. if (tryMatch(matchCount + (required === false || required[i] ? 1 : 0))) {
  1697. expr.popMark(true);
  1698. return true;
  1699. }
  1700. // Backtrack: try *not* matching using this rule, and
  1701. // let's see if it leads to a better overall match.
  1702. expr.popMark();
  1703. seen[i] = false;
  1704. }
  1705. if (pass === 0) {
  1706. max = Math.max(matchCount, max);
  1707. return matchCount === ms.length;
  1708. } else {
  1709. return matchCount === max;
  1710. }
  1711. }
  1712. }
  1713. /**
  1714. * @this {Matcher}
  1715. * @param {PropertyValueIterator} expr
  1716. */
  1717. static funcSeq(expr) {
  1718. return this.options.every(Matcher.invoke, expr);
  1719. }
  1720. /**************************** toStringFunc **********************/
  1721. /** @this {Matcher} */
  1722. static toStringAlt(prec) {
  1723. const p = Matcher.prec.ALT;
  1724. const s = this.options.map(m => m.toString(p)).join(' | ');
  1725. return prec > p ? `[ ${s} ]` : s;
  1726. }
  1727. /** @this {Matcher} */
  1728. static toStringBraces() {
  1729. const {marker, min, max, embraced} = this.options;
  1730. return embraced.toString(Matcher.prec.MOD) + (
  1731. !marker || marker === '#'
  1732. ? `${marker || ''}{${min}${min === max ? '' : ',' + max}}`
  1733. : marker);
  1734. }
  1735. /** @this {Matcher} */
  1736. static toStringFunc() {
  1737. const {name, body} = this.options;
  1738. return `${name}( ${body} )`;
  1739. }
  1740. /** @this {Matcher} */
  1741. static toStringMany(prec) {
  1742. const {options: ms, required} = this;
  1743. const p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND;
  1744. const s = ms.map((m, i) => {
  1745. if (required !== false && !required[i]) {
  1746. const str = m.toString(Matcher.prec.MOD);
  1747. return str.endsWith('?') ? str : str + '?';
  1748. }
  1749. return m.toString(p);
  1750. }).join(required === false ? ' || ' : ' && ');
  1751. return prec > p ? `[ ${s} ]` : s;
  1752. }
  1753. /** @this {Matcher} */
  1754. static toStringSeq(prec) {
  1755. const p = Matcher.prec.SEQ;
  1756. const s = this.options.map(m => m.toString(p)).join(' ');
  1757. return prec > p ? `[ ${s} ]` : s;
  1758. }
  1759. }
  1760. // Precedence table of combinators.
  1761. Matcher.prec = {
  1762. MOD: 5,
  1763. SEQ: 4,
  1764. ANDAND: 3,
  1765. OROR: 2,
  1766. ALT: 1,
  1767. };
  1768. Matcher.parseGrammar = (() => {
  1769. /** @type {StringReader} */
  1770. let reader;
  1771. return newReader => {
  1772. reader = newReader;
  1773. return alt();
  1774. };
  1775. function alt() {
  1776. // alt = oror (" | " oror)*
  1777. const m = [oror()];
  1778. while (reader.readMatch(' | ')) {
  1779. m.push(oror());
  1780. }
  1781. return m.length === 1 ? m[0] : Matcher.alt(...m);
  1782. }
  1783. // Matcher for two or more options in any order, at least one must be present.
  1784. function oror() {
  1785. // oror = andand ( " || " andand)*
  1786. const m = [andand()];
  1787. while (reader.readMatch(' || ')) {
  1788. m.push(andand());
  1789. }
  1790. return m.length === 1 ? m[0] : Matcher.many(false, ...m);
  1791. }
  1792. // Matcher for two or more options in any order, all mandatory.
  1793. function andand() {
  1794. // andand = seq ( " && " seq)*
  1795. const m = [seq()];
  1796. let reqPrev = !isOptional(m[0]);
  1797. const required = [reqPrev];
  1798. while (reader.readMatch(' && ')) {
  1799. const item = seq();
  1800. const req = !isOptional(item);
  1801. // Matcher.many apparently can't handle optional items first
  1802. if (req && !reqPrev) {
  1803. m.unshift(item);
  1804. required.unshift(req);
  1805. } else {
  1806. m.push(item);
  1807. required.push(req);
  1808. reqPrev = req;
  1809. }
  1810. }
  1811. return m.length === 1 ? m[0] : Matcher.many(required, ...m);
  1812. }
  1813. function seq() {
  1814. // seq = mod ( " " mod)*
  1815. const ms = [mod()];
  1816. while (reader.readMatch(/\s(?![&|)\]])/y)) {
  1817. ms.push(mod());
  1818. }
  1819. return Matcher.seq(...ms);
  1820. }
  1821. function mod() {
  1822. // mod = term ( "?" | "*" | "+" | "#" | "{<num>,<num>}" )?
  1823. // term = <nt> | literal | "[ " expression " ]" | fn "( " alt " )"
  1824. let m, fn;
  1825. if (reader.readMatch('[ ')) {
  1826. m = alt();
  1827. eat(' ]');
  1828. } else if ((fn = reader.readMatch(/[-\w]+(?=\(\s)/y))) {
  1829. reader.readCount(2);
  1830. m = alt();
  1831. eat(' )');
  1832. return Matcher.func(fn, m);
  1833. } else {
  1834. m = Matcher.fromType(eat(/<[^>]+>|[^\s?*+#{]+/y));
  1835. }
  1836. reader.mark();
  1837. let hash;
  1838. switch (reader.read()) {
  1839. case '?': return m.braces(0, 1, '?');
  1840. case '*': return m.braces(0, Infinity, '*');
  1841. case '+': return m.braces(1, Infinity, '+');
  1842. case '#':
  1843. if (reader.peek() !== '{') return m.braces(1, Infinity, '#', ',');
  1844. reader.read();
  1845. hash = '#';
  1846. // fallthrough
  1847. case '{': {
  1848. const [min, max] = eat(/\s*\d+\s*(,\s*\d+\s*)?}/y).trim().split(/\s+|,|}/);
  1849. return m.braces(min | 0, max | min | 0, hash, hash && ',');
  1850. }
  1851. default:
  1852. reader.reset();
  1853. }
  1854. return m;
  1855. }
  1856. function eat(pattern) {
  1857. const s = reader.readMatch(pattern);
  1858. if (s != null) return s;
  1859. throw new Error('Internal grammar error. ' +
  1860. `Expected ${pattern} at ${reader._cursor} in ${reader._input}`);
  1861. }
  1862. function isOptional({options}) {
  1863. return options && options.marker === '?';
  1864. }
  1865. })();
  1866. //#endregion
  1867. //#region EventTarget
  1868. class EventTarget {
  1869. constructor() {
  1870. this._listeners = new Map();
  1871. }
  1872. addListener(type, fn) {
  1873. let list = this._listeners.get(type);
  1874. if (!list) this._listeners.set(type, (list = new Set()));
  1875. list.add(fn);
  1876. }
  1877. fire(event) {
  1878. if (typeof event === 'string') {
  1879. event = {type: event};
  1880. }
  1881. event.target = this;
  1882. const list = this._listeners.get(event.type);
  1883. if (list) {
  1884. for (const fn of list) {
  1885. fn.call(this, event);
  1886. }
  1887. }
  1888. }
  1889. removeListener(type, fn) {
  1890. const list = this._listeners.get(type);
  1891. if (list) list.delete(fn);
  1892. }
  1893. }
  1894. //#endregion
  1895. //#region Syntax units
  1896. /**
  1897. * @property {boolean|number} [_isAttr]
  1898. * @property {boolean|number} [_isCalc]
  1899. * @property {boolean|number} [_isVar]
  1900. */
  1901. class SyntaxUnit {
  1902. constructor(text, pos, type, extras) {
  1903. this.col = pos.col;
  1904. this.line = pos.line;
  1905. this.offset = pos.offset;
  1906. this.text = text;
  1907. this.type = type;
  1908. if (extras) Object.assign(this, extras);
  1909. }
  1910. valueOf() {
  1911. return this.text;
  1912. }
  1913. toString() {
  1914. return this.text;
  1915. }
  1916. get isAttr() {
  1917. let res = this._isAttr;
  1918. if (res === 0) res = this._isAttr = lowerCmp(this.name, 'attr');
  1919. return res;
  1920. }
  1921. get isCalc() {
  1922. let res = this._isCalc;
  1923. if (res === 0) res = this._isCalc = rxCalc.test(this.text);
  1924. return res;
  1925. }
  1926. get isVar() {
  1927. let res = this._isVar;
  1928. if (res === 0) {
  1929. const pp = this.expr && this.expr.parts;
  1930. res = this._isVar = pp && pp.length > 0 && (
  1931. (pp.length === 1 || pp[1].text === ',') && (
  1932. pp[0].type === 'custom-property' && lowerCmp(this.name, 'var') ||
  1933. pp[0].type === 'identifier' && lowerCmp(this.name, 'env')));
  1934. }
  1935. return res;
  1936. }
  1937. static fromToken(token) {
  1938. return token && new SyntaxUnit(token.value, token);
  1939. }
  1940. /**
  1941. * @param {SyntaxUnit} unit
  1942. * @param {SyntaxUnit|parserlib.Token} token
  1943. * @returns {SyntaxUnit}
  1944. */
  1945. static addFuncInfo(unit, {expr, name} = unit) {
  1946. const isColor = expr && expr.parts && /^(rgb|hsl)a?$/i.test(name);
  1947. if (isColor) unit.type = 'color';
  1948. unit._isAttr =
  1949. unit._isCalc =
  1950. unit._isVar = isColor ? false : 0;
  1951. return unit;
  1952. }
  1953. }
  1954. class SyntaxError extends Error {
  1955. constructor(message, pos) {
  1956. super();
  1957. this.name = this.constructor.name;
  1958. this.col = pos.col;
  1959. this.line = pos.line;
  1960. this.message = message;
  1961. }
  1962. }
  1963. class ValidationError extends Error {
  1964. constructor(message, pos) {
  1965. super();
  1966. this.col = pos.col;
  1967. this.line = pos.line;
  1968. this.message = message;
  1969. }
  1970. }
  1971. // individual media query
  1972. class MediaQuery extends SyntaxUnit {
  1973. constructor(modifier, mediaType, features, pos) {
  1974. const text = (modifier ? modifier + ' ' : '') +
  1975. (mediaType ? mediaType : '') +
  1976. (mediaType && features.length > 0 ? ' and ' : '') +
  1977. features.join(' and ');
  1978. super(text, pos, TYPES.MEDIA_QUERY_TYPE);
  1979. this.modifier = modifier;
  1980. this.mediaType = mediaType;
  1981. this.features = features;
  1982. }
  1983. }
  1984. // e.g. max-width:500.
  1985. class MediaFeature extends SyntaxUnit {
  1986. constructor(name, value) {
  1987. const text = `(${name}${value != null ? ':' + value : ''})`;
  1988. super(text, name, TYPES.MEDIA_FEATURE_TYPE);
  1989. this.name = name;
  1990. this.value = value;
  1991. }
  1992. }
  1993. /**
  1994. * An entire single selector, including all parts but not
  1995. * including multiple selectors (those separated by commas).
  1996. */
  1997. class Selector extends SyntaxUnit {
  1998. constructor(parts, pos) {
  1999. super(parts.join(' '), pos, TYPES.SELECTOR_TYPE);
  2000. this.parts = parts;
  2001. // eslint-disable-next-line no-use-before-define
  2002. this.specificity = Specificity.calculate(this);
  2003. }
  2004. }
  2005. /**
  2006. * A single part of a selector string i.e. element name and modifiers.
  2007. * Does not include combinators such as spaces, +, >, etc.
  2008. */
  2009. class SelectorPart extends SyntaxUnit {
  2010. constructor(elementName, modifiers, text, pos) {
  2011. super(text, pos, TYPES.SELECTOR_PART_TYPE);
  2012. this.elementName = elementName;
  2013. this.modifiers = modifiers;
  2014. }
  2015. }
  2016. /**
  2017. * Selector modifier string
  2018. */
  2019. class SelectorSubPart extends SyntaxUnit {
  2020. constructor(text, type, pos) {
  2021. super(text, pos, TYPES.SELECTOR_SUB_PART_TYPE);
  2022. this.type = type;
  2023. // Some subparts have arguments
  2024. this.args = [];
  2025. }
  2026. }
  2027. /**
  2028. * A selector combinator (whitespace, +, >).
  2029. */
  2030. class Combinator extends SyntaxUnit {
  2031. constructor(token) {
  2032. const {value} = token;
  2033. super(value, token, TYPES.COMBINATOR_TYPE);
  2034. this.type =
  2035. value === '>' ? 'child' :
  2036. value === '+' ? 'adjacent-sibling' :
  2037. value === '~' ? 'sibling' :
  2038. value === '||' ? 'column' :
  2039. !value.trim() ? 'descendant' :
  2040. 'unknown';
  2041. }
  2042. }
  2043. /**
  2044. * A selector specificity.
  2045. */
  2046. class Specificity {
  2047. /**
  2048. * @param {int} a Should be 1 for inline styles, zero for stylesheet styles
  2049. * @param {int} b Number of ID selectors
  2050. * @param {int} c Number of classes and pseudo classes
  2051. * @param {int} d Number of element names and pseudo elements
  2052. */
  2053. constructor(a, b, c, d) {
  2054. this.a = a;
  2055. this.b = b;
  2056. this.c = c;
  2057. this.d = d;
  2058. this.constructor = Specificity;
  2059. }
  2060. /**
  2061. * @param {Specificity} other The other specificity to compare to.
  2062. * @return {int} -1 if the other specificity is larger, 1 if smaller, 0 if equal.
  2063. */
  2064. compare(other) {
  2065. const comps = ['a', 'b', 'c', 'd'];
  2066. for (let i = 0, len = comps.length; i < len; i++) {
  2067. if (this[comps[i]] < other[comps[i]]) {
  2068. return -1;
  2069. } else if (this[comps[i]] > other[comps[i]]) {
  2070. return 1;
  2071. }
  2072. }
  2073. return 0;
  2074. }
  2075. valueOf() {
  2076. return this.a * 1000 + this.b * 100 + this.c * 10 + this.d;
  2077. }
  2078. toString() {
  2079. return `${this.a},${this.b},${this.c},${this.d}`;
  2080. }
  2081. /**
  2082. * Calculates the specificity of the given selector.
  2083. * @param {Selector} selector The selector to calculate specificity for.
  2084. * @return {Specificity} The specificity of the selector.
  2085. */
  2086. static calculate(selector) {
  2087. let b = 0;
  2088. let c = 0;
  2089. let d = 0;
  2090. selector.parts.forEach(updateValues);
  2091. return new Specificity(0, b, c, d);
  2092. function updateValues(part) {
  2093. if (!(part instanceof SelectorPart)) return;
  2094. const elementName = part.elementName ? part.elementName.text : '';
  2095. if (elementName && !elementName.endsWith('*')) {
  2096. d++;
  2097. }
  2098. for (const modifier of part.modifiers) {
  2099. switch (modifier.type) {
  2100. case 'class':
  2101. case 'attribute':
  2102. c++;
  2103. break;
  2104. case 'id':
  2105. b++;
  2106. break;
  2107. case 'pseudo':
  2108. if (isPseudoElement(modifier.text)) {
  2109. d++;
  2110. } else {
  2111. c++;
  2112. }
  2113. break;
  2114. case 'not':
  2115. modifier.args.forEach(updateValues);
  2116. }
  2117. }
  2118. }
  2119. }
  2120. }
  2121. class PropertyName extends SyntaxUnit {
  2122. constructor(text, hack, pos) {
  2123. super(text, pos, TYPES.PROPERTY_NAME_TYPE);
  2124. this.hack = hack;
  2125. }
  2126. toString() {
  2127. return (this.hack || '') + this.text;
  2128. }
  2129. }
  2130. /**
  2131. * A single value between ":" and ";", that is if there are multiple values
  2132. * separated by commas, this type represents just one of the values.
  2133. */
  2134. class PropertyValue extends SyntaxUnit {
  2135. constructor(parts, pos) {
  2136. super(parts.join(' '), pos, TYPES.PROPERTY_VALUE_TYPE);
  2137. this.parts = parts;
  2138. }
  2139. }
  2140. /**
  2141. * A single part of a value
  2142. * e.g. '1px solid rgb(1, 2, 3)' has 3 parts
  2143. * @property {PropertyValue} expr
  2144. */
  2145. class PropertyValuePart extends SyntaxUnit {
  2146. /** @param {parserlib.Token} token */
  2147. constructor(token) {
  2148. const {value, type} = token;
  2149. super(value, token, TYPES.PROPERTY_VALUE_PART_TYPE);
  2150. this.tokenType = type;
  2151. this.expr = token.expr || null;
  2152. switch (type) {
  2153. case Tokens.ANGLE:
  2154. case Tokens.DIMENSION:
  2155. case Tokens.FREQ:
  2156. case Tokens.LENGTH:
  2157. case Tokens.NUMBER:
  2158. case Tokens.PERCENTAGE:
  2159. case Tokens.TIME:
  2160. this.value = token.number;
  2161. this.units = token.units;
  2162. this.type = token.unitsType;
  2163. this.isInt = this.type === 'number' && !value.includes('.');
  2164. break;
  2165. case Tokens.HASH:
  2166. this.type = 'color';
  2167. this.value = value;
  2168. break;
  2169. case Tokens.IDENT:
  2170. if (value.startsWith('--')) {
  2171. this.type = 'custom-property';
  2172. this.value = value;
  2173. } else {
  2174. const namedColor = Colors[value] || Colors[lower(value)];
  2175. this.type = namedColor ? 'color' : 'identifier';
  2176. this.value = namedColor || value;
  2177. }
  2178. break;
  2179. case Tokens.FUNCTION: {
  2180. this.name = token.name;
  2181. SyntaxUnit.addFuncInfo(this, token);
  2182. break;
  2183. }
  2184. case Tokens.STRING:
  2185. this.type = 'string';
  2186. this.value = parseString(value);
  2187. break;
  2188. case Tokens.URI:
  2189. this.type = 'uri';
  2190. this.name = token.name;
  2191. this.uri = token.uri;
  2192. break;
  2193. case Tokens.USO_VAR:
  2194. this._isVar = true;
  2195. break;
  2196. default:
  2197. if (value === ',' || value === '/') {
  2198. this.type = 'operator';
  2199. this.value = value;
  2200. } else {
  2201. this.type = 'unknown';
  2202. }
  2203. }
  2204. }
  2205. }
  2206. class PropertyValueIterator {
  2207. /**
  2208. * @param {PropertyValue} value
  2209. */
  2210. constructor(value) {
  2211. this._i = 0;
  2212. this._parts = value.parts;
  2213. this._marks = [];
  2214. this.value = value;
  2215. this.hasNext = this._parts.length > 0;
  2216. }
  2217. /** @returns {PropertyValuePart|null} */
  2218. peek(count) {
  2219. return this._parts[this._i + (count || 0)] || null;
  2220. }
  2221. /** @returns {?PropertyValuePart} */
  2222. next() {
  2223. if (this.hasNext) {
  2224. this.hasNext = this._i + 1 < this._parts.length;
  2225. return this._parts[this._i++];
  2226. }
  2227. }
  2228. /** @returns {PropertyValueIterator} */
  2229. mark() {
  2230. this._marks.push(this._i);
  2231. return this;
  2232. }
  2233. popMark(success) {
  2234. const i = this._marks.pop();
  2235. if (!success && i != null) {
  2236. this._i = i;
  2237. this.hasNext = i < this._parts.length;
  2238. }
  2239. return success;
  2240. }
  2241. resetTo(i) {
  2242. this._i = i;
  2243. this.hasNext = this._parts.length > i;
  2244. }
  2245. }
  2246. //#endregion
  2247. //#region ValidationTypes - implementation
  2248. for (const obj of [VTSimple, VTComplex]) {
  2249. const action = obj === VTSimple
  2250. ? rule => vtIsLiteral.bind(obj, rule)
  2251. : Matcher.parse;
  2252. for (const [id, rule] of Object.entries(obj)) {
  2253. if (typeof rule === 'string') {
  2254. obj[id] = Object.defineProperty(action(rule), 'originalText', {value: rule});
  2255. } else if (/^Matcher\s/.test(rule)) {
  2256. obj[id] = rule(Matcher);
  2257. }
  2258. }
  2259. }
  2260. function vtDescribe(type) {
  2261. const complex = VTComplex[type];
  2262. const text = complex instanceof Matcher ? complex.toString(0) : type;
  2263. return vtExplode(text);
  2264. }
  2265. function vtExplode(text) {
  2266. if (!text.includes('<')) return text;
  2267. return text.replace(/(<.*?>)([{#?]?)/g, (s, rule, mod) => {
  2268. const ref = VTSimple[rule] || VTComplex[rule];
  2269. if (!ref || !ref.originalText) return s;
  2270. const full = vtExplode(ref.originalText);
  2271. const brace = mod || full.includes(' ');
  2272. return ((brace ? '[ ' : '') + full + (brace ? ' ]' : '')) + mod;
  2273. });
  2274. }
  2275. /** @param {PropertyValuePart} p */
  2276. function vtIsAttr(p) {
  2277. return p.isAttr && (p = p.expr) && (p = p.parts) && p.length && vtIsIdent(p[0]);
  2278. }
  2279. /** @param {PropertyValuePart} p */
  2280. function vtIsIdent(p) {
  2281. return p.tokenType === Tokens.IDENT;
  2282. }
  2283. /** @param {PropertyValuePart} p */
  2284. function vtIsLength(p) {
  2285. return p.text === '0' || p.type === 'length' || p.isCalc;
  2286. }
  2287. /**
  2288. * @param {string} literals
  2289. * @param {PropertyValuePart} part
  2290. * @return {?boolean}
  2291. */
  2292. function vtIsLiteral(literals, part) {
  2293. let text;
  2294. for (const arg of literals.includes(' | ') ? literals.split(' | ') : [literals]) {
  2295. if (arg.startsWith('<')) {
  2296. const vt = VTSimple[arg];
  2297. if (vt && vt(part)) {
  2298. return true;
  2299. }
  2300. continue;
  2301. }
  2302. if (arg.endsWith('()') &&
  2303. part.name &&
  2304. part.name.length === arg.length - 2 &&
  2305. lowerCmp(part.name, arg.slice(0, -2))) {
  2306. return true;
  2307. }
  2308. if ((text || part.text) === arg ||
  2309. (text || part.text).length >= arg.length &&
  2310. lowerCmp(arg, text || (text = rxVendorPrefix.test(part.text) ? RegExp.$2 : part.text))) {
  2311. return true;
  2312. }
  2313. }
  2314. }
  2315. /** @param {PropertyValuePart} p */
  2316. function vtIsPct(p) {
  2317. return p.text === '0' || p.type === 'percentage' || p.isCalc;
  2318. }
  2319. //#endregion
  2320. //#region Validation
  2321. const validationCache = new Map();
  2322. function validateProperty(name, property, value, Props = Properties) {
  2323. if (isGlobalKeyword(value.parts[0])) {
  2324. if (value.parts.length > 1) {
  2325. throwEndExpected(value.parts[1], true);
  2326. }
  2327. return;
  2328. }
  2329. const prop = lower(name);
  2330. const spec = Props[prop] || rxVendorPrefix.test(prop) && Props[RegExp.$2];
  2331. if (typeof spec === 'number' || !spec && prop.startsWith('-')) {
  2332. return;
  2333. }
  2334. if (!spec) {
  2335. const problem = Props === Properties || !Properties[prop] ? 'Unknown' : 'Misplaced';
  2336. throw new ValidationError(`${problem} property '${name}'.`, value);
  2337. }
  2338. if (hasVarParts(value)) {
  2339. return;
  2340. }
  2341. let known = validationCache.get(prop);
  2342. if (known && known.has(value.text)) {
  2343. return;
  2344. }
  2345. // Property-specific validation.
  2346. const expr = new PropertyValueIterator(value);
  2347. const m = Matcher.parse(spec);
  2348. let result = m.match(expr);
  2349. if (/\battr\(/i.test(value.text)) {
  2350. if (!result) {
  2351. expr.tryAttr = true;
  2352. expr.resetTo(0);
  2353. result = m.match(expr);
  2354. }
  2355. for (let p; (p = expr.peek()) && p.isAttr && vtIsAttr(p);) {
  2356. expr.next();
  2357. }
  2358. }
  2359. if (result) {
  2360. if (expr.hasNext) throwEndExpected(expr.next());
  2361. } else if (expr.hasNext && expr._i) {
  2362. throwEndExpected(expr.peek());
  2363. } else {
  2364. const {text} = expr.value;
  2365. throw new ValidationError(`Expected '${vtDescribe(spec)}' but found '${text}'.`,
  2366. expr.value);
  2367. }
  2368. if (!known) validationCache.set(prop, (known = new Set()));
  2369. known.add(value.text);
  2370. function throwEndExpected(unit, force) {
  2371. if (force || !unit.isVar) {
  2372. throw new ValidationError(`Expected end of value but found '${unit.text}'.`, unit);
  2373. }
  2374. }
  2375. }
  2376. //#endregion
  2377. //#region TokenStreamBase
  2378. /** lookup table size for TokenStreamBase */
  2379. const LT_SIZE = 5;
  2380. /**
  2381. * Generic TokenStream providing base functionality.
  2382. * @typedef TokenStream
  2383. */
  2384. class TokenStreamBase {
  2385. constructor(input) {
  2386. this._reader = new StringReader(input ? input.toString() : '');
  2387. this.resetLT();
  2388. }
  2389. resetLT() {
  2390. /** @type {parserlib.Token} Last consumed token object */
  2391. this._token = null;
  2392. // Lookahead token buffer.
  2393. this._lt = Array(LT_SIZE).fill(null);
  2394. this._ltIndex = 0;
  2395. this._ltAhead = 0;
  2396. this._ltShift = 0;
  2397. }
  2398. /**
  2399. * Consumes the next token if that matches any of the given token type(s).
  2400. * @param {int|int[]} tokenTypes
  2401. * @param {string|string[]} [values]
  2402. * @return {parserlib.Token|boolean} token or `false`
  2403. */
  2404. match(tokenTypes, values) {
  2405. const isArray = typeof tokenTypes === 'object';
  2406. for (let token, tt; (tt = (token = this.get(true)).type);) {
  2407. if ((isArray ? tokenTypes.includes(tt) : tt === tokenTypes) &&
  2408. (!values || values.some(lowerCmpThis, token.value))) {
  2409. return token;
  2410. }
  2411. if (tt !== Tokens.COMMENT) {
  2412. break;
  2413. }
  2414. }
  2415. this.unget();
  2416. return false;
  2417. }
  2418. /**
  2419. * Consumes the next token if that matches the given token type(s).
  2420. * Otherwise an error is thrown.
  2421. * @param {int|int[]} tokenTypes
  2422. * @throws {SyntaxError}
  2423. */
  2424. mustMatch(tokenTypes) {
  2425. return this.match(tokenTypes) ||
  2426. this.throwUnexpected(this.LT(1), tokenTypes);
  2427. }
  2428. /**
  2429. * Keeps reading until one of the specified token types is found or EOF.
  2430. * @param {int|int[]} tokenTypes
  2431. */
  2432. advance(tokenTypes) {
  2433. while (this.LA(0) !== 0 && !this.match(tokenTypes)) {
  2434. this.get();
  2435. }
  2436. return this.LA(0);
  2437. }
  2438. /**
  2439. * Consumes the next token from the token stream.
  2440. * @param {boolean} [asToken]
  2441. * @return {int|parserlib.Token} The token type
  2442. */
  2443. get(asToken) {
  2444. const i = this._ltIndex;
  2445. const next = i + 1;
  2446. const slot = (i + this._ltShift) % LT_SIZE;
  2447. if (i < this._ltAhead) {
  2448. this._ltIndex = next;
  2449. const token = this._token = this._lt[slot];
  2450. return asToken ? token : token.type;
  2451. }
  2452. const token = this._getToken();
  2453. const {type} = token;
  2454. const hide = type && (Tokens[type] || {}).hide;
  2455. if (type >= 0 && !hide) {
  2456. this._token = token;
  2457. this._lt[slot] = token;
  2458. if (this._ltAhead < LT_SIZE) {
  2459. this._ltIndex = next;
  2460. this._ltAhead++;
  2461. } else {
  2462. this._ltShift = (this._ltShift + 1) % LT_SIZE;
  2463. }
  2464. }
  2465. // Skip to the next token if the token type is marked as hidden.
  2466. return hide ? this.get(asToken) :
  2467. asToken ? token : type;
  2468. }
  2469. /**
  2470. * Looks ahead a certain number of tokens and returns the token type at that position.
  2471. * @param {int} index The index of the token type to retrieve.
  2472. * 0 for the current token, 1 for the next, -1 for the previous, etc.
  2473. * @return {int} The token type
  2474. * @throws if you lookahead past EOF, past the size of the lookahead buffer,
  2475. * or back past the first token in the lookahead buffer.
  2476. */
  2477. LA(index) {
  2478. return (index ? this.LT(index) : this._token).type;
  2479. }
  2480. /**
  2481. * Looks ahead a certain number of tokens and returns the token at that position.
  2482. * @param {int} index The index of the token type to retrieve.
  2483. * 0 for the current token, 1 for the next, -1 for the previous, etc.
  2484. * @param {boolean} [forceCache] won't call get() so it's useful in fast tentative checks
  2485. * @return {Object} The token
  2486. * @throws if you lookahead past EOF, past the size of the lookahead buffer,
  2487. * or back past the first token in the lookahead buffer.
  2488. */
  2489. LT(index, forceCache) {
  2490. if (!index) {
  2491. return this._token;
  2492. }
  2493. let i = index + this._ltIndex - (index > 0);
  2494. if (index < 0 ? i >= 0 : i < this._ltAhead) {
  2495. return this._lt[(i + this._ltShift) % LT_SIZE];
  2496. } else if (forceCache) {
  2497. return false;
  2498. }
  2499. if (index < 0) {
  2500. throw new Error('Too much lookbehind.');
  2501. }
  2502. if (index > LT_SIZE) {
  2503. throw new Error('Too much lookahead.');
  2504. }
  2505. i = index;
  2506. const oldToken = this._token;
  2507. while (i && i--) this.get();
  2508. const token = this._token;
  2509. this._ltIndex -= index;
  2510. this._token = oldToken;
  2511. return token;
  2512. }
  2513. /** Returns the token type for the next token in the stream without consuming it. */
  2514. peek() {
  2515. return this.LT(1).type;
  2516. }
  2517. /** Restores the last consumed token to the token stream. */
  2518. unget() {
  2519. if (this._ltIndex) {
  2520. this._ltIndex--;
  2521. this._token = this._lt[(this._ltIndex - 1 + this._ltShift + LT_SIZE) % LT_SIZE];
  2522. } else {
  2523. throw new Error('Too much lookahead.');
  2524. }
  2525. }
  2526. throwUnexpected(token = this._token, expected = []) {
  2527. expected = (Array.isArray(expected) ? expected : [expected])
  2528. .map(e => typeof e === 'string' ? e : Tokens.name(e))
  2529. .join(', ');
  2530. const msg = expected
  2531. ? `Expected ${expected} but found '${token.value}'.`
  2532. : `Unexpected '${token.value}'.`;
  2533. throw new SyntaxError(msg, token);
  2534. }
  2535. }
  2536. //#endregion
  2537. //#region TokenStream
  2538. class TokenStream extends TokenStreamBase {
  2539. /**
  2540. * @param {Number|Number[]} tokenTypes
  2541. * @param {Boolean} [skipCruftBefore=true] - skip comments/whitespace before matching
  2542. * @returns {Object} token
  2543. */
  2544. mustMatch(tokenTypes, skipCruftBefore = true) {
  2545. if (skipCruftBefore && tokenTypes !== Tokens.S) {
  2546. this.skipComment(true);
  2547. }
  2548. return super.mustMatch(tokenTypes);
  2549. }
  2550. /**
  2551. * @param {Boolean} [skipWS] - skip whitespace too
  2552. * @param {Boolean} [skipUsoVar] - skip USO_VAR too
  2553. */
  2554. skipComment(skipWS, skipUsoVar) {
  2555. const tt = this.LT(1, true).type;
  2556. if (skipWS && tt === Tokens.S ||
  2557. skipUsoVar && tt === Tokens.USO_VAR ||
  2558. tt === Tokens.COMMENT ||
  2559. tt == null && this._ltIndex === this._ltAhead && (
  2560. skipWS && this._reader.readMatch(/\s+/y),
  2561. this._reader.peekTest(/\/\*/y))) {
  2562. while (this.match(skipUsoVar ? TT.usoS : Tokens.S)) { /*NOP*/ }
  2563. }
  2564. }
  2565. /**
  2566. * @returns {Object} token
  2567. */
  2568. _getToken() {
  2569. const reader = this._reader;
  2570. /** @namespace parserlib.Token */
  2571. const tok = {
  2572. value: '',
  2573. type: Tokens.CHAR,
  2574. col: reader._col,
  2575. line: reader._line,
  2576. offset: reader._cursor,
  2577. };
  2578. let a = tok.value = reader.read();
  2579. let b = reader.peek();
  2580. if (a === '\\') {
  2581. if (b === '\n' || b === '\f') return tok;
  2582. a = this.readEscape();
  2583. b = reader.peek();
  2584. }
  2585. switch (a) {
  2586. case ' ':
  2587. case '\n':
  2588. case '\r':
  2589. case '\t':
  2590. case '\f':
  2591. tok.type = Tokens.S;
  2592. if (/\s/.test(b)) {
  2593. tok.value += reader.readMatch(/\s+/y) || '';
  2594. }
  2595. return tok;
  2596. case '{':
  2597. tok.type = Tokens.LBRACE;
  2598. tok.endChar = '}';
  2599. return tok;
  2600. case '(':
  2601. tok.type = Tokens.LPAREN;
  2602. tok.endChar = ')';
  2603. return tok;
  2604. case '[':
  2605. tok.type = Tokens.LBRACKET;
  2606. tok.endChar = ']';
  2607. return tok;
  2608. case '/':
  2609. if (b === '*') {
  2610. const str = tok.value = this.readComment(a);
  2611. tok.type = str.startsWith('/*[[') && str.endsWith(']]*/')
  2612. ? Tokens.USO_VAR
  2613. : Tokens.COMMENT;
  2614. } else {
  2615. tok.type = Tokens.SLASH;
  2616. }
  2617. return tok;
  2618. case '|':
  2619. case '~':
  2620. case '^':
  2621. case '$':
  2622. case '*':
  2623. if (b === '=') {
  2624. tok.value = a + reader.read();
  2625. tok.type = typeMap.get(tok.value) || Tokens.CHAR;
  2626. } else if (a === '|' && b === '|') {
  2627. reader.read();
  2628. tok.value = '||';
  2629. tok.type = Tokens.COLUMN;
  2630. } else {
  2631. tok.type = typeMap.get(a) || Tokens.CHAR;
  2632. }
  2633. return tok;
  2634. case '"':
  2635. case "'":
  2636. return this.stringToken(a, tok);
  2637. case '#':
  2638. if (rxNameChar.test(b)) {
  2639. tok.type = Tokens.HASH;
  2640. tok.value = this.readName(a);
  2641. }
  2642. return tok;
  2643. case '.':
  2644. if (b >= '0' && b <= '9') {
  2645. this.numberToken(a, tok);
  2646. } else {
  2647. tok.type = Tokens.DOT;
  2648. }
  2649. return tok;
  2650. case '-':
  2651. // could be closing HTML-style comment or CSS variable
  2652. if (b === '-') {
  2653. if (reader.peekTest(/-\w/yu)) {
  2654. this.identOrFunctionToken(a, tok);
  2655. } else if (reader.readMatch('->')) {
  2656. tok.type = Tokens.CDC;
  2657. tok.value = '-->';
  2658. }
  2659. } else if (b >= '0' && b <= '9' || b === '.' && reader.peekTest(/\.\d/y)) {
  2660. this.numberToken(a, tok);
  2661. } else if (rxIdentStart.test(b)) {
  2662. this.identOrFunctionToken(a, tok);
  2663. } else {
  2664. tok.type = Tokens.MINUS;
  2665. }
  2666. return tok;
  2667. case '+':
  2668. if (b >= '0' && b <= '9' || b === '.' && reader.peekTest(/\.\d/y)) {
  2669. this.numberToken(a, tok);
  2670. } else {
  2671. tok.type = Tokens.PLUS;
  2672. }
  2673. return tok;
  2674. case '!':
  2675. return this.importantToken(a, tok);
  2676. case '@':
  2677. return this.atRuleToken(a, tok);
  2678. case ':': {
  2679. const func = /[-niw]/i.test(b) &&
  2680. reader.readMatch(/(not|is|where|(-(moz|webkit)-)?any)\(/iy);
  2681. if (func) {
  2682. const first = b.toLowerCase();
  2683. tok.type =
  2684. first === 'n' ? Tokens.NOT :
  2685. first === 'i' ? Tokens.IS :
  2686. first === 'w' ? Tokens.WHERE : Tokens.ANY;
  2687. tok.value += func;
  2688. } else {
  2689. tok.type = Tokens.COLON;
  2690. }
  2691. return tok;
  2692. }
  2693. case '<':
  2694. if (b === '!' && reader.readMatch('!--')) {
  2695. tok.type = Tokens.CDO;
  2696. tok.value = '<!--';
  2697. }
  2698. return tok;
  2699. // EOF
  2700. case null:
  2701. tok.type = Tokens.EOF;
  2702. return tok;
  2703. case 'U':
  2704. case 'u':
  2705. return b === '+'
  2706. ? this.unicodeRangeToken(a, tok)
  2707. : this.identOrFunctionToken(a, tok);
  2708. }
  2709. if (a >= '0' && a <= '9') {
  2710. this.numberToken(a, tok);
  2711. } else if (rxIdentStart.test(a)) {
  2712. this.identOrFunctionToken(a, tok);
  2713. } else {
  2714. tok.type = typeMap.get(a) || Tokens.CHAR;
  2715. }
  2716. return tok;
  2717. }
  2718. atRuleToken(first, token) {
  2719. this._reader.mark();
  2720. let rule = first + this.readName();
  2721. let tt = Tokens.type(lower(rule));
  2722. // if it's not valid, use the first character only and reset the reader
  2723. if (tt === Tokens.CHAR || tt === Tokens.UNKNOWN) {
  2724. if (rule.length > 1) {
  2725. tt = Tokens.UNKNOWN_SYM;
  2726. } else {
  2727. tt = Tokens.CHAR;
  2728. rule = first;
  2729. this._reader.reset();
  2730. }
  2731. }
  2732. token.type = tt;
  2733. token.value = rule;
  2734. return token;
  2735. }
  2736. identOrFunctionToken(first, token) {
  2737. const reader = this._reader;
  2738. const name = this.readChunksWithEscape(first, rxNameCharNoEsc);
  2739. const next = reader.peek();
  2740. token.value = name;
  2741. // might be a URI or function
  2742. if (next === '(') {
  2743. reader.read();
  2744. if (/^(url(-prefix)?|domain)$/i.test(name)) {
  2745. reader.mark();
  2746. const uri = this.readURI(name + '(');
  2747. if (uri) {
  2748. token.type = Tokens.URI;
  2749. token.value = uri.text;
  2750. token.name = name;
  2751. token.uri = uri.value;
  2752. return token;
  2753. }
  2754. reader.reset();
  2755. }
  2756. token.type = Tokens.FUNCTION;
  2757. token.value += '(';
  2758. } else if (next === ':' && lowerCmp(name, 'progid')) {
  2759. token.type = Tokens.IE_FUNCTION;
  2760. token.value += reader.readTo('(');
  2761. } else {
  2762. token.type = Tokens.IDENT;
  2763. }
  2764. return token;
  2765. }
  2766. importantToken(first, token) {
  2767. const reader = this._reader;
  2768. let text = first;
  2769. reader.mark();
  2770. for (let pass = 1; pass++ <= 2;) {
  2771. const important = reader.readMatch(/\s*important\b/iy);
  2772. if (important) {
  2773. token.type = Tokens.IMPORTANT;
  2774. token.value = text + important;
  2775. return token;
  2776. }
  2777. const comment = reader.readMatch('/*');
  2778. if (!comment) break;
  2779. text += this.readComment(comment);
  2780. }
  2781. reader.reset();
  2782. return token;
  2783. }
  2784. numberToken(first, token) {
  2785. const reader = this._reader;
  2786. const value = first + (
  2787. this._reader.readMatch(
  2788. first === '.' ?
  2789. /\d+(e[+-]?\d+)?/iy :
  2790. first >= '0' && first <= '9' ?
  2791. /\d*\.?\d*(e[+-]?\d+)?/iy :
  2792. /(\d*\.\d+|\d+\.?\d*)(e[+-]?\d+)?/iy
  2793. ) || '');
  2794. let tt = Tokens.NUMBER;
  2795. let units, type;
  2796. const c = reader.peek();
  2797. if (rxIdentStart.test(c)) {
  2798. units = this.readName(reader.read());
  2799. type = UNITS[units] || UNITS[lower(units)];
  2800. tt = type && Tokens[type.toUpperCase()] ||
  2801. type === 'frequency' && Tokens.FREQ ||
  2802. Tokens.DIMENSION;
  2803. } else if (c === '%') {
  2804. units = reader.read();
  2805. type = 'percentage';
  2806. tt = Tokens.PERCENTAGE;
  2807. } else {
  2808. type = 'number';
  2809. }
  2810. token.type = tt;
  2811. token.value = units ? value + units : value;
  2812. token.number = parseFloat(value);
  2813. if (units) token.units = units;
  2814. if (type) token.unitsType = type;
  2815. return token;
  2816. }
  2817. stringToken(first, token) {
  2818. const delim = first;
  2819. const string = first ? [first] : [];
  2820. const reader = this._reader;
  2821. let tt = Tokens.STRING;
  2822. let c;
  2823. while (true) {
  2824. c = reader.readMatch(/[^\n\r\f\\'"]+|./y);
  2825. if (!c) break;
  2826. string.push(c);
  2827. if (c === '\\') {
  2828. c = reader.read();
  2829. if (c == null) {
  2830. break; // premature EOF after backslash
  2831. } else if (/[^\r\n\f0-9a-f]/i.test(c)) {
  2832. // single-character escape
  2833. string.push(c);
  2834. } else {
  2835. // read up to six hex digits + newline
  2836. string.push(c, reader.readMatch(/[0-9a-f]{1,6}\n?/yi));
  2837. }
  2838. } else if (c === delim) {
  2839. break; // delimiter found.
  2840. } else if (reader.peekTest(/[\n\r\f]/y)) {
  2841. // newline without an escapement: it's an invalid string
  2842. tt = Tokens.INVALID;
  2843. break;
  2844. }
  2845. }
  2846. token.type = c ? tt : Tokens.INVALID; // if the string wasn't closed
  2847. token.value = fastJoin(string);
  2848. return token;
  2849. }
  2850. unicodeRangeToken(first, token) {
  2851. const reader = this._reader;
  2852. reader.mark();
  2853. token.value += reader.read(); // +
  2854. let chunk = this.readUnicodeRangePart(true);
  2855. if (!chunk) {
  2856. reader.reset();
  2857. return token;
  2858. }
  2859. token.value += chunk;
  2860. // if there's a ? in the first part, there can't be a second part
  2861. if (!token.value.includes('?') && reader.peek() === '-') {
  2862. reader.mark();
  2863. reader.read();
  2864. chunk = this.readUnicodeRangePart(false);
  2865. if (!chunk) {
  2866. reader.reset();
  2867. } else {
  2868. token.value += '-' + chunk;
  2869. }
  2870. }
  2871. token.type = Tokens.UNICODE_RANGE;
  2872. return token;
  2873. }
  2874. readUnicodeRangePart(allowQuestionMark) {
  2875. const reader = this._reader;
  2876. let part = reader.readMatch(/[0-9a-f]{1,6}/iy);
  2877. while (allowQuestionMark && part.length < 6 && reader.peek() === '?') {
  2878. part += reader.read();
  2879. }
  2880. return part;
  2881. }
  2882. // returns null w/o resetting reader if string is invalid.
  2883. readString(first = this._reader.read()) {
  2884. const token = this.stringToken(first, {});
  2885. return token.type !== Tokens.INVALID ? token.value : null;
  2886. }
  2887. // returns null w/o resetting reader if URI is invalid.
  2888. readURI(first) {
  2889. const reader = this._reader;
  2890. const uri = first;
  2891. let value = '';
  2892. this._reader.readMatch(/\s+/y);
  2893. if (reader.peekTest(/['"]/y)) {
  2894. value = this.readString();
  2895. if (value == null) return null;
  2896. value = parseString(value);
  2897. } else {
  2898. value = this.readChunksWithEscape('', rxUnquotedUrlCharNoEsc);
  2899. }
  2900. this._reader.readMatch(/\s+/y);
  2901. // Ensure argument to URL is always double-quoted
  2902. // (This simplifies later processing in PropertyValuePart.)
  2903. return reader.peek() !== ')' ? null : {
  2904. value,
  2905. text: uri + serializeString(value) + reader.read(),
  2906. };
  2907. }
  2908. readName(first) {
  2909. return this.readChunksWithEscape(first, rxNameCharNoEsc);
  2910. }
  2911. readEscape() {
  2912. let res = this._reader.readMatch(/[0-9a-f]{1,6}\s?/iy);
  2913. if (res) {
  2914. res = parseInt(res, 16);
  2915. res = String.fromCodePoint(res && res <= 0x10FFFF ? res : 0xFFFD);
  2916. } else {
  2917. res = this._reader.read();
  2918. }
  2919. return res;
  2920. }
  2921. /**
  2922. * @param {?string} first
  2923. * @param {RegExp} rx - must not match \\
  2924. * @returns {string}
  2925. */
  2926. readChunksWithEscape(first, rx) {
  2927. const reader = this._reader;
  2928. const url = first ? [first] : [];
  2929. while (true) {
  2930. const chunk = reader.readMatch(rx);
  2931. if (chunk) url.push(chunk);
  2932. if (reader.peekTest(/\\[^\r\n\f]/y)) {
  2933. reader.read();
  2934. url.push(this.readEscape());
  2935. } else {
  2936. break;
  2937. }
  2938. }
  2939. return fastJoin(url);
  2940. }
  2941. readComment(first) {
  2942. return first +
  2943. this._reader.readCount(2 - first.length) +
  2944. this._reader.readMatch(/([^*]|\*(?!\/))*(\*\/|$)/y);
  2945. }
  2946. /**
  2947. * @param {boolean} [omitComments]
  2948. * @param {string} [stopOn] - goes to the parent if used at the top nesting level of the value,
  2949. specifying an empty string will stop after consuming the first encountered top block.
  2950. * @returns {?string}
  2951. */
  2952. readDeclValue({omitComments, stopOn = ';!})'} = {}) {
  2953. const reader = this._reader;
  2954. const value = [];
  2955. const endings = [];
  2956. let end = stopOn;
  2957. const rx = stopOn.includes(';')
  2958. ? /([^;!'"{}()[\]/\\]|\/(?!\*))+/y
  2959. : /([^'"{}()[\]/\\]|\/(?!\*))+/y;
  2960. while (!reader.eof()) {
  2961. const chunk = reader.readMatch(rx);
  2962. if (chunk) {
  2963. value.push(chunk);
  2964. }
  2965. reader.mark();
  2966. const c = reader.read();
  2967. if (!endings.length && stopOn.includes(c)) {
  2968. reader.reset();
  2969. break;
  2970. }
  2971. value.push(c);
  2972. if (c === '\\') {
  2973. value[value.length - 1] = this.readEscape();
  2974. } else if (c === '/') {
  2975. value[value.length - 1] = this.readComment(c);
  2976. if (omitComments) value.pop();
  2977. } else if (c === '"' || c === "'") {
  2978. value[value.length - 1] = this.readString(c);
  2979. } else if (c === '{' || c === '(' || c === '[') {
  2980. endings.push(end);
  2981. end = c === '{' ? '}' : c === '(' ? ')' : ']';
  2982. } else if (c === '}' || c === ')' || c === ']') {
  2983. if (!end.includes(c)) {
  2984. reader.reset();
  2985. return null;
  2986. }
  2987. end = endings.pop();
  2988. if (!end && !stopOn) {
  2989. break;
  2990. }
  2991. }
  2992. }
  2993. return fastJoin(value);
  2994. }
  2995. readUnknownSym() {
  2996. const reader = this._reader;
  2997. const prelude = [];
  2998. let block;
  2999. while (true) {
  3000. if (reader.eof()) this.throwUnexpected();
  3001. const c = reader.peek();
  3002. if (c === '{') {
  3003. block = this.readDeclValue({stopOn: ''});
  3004. break;
  3005. } else if (c === ';') {
  3006. reader.read();
  3007. break;
  3008. } else {
  3009. prelude.push(this.readDeclValue({omitComments: true, stopOn: ';{'}));
  3010. }
  3011. }
  3012. return {prelude, block};
  3013. }
  3014. }
  3015. //#endregion
  3016. //#region parserCache
  3017. /**
  3018. * Caches the results and reuses them on subsequent parsing of the same code
  3019. */
  3020. const parserCache = (() => {
  3021. const MAX_DURATION = 10 * 60e3;
  3022. const TRIM_DELAY = 10e3;
  3023. // all blocks since page load; key = text between block start and { inclusive
  3024. const data = new Map();
  3025. // nested block stack
  3026. const stack = [];
  3027. // performance.now() of the current parser
  3028. let generation = null;
  3029. // performance.now() of the first parser after reset or page load,
  3030. // used for weighted sorting in getBlock()
  3031. let generationBase = null;
  3032. // true on page load, first run is pure analysis
  3033. let firstRun = true;
  3034. let parser = null;
  3035. let stream = null;
  3036. return {
  3037. start(newParser) {
  3038. parser = newParser;
  3039. if (!parser) {
  3040. data.clear();
  3041. stack.length = 0;
  3042. generationBase = performance.now();
  3043. return;
  3044. }
  3045. if (firstRun) firstRun = false;
  3046. stream = parser._tokenStream;
  3047. generation = performance.now();
  3048. trim();
  3049. },
  3050. addEvent(event) {
  3051. if (!parser) return;
  3052. for (let i = stack.length; --i >= 0;) {
  3053. const {offset, endOffset, events} = stack[i];
  3054. if (event.offset >= offset && (!endOffset || event.offset <= endOffset)) {
  3055. events.push(event);
  3056. return;
  3057. }
  3058. }
  3059. },
  3060. findBlock(token = getToken()) {
  3061. if (!parser || firstRun || !token) return;
  3062. const reader = stream._reader;
  3063. const input = reader._input;
  3064. let start = token.offset;
  3065. const c = input[start];
  3066. if (c === ' ' || c === '\t' || c === '\n' || c === '\f' || c === '\r') {
  3067. const rx = /\s*/y;
  3068. rx.lastIndex = start;
  3069. rx.exec(input);
  3070. start = rx.lastIndex;
  3071. }
  3072. const key = input.slice(start, input.indexOf('{', start) + 1);
  3073. const blocks = data.get(key);
  3074. if (!blocks) return;
  3075. const block = getBlock(blocks, input, start, key);
  3076. if (!block) return;
  3077. reader.readCount(start - reader._cursor);
  3078. shiftBlock(reader, start, block);
  3079. shiftStream(reader, block);
  3080. parser._ws();
  3081. return true;
  3082. },
  3083. startBlock(start = getToken()) {
  3084. if (!parser) return;
  3085. stack.push({
  3086. text: '',
  3087. events: [],
  3088. generation: generation,
  3089. line: start.line,
  3090. col: start.col,
  3091. offset: start.offset,
  3092. endLine: undefined,
  3093. endCol: undefined,
  3094. endOffset: undefined,
  3095. });
  3096. },
  3097. adjustBlockStart(start = getToken()) {
  3098. if (!parser) return;
  3099. const block = stack[stack.length - 1];
  3100. block.line = start.line;
  3101. block.col = start.col;
  3102. block.offset = start.offset;
  3103. },
  3104. endBlock(end = getToken()) {
  3105. if (!parser) return;
  3106. const block = stack.pop();
  3107. block.endLine = end.line;
  3108. block.endCol = end.col + end.value.length;
  3109. block.endOffset = end.offset + end.value.length;
  3110. const input = stream._reader._input;
  3111. const key = input.slice(block.offset, input.indexOf('{', block.offset) + 1);
  3112. block.text = input.slice(block.offset, block.endOffset);
  3113. let blocks = data.get(key);
  3114. if (!blocks) data.set(key, (blocks = []));
  3115. blocks.push(block);
  3116. },
  3117. cancelBlock: () => stack.pop(),
  3118. feedback({messages}) {
  3119. messages = new Set(messages);
  3120. for (const blocks of data.values()) {
  3121. for (const block of blocks) {
  3122. if (!block.events.length) continue;
  3123. if (block.generation !== generation) continue;
  3124. const {
  3125. line: L1,
  3126. col: C1,
  3127. endLine: L2,
  3128. endCol: C2,
  3129. } = block;
  3130. let isClean = true;
  3131. for (const msg of messages) {
  3132. const {line, col} = msg;
  3133. if (L1 === L2 && line === L1 && C1 <= col && col <= C2 ||
  3134. line === L1 && col >= C1 ||
  3135. line === L2 && col <= C2 ||
  3136. line > L1 && line < L2) {
  3137. messages.delete(msg);
  3138. isClean = false;
  3139. }
  3140. }
  3141. if (isClean) block.events.length = 0;
  3142. }
  3143. }
  3144. },
  3145. };
  3146. /**
  3147. * Removes old entries from the cache.
  3148. * 'Old' means older than MAX_DURATION or half the blocks from the previous generation(s).
  3149. * @param {Boolean} [immediately] - set internally when debounced by TRIM_DELAY
  3150. */
  3151. function trim(immediately) {
  3152. if (!immediately) {
  3153. clearTimeout(trim.timer);
  3154. trim.timer = setTimeout(trim, TRIM_DELAY, true);
  3155. return;
  3156. }
  3157. const cutoff = performance.now() - MAX_DURATION;
  3158. for (const [key, blocks] of data.entries()) {
  3159. const halfLen = blocks.length >> 1;
  3160. const newBlocks = blocks
  3161. .sort((a, b) => a.time - b.time)
  3162. .filter((block, i) => block.generation > cutoff ||
  3163. block.generation !== generation && i < halfLen);
  3164. if (!newBlocks.length) {
  3165. data.delete(key);
  3166. } else if (newBlocks.length !== blocks.length) {
  3167. data.set(key, newBlocks);
  3168. }
  3169. }
  3170. }
  3171. // gets the matching block
  3172. function getBlock(blocks, input, start, key) {
  3173. // extracted to prevent V8 deopt
  3174. const keyLast = Math.max(key.length - 1);
  3175. const check1 = input[start];
  3176. const check2 = input[start + keyLast];
  3177. const generationSpan = performance.now() - generationBase;
  3178. blocks = blocks
  3179. .filter(({text, offset, endOffset}) =>
  3180. text[0] === check1 &&
  3181. text[keyLast] === check2 &&
  3182. text[text.length - 1] === input[start + text.length - 1] &&
  3183. text.startsWith(key) &&
  3184. text === input.substr(start, endOffset - offset))
  3185. .sort((a, b) =>
  3186. // newest and closest will be the first element
  3187. (a.generation - b.generation) / generationSpan +
  3188. (Math.abs(a.offset - start) - Math.abs(b.offset - start)) / input.length);
  3189. // identical blocks may produce different reports in CSSLint
  3190. // so we need to either hijack an older generation block or make a clone
  3191. const block = blocks.find(b => b.generation !== generation);
  3192. return block || deepCopy(blocks[0]);
  3193. }
  3194. // Shifts positions of the block and its events, also fires the events
  3195. function shiftBlock(reader, start, block) {
  3196. // extracted to prevent V8 deopt
  3197. const deltaLines = reader._line - block.line;
  3198. const deltaCols = block.col === 1 && reader._col === 1 ? 0 : reader._col - block.col;
  3199. const deltaOffs = reader._cursor - block.offset;
  3200. const hasDelta = deltaLines || deltaCols || deltaOffs;
  3201. const shifted = new Set();
  3202. for (const e of block.events) {
  3203. if (hasDelta) {
  3204. applyDelta(e, shifted, block.line, deltaLines, deltaCols, deltaOffs);
  3205. }
  3206. parser.fire(e, false);
  3207. }
  3208. block.generation = generation;
  3209. block.endCol += block.endLine === block.line ? deltaCols : 0;
  3210. block.endLine += deltaLines;
  3211. block.endOffset = reader._cursor + block.text.length;
  3212. block.line += deltaLines;
  3213. block.col += deltaCols;
  3214. block.offset = reader._cursor;
  3215. }
  3216. function shiftStream(reader, block) {
  3217. reader._line = block.endLine;
  3218. reader._col = block.endCol;
  3219. reader._cursor = block.endOffset;
  3220. stream.resetLT();
  3221. }
  3222. // Recursively applies the delta to the event and all its nested parts
  3223. function applyDelta(obj, seen, line, lines, cols, offs) {
  3224. if (seen.has(obj)) return;
  3225. seen.add(obj);
  3226. if (Array.isArray(obj)) {
  3227. for (const item of obj) {
  3228. if ((typeof item === 'object' || Array.isArray(item)) && item) {
  3229. applyDelta(item, seen, line, lines, cols, offs);
  3230. }
  3231. }
  3232. return;
  3233. }
  3234. // applyDelta may get surpisingly slow on complex objects so we're using an array
  3235. // because in js an array lookup is much faster than a property lookup
  3236. const keys = Object.keys(obj);
  3237. if (cols !== 0) {
  3238. if (keys.includes('col') && obj.line === line) obj.col += cols;
  3239. if (keys.includes('endCol') && obj.endLine === line) obj.endCol += cols;
  3240. }
  3241. if (lines !== 0) {
  3242. if (keys.includes('line')) obj.line += lines;
  3243. if (keys.includes('endLine')) obj.endLine += lines;
  3244. }
  3245. if (offs !== 0 && keys.includes('offset')) obj.offset += offs;
  3246. for (const k of keys) {
  3247. if (k !== 'col' && k !== 'endCol' &&
  3248. k !== 'line' && k !== 'endLine' &&
  3249. k !== 'offset') {
  3250. const v = obj[k];
  3251. if (v && typeof v === 'object') {
  3252. applyDelta(v, seen, line, lines, cols, offs);
  3253. }
  3254. }
  3255. }
  3256. }
  3257. // returns next token if it's already seen or the current token
  3258. function getToken() {
  3259. return parser && (stream.LT(1, true) || stream._token);
  3260. }
  3261. function deepCopy(obj) {
  3262. if (!obj || typeof obj !== 'object') {
  3263. return obj;
  3264. }
  3265. if (Array.isArray(obj)) {
  3266. return obj.map(deepCopy);
  3267. }
  3268. const copy = {};
  3269. for (const k in obj) {
  3270. copy[k] = deepCopy(obj[k]);
  3271. }
  3272. return copy;
  3273. }
  3274. })();
  3275. //#endregion
  3276. //#region Parser
  3277. class Parser extends EventTarget {
  3278. /**
  3279. * @param {Object} [options]
  3280. * @param {boolean} [options.ieFilters] - accepts IE < 8 filters instead of throwing
  3281. * @param {boolean} [options.skipValidation] - skip syntax validation
  3282. * @param {boolean} [options.starHack] - allows IE6 star hack
  3283. * @param {boolean} [options.strict] - stop on errors instead of reporting them and continuing
  3284. * @param {boolean} [options.topDocOnly] - quickly extract all top-level @-moz-document,
  3285. their {}-block contents is retrieved as text using _simpleBlock()
  3286. * @param {boolean} [options.underscoreHack] - interprets leading _ as IE6-7 for known props
  3287. */
  3288. constructor(options) {
  3289. super();
  3290. this.options = options || {};
  3291. /** @type {TokenStream|TokenStreamBase} */
  3292. this._tokenStream = null;
  3293. }
  3294. /**
  3295. * @param {string|Object} event
  3296. * @param {parserlib.Token|SyntaxUnit} [token=this._tokenStream._token] - sets the position
  3297. */
  3298. fire(event, token = this._tokenStream._token) {
  3299. if (typeof event === 'string') {
  3300. event = {type: event};
  3301. }
  3302. if (event.offset === undefined && token) {
  3303. event.offset = token.offset;
  3304. if (event.line === undefined) event.line = token.line;
  3305. if (event.col === undefined) event.col = token.col;
  3306. }
  3307. if (token !== false) parserCache.addEvent(event);
  3308. super.fire(event);
  3309. }
  3310. _stylesheet() {
  3311. const stream = this._tokenStream;
  3312. this.fire('startstylesheet');
  3313. this._sheetGlobals();
  3314. const {topDocOnly} = this.options;
  3315. const allowedActions = topDocOnly ? Parser.ACTIONS.topDoc : Parser.ACTIONS.stylesheet;
  3316. for (let tt, token; (tt = (token = stream.get(true)).type); this._skipCruft()) {
  3317. try {
  3318. const action = allowedActions.get(tt);
  3319. if (action) {
  3320. action.call(this, token);
  3321. continue;
  3322. }
  3323. if (topDocOnly) {
  3324. stream.readDeclValue({stopOn: '{}'});
  3325. if (stream._reader.peek() === '{') {
  3326. stream.readDeclValue({stopOn: ''});
  3327. }
  3328. continue;
  3329. }
  3330. stream.unget();
  3331. if (!this._ruleset() && stream.peek() !== Tokens.EOF) {
  3332. stream.throwUnexpected(stream.get(true));
  3333. }
  3334. } catch (ex) {
  3335. if (ex instanceof SyntaxError && !this.options.strict) {
  3336. this.fire(Object.assign({}, ex, {type: 'error', error: ex}));
  3337. } else {
  3338. ex.message = ex.stack;
  3339. ex.line = token.line;
  3340. ex.col = token.col;
  3341. throw ex;
  3342. }
  3343. }
  3344. }
  3345. this.fire('endstylesheet');
  3346. }
  3347. _sheetGlobals() {
  3348. const stream = this._tokenStream;
  3349. this._skipCruft();
  3350. for (const [type, fn, max = Infinity] of [
  3351. [Tokens.CHARSET_SYM, this._charset, 1],
  3352. [Tokens.IMPORT_SYM, this._import],
  3353. [Tokens.NAMESPACE_SYM, this._namespace],
  3354. ]) {
  3355. for (let i = 0; i++ < max && stream.peek() === type;) {
  3356. fn.call(this, stream.get(true));
  3357. this._skipCruft();
  3358. }
  3359. }
  3360. }
  3361. _charset(start) {
  3362. const stream = this._tokenStream;
  3363. const charset = stream.mustMatch(Tokens.STRING).value;
  3364. stream.mustMatch(Tokens.SEMICOLON);
  3365. this.fire({type: 'charset', charset}, start);
  3366. }
  3367. _import(start) {
  3368. const stream = this._tokenStream;
  3369. const token = stream.mustMatch(TT.stringUri);
  3370. const uri = token.uri || token.value.replace(/^["']|["']$/g, '');
  3371. this._ws();
  3372. const media = this._mediaQueryList();
  3373. stream.mustMatch(Tokens.SEMICOLON);
  3374. this.fire({type: 'import', media, uri}, start);
  3375. this._ws();
  3376. }
  3377. _namespace(start) {
  3378. const stream = this._tokenStream;
  3379. this._ws();
  3380. const prefix = stream.match(Tokens.IDENT).value;
  3381. if (prefix) this._ws();
  3382. const token = stream.mustMatch(TT.stringUri);
  3383. const uri = token.uri || token.value.replace(/^["']|["']$/g, '');
  3384. stream.mustMatch(Tokens.SEMICOLON);
  3385. this.fire({type: 'namespace', prefix, uri}, start);
  3386. this._ws();
  3387. }
  3388. _supports(start) {
  3389. const stream = this._tokenStream;
  3390. this._ws();
  3391. this._supportsCondition();
  3392. stream.mustMatch(Tokens.LBRACE);
  3393. this.fire('startsupports', start);
  3394. this._ws();
  3395. for (;; stream.skipComment()) {
  3396. const action = Parser.ACTIONS.supports.get(stream.peek());
  3397. if (action) {
  3398. action.call(this, stream.get(true));
  3399. } else if (!this._ruleset()) {
  3400. break;
  3401. }
  3402. }
  3403. stream.mustMatch(Tokens.RBRACE);
  3404. this.fire('endsupports');
  3405. this._ws();
  3406. }
  3407. _supportsCondition() {
  3408. const stream = this._tokenStream;
  3409. if (stream.match(Tokens.IDENT, ['not'])) {
  3410. stream.mustMatch(Tokens.S);
  3411. this._supportsConditionInParens();
  3412. } else {
  3413. this._supportsConditionInParens();
  3414. while (stream.match(Tokens.IDENT, ['and', 'or'])) {
  3415. this._ws();
  3416. this._supportsConditionInParens();
  3417. }
  3418. }
  3419. }
  3420. _supportsConditionInParens() {
  3421. const stream = this._tokenStream;
  3422. const next = stream.LT(1);
  3423. if (next.type === Tokens.LPAREN) {
  3424. stream.get();
  3425. this._ws();
  3426. const {type, value} = stream.LT(1);
  3427. if (type === Tokens.IDENT) {
  3428. if (lowerCmp(value, 'not')) {
  3429. this._supportsCondition();
  3430. stream.mustMatch(Tokens.RPAREN);
  3431. } else {
  3432. this._supportsDecl(false);
  3433. }
  3434. } else {
  3435. this._supportsCondition();
  3436. stream.mustMatch(Tokens.RPAREN);
  3437. }
  3438. } else if (stream.match(Tokens.FUNCTION, ['selector('])) {
  3439. this._ws();
  3440. const selector = this._selector();
  3441. this.fire({type: 'supportsSelector', selector}, selector);
  3442. stream.mustMatch(Tokens.RPAREN);
  3443. } else {
  3444. this._supportsDecl();
  3445. }
  3446. this._ws();
  3447. }
  3448. _supportsDecl(requireStartParen = true) {
  3449. const stream = this._tokenStream;
  3450. if (requireStartParen) {
  3451. stream.mustMatch(Tokens.LPAREN);
  3452. }
  3453. this._ws();
  3454. this._declaration();
  3455. stream.mustMatch(Tokens.RPAREN);
  3456. }
  3457. _media(start) {
  3458. const stream = this._tokenStream;
  3459. this._ws();
  3460. const mediaList = this._mediaQueryList();
  3461. stream.mustMatch(Tokens.LBRACE);
  3462. this.fire({
  3463. type: 'startmedia',
  3464. media: mediaList,
  3465. }, start);
  3466. this._ws();
  3467. let action;
  3468. do action = Parser.ACTIONS.media.get(stream.peek());
  3469. while (action ? action.call(this, stream.get(true)) || true : this._ruleset());
  3470. stream.mustMatch(Tokens.RBRACE);
  3471. this.fire({
  3472. type: 'endmedia',
  3473. media: mediaList,
  3474. });
  3475. this._ws();
  3476. }
  3477. _mediaQueryList() {
  3478. const stream = this._tokenStream;
  3479. const mediaList = [];
  3480. this._ws();
  3481. if ([Tokens.IDENT, Tokens.LPAREN].includes(stream.peek())) {
  3482. mediaList.push(this._mediaQuery());
  3483. }
  3484. while (stream.match(Tokens.COMMA)) {
  3485. this._ws();
  3486. mediaList.push(this._mediaQuery());
  3487. }
  3488. return mediaList;
  3489. }
  3490. _mediaQuery() {
  3491. const stream = this._tokenStream;
  3492. const expressions = [];
  3493. let type = null;
  3494. const token = stream.match(Tokens.IDENT, ['only', 'not']);
  3495. const ident = token.value || null;
  3496. this._ws();
  3497. const next = stream.LT(1);
  3498. switch (next.type) {
  3499. case Tokens.IDENT:
  3500. type = this._mediaFeature();
  3501. break;
  3502. case Tokens.LPAREN:
  3503. expressions.push(this._mediaExpression());
  3504. break;
  3505. default:
  3506. return;
  3507. }
  3508. this._ws();
  3509. while (stream.match(Tokens.IDENT)) {
  3510. if (lowerCmp(stream._token.value, 'and')) {
  3511. this._ws();
  3512. expressions.push(this._mediaExpression());
  3513. } else {
  3514. stream.throwUnexpected(undefined, ["'and'"]);
  3515. }
  3516. }
  3517. return new MediaQuery(ident, type, expressions, token || next);
  3518. }
  3519. _mediaExpression() {
  3520. const stream = this._tokenStream;
  3521. let token;
  3522. let expression = null;
  3523. stream.mustMatch(Tokens.LPAREN);
  3524. const feature = this._mediaFeature();
  3525. this._ws();
  3526. if (stream.match(Tokens.COLON)) {
  3527. this._ws();
  3528. token = stream.LT(1);
  3529. expression = this._expression({calc: true});
  3530. }
  3531. stream.mustMatch(Tokens.RPAREN);
  3532. this._ws();
  3533. return new MediaFeature(feature, expression ? new SyntaxUnit(expression, token) : null);
  3534. }
  3535. _mediaFeature() {
  3536. this._tokenStream.mustMatch(Tokens.IDENT);
  3537. return SyntaxUnit.fromToken(this._tokenStream._token);
  3538. }
  3539. _page(start) {
  3540. const stream = this._tokenStream;
  3541. this._ws();
  3542. const id = stream.match(Tokens.IDENT).value || null;
  3543. if (id && lowerCmp(id, 'auto')) {
  3544. stream.throwUnexpected();
  3545. }
  3546. const pseudo = stream.match(Tokens.COLON)
  3547. ? stream.mustMatch(Tokens.IDENT, false).value
  3548. : null;
  3549. this._ws();
  3550. this.fire({type: 'startpage', id, pseudo}, start);
  3551. this._readDeclarations({readMargins: true});
  3552. this.fire({type: 'endpage', id, pseudo});
  3553. }
  3554. _margin() {
  3555. const margin = SyntaxUnit.fromToken(this._tokenStream.match(TT.margins));
  3556. if (!margin) return false;
  3557. this.fire({type: 'startpagemargin', margin});
  3558. this._readDeclarations();
  3559. this.fire({type: 'endpagemargin', margin});
  3560. return true;
  3561. }
  3562. _fontFace(start) {
  3563. this.fire('startfontface', start);
  3564. this._ws();
  3565. this._readDeclarations({Props: ScopedProperties['@font-face']});
  3566. this.fire('endfontface');
  3567. }
  3568. _viewport(start) {
  3569. // only viewport-fit is allowed but we're reusing MediaQuery syntax unit,
  3570. // and accept anything for the sake of simplicity since the spec isn't yet final:
  3571. // https://drafts.csswg.org/css-round-display/#extending-viewport-rule
  3572. const descriptors = this._mediaQueryList();
  3573. this.fire({type: 'startviewport', descriptors}, start);
  3574. this._ws();
  3575. this._readDeclarations();
  3576. this.fire({type: 'endviewport', descriptors});
  3577. }
  3578. _document(start) {
  3579. const stream = this._tokenStream;
  3580. const functions = [];
  3581. const prefix = start.value.split('-')[1] || '';
  3582. do {
  3583. this._ws();
  3584. const uri = stream.match(Tokens.URI);
  3585. const fn = uri ? new PropertyValuePart(uri) : this._function() || stream.LT(1);
  3586. functions.push(fn);
  3587. if (uri) this._ws();
  3588. } while (stream.match(Tokens.COMMA));
  3589. for (const fn of functions) {
  3590. if ((fn.type !== 'function' || !/^(url(-prefix)?|domain|regexp)$/i.test(fn.name)) &&
  3591. fn.type !== 'uri') {
  3592. this.fire({
  3593. type: 'error',
  3594. message: 'Expected url( or url-prefix( or domain( or regexp(, instead saw ' +
  3595. Tokens.name(fn.tokenType || fn.type) + ' ' + (fn.text || fn.value),
  3596. }, fn);
  3597. }
  3598. }
  3599. stream.mustMatch(Tokens.LBRACE);
  3600. this.fire({type: 'startdocument', functions, prefix}, start);
  3601. if (this.options.topDocOnly) {
  3602. stream.readDeclValue({stopOn: '}'});
  3603. } else {
  3604. /* We allow @import and such inside document sections because the final generated CSS for
  3605. * a given page may be valid e.g. if this section is the first one that matched the URL */
  3606. this._sheetGlobals();
  3607. this._ws();
  3608. let action;
  3609. do action = Parser.ACTIONS.document.get(stream.peek());
  3610. while (action ? action.call(this, stream.get(true)) || true : this._ruleset());
  3611. }
  3612. stream.mustMatch(Tokens.RBRACE);
  3613. this.fire({type: 'enddocument', functions, prefix});
  3614. this._ws();
  3615. }
  3616. _documentMisplaced(start) {
  3617. this.fire({
  3618. type: 'error',
  3619. message: 'Nested @document produces broken code',
  3620. }, start);
  3621. this._document(start);
  3622. }
  3623. _combinator() {
  3624. const token = this._tokenStream.match(TT.combinator);
  3625. if (token) {
  3626. this._ws();
  3627. return new Combinator(token);
  3628. }
  3629. }
  3630. _property() {
  3631. const stream = this._tokenStream;
  3632. let token = stream.get(true);
  3633. let value = null;
  3634. let hack = null;
  3635. let start;
  3636. if (this.options.starHack && token.type === Tokens.STAR) {
  3637. hack = '*';
  3638. start = token;
  3639. token = stream.get(true);
  3640. }
  3641. if (token.type === Tokens.IDENT) {
  3642. let tokenValue = token.value;
  3643. // check for underscore hack - no error if not allowed because it's valid CSS syntax
  3644. if (this.options.underscoreHack && tokenValue.startsWith('_')) {
  3645. hack = '_';
  3646. tokenValue = tokenValue.slice(1);
  3647. }
  3648. value = new PropertyName(tokenValue, hack, start || token);
  3649. this._ws();
  3650. } else {
  3651. stream.unget();
  3652. }
  3653. return value;
  3654. }
  3655. _ruleset() {
  3656. const stream = this._tokenStream;
  3657. let braceOpened;
  3658. try {
  3659. stream.skipComment(undefined, true);
  3660. if (parserCache.findBlock()) {
  3661. return true;
  3662. }
  3663. parserCache.startBlock();
  3664. const selectors = this._selectorsGroup();
  3665. if (!selectors) {
  3666. parserCache.cancelBlock();
  3667. return false;
  3668. }
  3669. parserCache.adjustBlockStart(selectors[0]);
  3670. this.fire({type: 'startrule', selectors}, selectors[0]);
  3671. this._readDeclarations({stopAfterBrace: true});
  3672. braceOpened = true;
  3673. this.fire({type: 'endrule', selectors});
  3674. parserCache.endBlock();
  3675. this._ws();
  3676. return true;
  3677. } catch (ex) {
  3678. parserCache.cancelBlock();
  3679. if (!(ex instanceof SyntaxError) || this.options.strict) throw ex;
  3680. this.fire(Object.assign({}, ex, {type: 'error', error: ex}));
  3681. // if there's a right brace, the rule is finished so don't do anything
  3682. // otherwise, rethrow the error because it wasn't handled properly
  3683. if (braceOpened && stream.advance(Tokens.RBRACE) !== Tokens.RBRACE) throw ex;
  3684. // If even a single selector fails to parse, the entire ruleset should be thrown away,
  3685. // so we let the parser continue with the next one
  3686. return true;
  3687. }
  3688. }
  3689. _selectorsGroup() {
  3690. const stream = this._tokenStream;
  3691. const selectors = [];
  3692. let comma;
  3693. for (let sel; (sel = this._selector());) {
  3694. selectors.push(sel);
  3695. this._ws(null, true);
  3696. comma = stream.match(Tokens.COMMA);
  3697. if (!comma) break;
  3698. this._ws(null, true);
  3699. }
  3700. if (comma) stream.throwUnexpected(stream.LT(1));
  3701. return selectors.length ? selectors : null;
  3702. }
  3703. _selector() {
  3704. const stream = this._tokenStream;
  3705. const sel = [];
  3706. let nextSel = null;
  3707. let combinator = null;
  3708. nextSel = this._simpleSelectorSequence();
  3709. if (!nextSel) {
  3710. return null;
  3711. }
  3712. sel.push(nextSel);
  3713. while (true) {
  3714. combinator = this._combinator();
  3715. if (combinator) {
  3716. sel.push(combinator);
  3717. nextSel = this._simpleSelectorSequence();
  3718. if (nextSel) {
  3719. sel.push(nextSel);
  3720. continue;
  3721. }
  3722. stream.throwUnexpected(stream.LT(1));
  3723. break;
  3724. }
  3725. if (!this._ws(null, true)) {
  3726. break;
  3727. }
  3728. // make a fallback whitespace combinator
  3729. const ws = new Combinator(stream._token);
  3730. // look for an explicit combinator
  3731. combinator = this._combinator();
  3732. // selector is required if there's a combinator
  3733. nextSel = this._simpleSelectorSequence();
  3734. if (nextSel) {
  3735. sel.push(combinator || ws);
  3736. sel.push(nextSel);
  3737. } else if (combinator) {
  3738. stream.throwUnexpected(stream.LT(1));
  3739. }
  3740. }
  3741. return new Selector(sel, sel[0]);
  3742. }
  3743. _simpleSelectorSequence() {
  3744. const stream = this._tokenStream;
  3745. const start = stream.LT(1);
  3746. const modifiers = [];
  3747. const seq = [];
  3748. const ns = this._namespacePrefix(start.type);
  3749. const elementName = this._typeSelector(ns) || this._universal(ns);
  3750. if (elementName) {
  3751. seq.push(elementName);
  3752. } else if (ns) {
  3753. stream.unget();
  3754. }
  3755. while (true) {
  3756. const token = stream.get(true);
  3757. const action = Parser.ACTIONS.simpleSelectorSequence.get(token.type);
  3758. const component = action ? action.call(this, token) : (stream.unget(), 0);
  3759. if (!component) break;
  3760. modifiers.push(component);
  3761. seq.push(component);
  3762. }
  3763. const text = fastJoin(seq);
  3764. return text && new SelectorPart(elementName, modifiers, text, start);
  3765. }
  3766. _typeSelector(ns) {
  3767. const stream = this._tokenStream;
  3768. const nsSupplied = ns !== undefined;
  3769. if (!nsSupplied) ns = this._namespacePrefix();
  3770. const name = stream.match(Tokens.IDENT) &&
  3771. new SelectorSubPart(stream._token.value, 'elementName', stream._token);
  3772. if (!name) {
  3773. if (!nsSupplied && ns && ns.length > 0) stream.unget();
  3774. if (!nsSupplied && ns && ns.length > 1) stream.unget();
  3775. return null;
  3776. }
  3777. if (ns) {
  3778. name.text = ns + name.text;
  3779. name.col -= ns.length;
  3780. }
  3781. return name;
  3782. }
  3783. _hash(start) {
  3784. return new SelectorSubPart(start.value, 'id', start);
  3785. }
  3786. _class(start) {
  3787. const name = this._tokenStream.mustMatch(Tokens.IDENT, false).value;
  3788. return new SelectorSubPart('.' + name, 'class', start);
  3789. }
  3790. _namespacePrefix(next) {
  3791. const stream = this._tokenStream;
  3792. if (!next) next = stream.LA(1);
  3793. return next === Tokens.PIPE ? '|' :
  3794. (next === Tokens.IDENT || next === Tokens.STAR) && stream.LA(2) === Tokens.PIPE
  3795. ? stream.get().value + stream.get().value
  3796. : null;
  3797. }
  3798. _universal(ns = this._namespacePrefix()) {
  3799. return `${ns || ''}${this._tokenStream.match(Tokens.STAR).value || ''}` || null;
  3800. }
  3801. _attrib(start) {
  3802. const stream = this._tokenStream;
  3803. const value = [
  3804. start.value,
  3805. this._ws(),
  3806. this._namespacePrefix() || '',
  3807. stream.mustMatch(Tokens.IDENT, false).value,
  3808. this._ws(),
  3809. ];
  3810. if (stream.match(TT.attrMatch)) {
  3811. value.push(
  3812. stream._token.value,
  3813. this._ws(),
  3814. stream.mustMatch(TT.identString).value,
  3815. this._ws());
  3816. if (stream.match(Tokens.IDENT, ['i', 's'])) {
  3817. value.push(
  3818. stream._token.value,
  3819. this._ws());
  3820. }
  3821. }
  3822. value.push(stream.mustMatch(Tokens.RBRACKET).value);
  3823. return new SelectorSubPart(fastJoin(value), 'attribute', start);
  3824. }
  3825. _pseudo(start) {
  3826. const stream = this._tokenStream;
  3827. const colons = start.value + (stream.match(Tokens.COLON).value || '');
  3828. const t = stream.mustMatch(TT.pseudo);
  3829. const pseudo = t.type === Tokens.IDENT ? t.value :
  3830. t.value +
  3831. this._ws() +
  3832. (this._expression({list: true}) || '') +
  3833. stream.mustMatch(Tokens.RPAREN).value;
  3834. return new SelectorSubPart(colons + pseudo, 'pseudo', {
  3835. line: t.line,
  3836. col: t.col - colons.length,
  3837. offset: t.offset - colons.length,
  3838. });
  3839. }
  3840. _expression({calc, list} = {}) {
  3841. const chunks = [];
  3842. const stream = this._tokenStream;
  3843. while (stream.get()) {
  3844. const {type, value} = stream._token;
  3845. if (calc && type === Tokens.FUNCTION) {
  3846. if (!rxCalc.test(value)) {
  3847. stream.throwUnexpected();
  3848. }
  3849. chunks.push(value,
  3850. this._expr('calc').text,
  3851. stream.mustMatch(Tokens.RPAREN).value);
  3852. } else if (TT.expression.includes(type) || list && type === Tokens.COMMA) {
  3853. chunks.push(value, this._ws());
  3854. } else if (type !== Tokens.COMMENT) {
  3855. stream.unget();
  3856. break;
  3857. }
  3858. }
  3859. return fastJoin(chunks) || null;
  3860. }
  3861. _is(start) {
  3862. let args;
  3863. const value =
  3864. start.value +
  3865. this._ws() +
  3866. (args = this._selectorsGroup()) +
  3867. this._ws() +
  3868. this._tokenStream.mustMatch(Tokens.RPAREN).value;
  3869. const type = lower(Tokens.name(start.type));
  3870. return Object.assign(new SelectorSubPart(value, type, start), {args});
  3871. }
  3872. _negation(start) {
  3873. const stream = this._tokenStream;
  3874. const value = [start.value, this._ws()];
  3875. const args = this._selectorsGroup();
  3876. if (!args) stream.throwUnexpected(stream.LT(1));
  3877. value.push(...args, this._ws(), stream.mustMatch(Tokens.RPAREN).value);
  3878. return Object.assign(new SelectorSubPart(fastJoin(value), 'not', start), {args});
  3879. }
  3880. _declaration(consumeSemicolon, Props) {
  3881. const stream = this._tokenStream;
  3882. const property = this._property();
  3883. if (!property) {
  3884. return false;
  3885. }
  3886. stream.mustMatch(Tokens.COLON);
  3887. const value = property.text.startsWith('--')
  3888. ? this._customProperty() // whitespace is a part of custom property value
  3889. : (this._ws(), this._expr());
  3890. // if there's no parts for the value, it's an error
  3891. if (!value || value.length === 0) {
  3892. stream.throwUnexpected(stream.LT(1));
  3893. }
  3894. let invalid;
  3895. if (!this.options.skipValidation) {
  3896. try {
  3897. /* If hacks are allowed, then only check the root property.
  3898. Otherwise treat `_property` or `*property` as invalid */
  3899. const name =
  3900. this.options.starHack && property.hack === '*' ||
  3901. this.options.underscoreHack && property.hack === '_'
  3902. ? property.text
  3903. : property.toString();
  3904. validateProperty(name, property, value, Props);
  3905. } catch (ex) {
  3906. if (!(ex instanceof ValidationError)) {
  3907. ex.message = ex.stack;
  3908. }
  3909. invalid = ex;
  3910. }
  3911. }
  3912. const event = {
  3913. type: 'property',
  3914. important: stream.match(Tokens.IMPORTANT),
  3915. property,
  3916. value,
  3917. };
  3918. this._ws();
  3919. if (invalid) {
  3920. event.invalid = invalid;
  3921. event.message = invalid.message;
  3922. }
  3923. this.fire(event, property);
  3924. if (consumeSemicolon) {
  3925. while (stream.match(TT.semiS)) {/*NOP*/}
  3926. }
  3927. this._ws();
  3928. return true;
  3929. }
  3930. _expr(inFunction, endToken = Tokens.RPAREN) {
  3931. const stream = this._tokenStream;
  3932. const values = [];
  3933. while (true) {
  3934. let value = this._term(inFunction);
  3935. if (!value && !values.length) {
  3936. return null;
  3937. }
  3938. // get everything inside the parens and let validateProperty handle that
  3939. if (!value && inFunction && stream.peek() !== endToken) {
  3940. stream.get();
  3941. value = new PropertyValuePart(stream._token);
  3942. } else if (!value) {
  3943. break;
  3944. }
  3945. // TODO: remove this hack
  3946. const last = values[values.length - 1];
  3947. if (last && last.offset === value.offset && last.text === value.text) {
  3948. break;
  3949. }
  3950. values.push(value);
  3951. this._ws();
  3952. const operator = this._tokenStream.match(inFunction ? TT.opInFunc : TT.op);
  3953. if (operator) {
  3954. this._ws();
  3955. values.push(new PropertyValuePart(operator));
  3956. }
  3957. }
  3958. return values[0] ? new PropertyValue(values, values[0]) : null;
  3959. }
  3960. _customProperty() {
  3961. const value = this._tokenStream.readDeclValue();
  3962. if (value) {
  3963. const token = this._tokenStream._token;
  3964. token.value = value;
  3965. token.type = Tokens.IDENT;
  3966. return new PropertyValue([new PropertyValuePart(token)], token);
  3967. }
  3968. }
  3969. _term(inFunction) {
  3970. const stream = this._tokenStream;
  3971. const unary = stream.match(TT.plusMinus) && stream._token;
  3972. const finalize = (token, value) => {
  3973. if (!token && unary) stream.unget();
  3974. if (!token) return null;
  3975. if (token instanceof SyntaxUnit) return token;
  3976. if (unary) {
  3977. token.line = unary.line;
  3978. token.col = unary.col;
  3979. token.value = unary.value + (value || token.value);
  3980. } else if (value) {
  3981. token.value = value;
  3982. }
  3983. return new PropertyValuePart(token);
  3984. };
  3985. const next = this.options.ieFilters && stream.LT(1);
  3986. if (next && next.type === Tokens.IE_FUNCTION) {
  3987. return finalize(next, this._ieFunction());
  3988. }
  3989. // see if it's a simple block
  3990. if (stream.match(inFunction ? TT.LParenBracketBrace : TT.LParenBracket)) {
  3991. const token = stream._token;
  3992. const endToken = Tokens.type(token.endChar);
  3993. token.expr = this._expr(inFunction, endToken);
  3994. stream.mustMatch(endToken);
  3995. return finalize(token, token.value + (token.expr || '') + token.endChar);
  3996. }
  3997. return finalize(
  3998. // see if there's a simple match
  3999. stream.match(TT.term) && stream._token ||
  4000. this._hexcolor() ||
  4001. this._function({asText: Boolean(unary)}));
  4002. }
  4003. _function({asText} = {}) {
  4004. const stream = this._tokenStream;
  4005. if (!stream.match(Tokens.FUNCTION)) return null;
  4006. const start = stream._token;
  4007. const name = start.value.slice(0, -1);
  4008. this._ws();
  4009. const expr = this._expr(lower(name));
  4010. const ieFilter = this.options.ieFilters && stream.peek() === Tokens.EQUALS ?
  4011. this._functionIeFilter() : '';
  4012. const text = name + '(' + (expr || '') + ieFilter + ')';
  4013. stream.mustMatch(Tokens.RPAREN);
  4014. this._ws();
  4015. if (asText) {
  4016. return text;
  4017. }
  4018. const m = rxVendorPrefix.exec(name) || [];
  4019. return SyntaxUnit.addFuncInfo(
  4020. new SyntaxUnit(text, start, 'function', {
  4021. expr,
  4022. name: m[2] || name,
  4023. prefix: m[1] || '',
  4024. tokenType: Tokens.FUNCTION,
  4025. }));
  4026. }
  4027. _functionIeFilter() {
  4028. const stream = this._tokenStream;
  4029. const text = [];
  4030. do {
  4031. if (this._ws()) {
  4032. text.push(stream._token.value);
  4033. }
  4034. // might be second time in the loop
  4035. if (stream.LA(0) === Tokens.COMMA) {
  4036. text.push(stream._token.value);
  4037. }
  4038. stream.match(Tokens.IDENT);
  4039. text.push(stream._token.value);
  4040. stream.match(Tokens.EQUALS);
  4041. text.push(stream._token.value);
  4042. let lt = stream.peek();
  4043. while (lt !== Tokens.COMMA &&
  4044. lt !== Tokens.S &&
  4045. lt !== Tokens.RPAREN &&
  4046. lt !== Tokens.EOF) {
  4047. stream.get();
  4048. text.push(stream._token.value);
  4049. lt = stream.peek();
  4050. }
  4051. } while (stream.match([Tokens.COMMA, Tokens.S]));
  4052. return fastJoin(text);
  4053. }
  4054. _ieFunction() {
  4055. const stream = this._tokenStream;
  4056. const text = [];
  4057. // IE function can begin like a regular function, too
  4058. if (stream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])) {
  4059. text.push(stream._token.value);
  4060. do {
  4061. text.push(
  4062. stream._token.value === ',' ? ',' : '', // subsequent loops
  4063. this._ws(),
  4064. stream.match(Tokens.IDENT).value || '',
  4065. stream.match(Tokens.EQUALS).value || '');
  4066. // functionText.push(this._term());
  4067. while (!/^([,)]|\s+)$/.test(stream.LT(1).value)) {
  4068. text.push(stream.get(true).value);
  4069. }
  4070. } while (stream.match([Tokens.COMMA, Tokens.S]));
  4071. text.push(stream.match(Tokens.RPAREN).value);
  4072. this._ws();
  4073. }
  4074. return fastJoin(text) || null;
  4075. }
  4076. _hexcolor() {
  4077. const stream = this._tokenStream;
  4078. if (!stream.match(Tokens.HASH)) return null;
  4079. const token = stream._token;
  4080. const color = token.value;
  4081. const len = color.length;
  4082. if (len !== 4 && len !== 5 && len !== 7 && len !== 9 ||
  4083. !/^#([a-f\d]{3}(?:[a-f\d](?:[a-f\d]{2}){0,2})?)$/i.test(color)) {
  4084. throw new SyntaxError(`Expected a hex color but found '${color}'.`, token);
  4085. }
  4086. this._ws();
  4087. return token;
  4088. }
  4089. _keyframes(start) {
  4090. const stream = this._tokenStream;
  4091. const prefix = rxVendorPrefix.test(start.value) ? RegExp.$1 : '';
  4092. this._ws();
  4093. const name = this._keyframeName();
  4094. stream.mustMatch(Tokens.LBRACE);
  4095. this.fire({type: 'startkeyframes', name, prefix}, start);
  4096. // check for key
  4097. while (true) {
  4098. this._ws();
  4099. const tt = stream.peek();
  4100. if (tt !== Tokens.IDENT && tt !== Tokens.PERCENTAGE) break;
  4101. this._keyframeRule();
  4102. }
  4103. stream.mustMatch(Tokens.RBRACE);
  4104. this.fire({type: 'endkeyframes', name, prefix});
  4105. this._ws();
  4106. }
  4107. _keyframeName() {
  4108. const stream = this._tokenStream;
  4109. stream.mustMatch(TT.identString);
  4110. return SyntaxUnit.fromToken(stream._token);
  4111. }
  4112. _keyframeRule() {
  4113. const keys = this._keyList();
  4114. this.fire({type: 'startkeyframerule', keys}, keys[0]);
  4115. this._readDeclarations();
  4116. this.fire({type: 'endkeyframerule', keys});
  4117. }
  4118. _keyList() {
  4119. const stream = this._tokenStream;
  4120. const keyList = [];
  4121. // must be least one key
  4122. keyList.push(this._key());
  4123. this._ws();
  4124. while (stream.match(Tokens.COMMA)) {
  4125. this._ws();
  4126. keyList.push(this._key());
  4127. this._ws();
  4128. }
  4129. return keyList;
  4130. }
  4131. _key() {
  4132. const stream = this._tokenStream;
  4133. if (stream.match(Tokens.PERCENTAGE)) {
  4134. return SyntaxUnit.fromToken(stream._token);
  4135. }
  4136. if (stream.match(Tokens.IDENT)) {
  4137. if (/^(from|to)$/i.test(stream._token.value)) {
  4138. return SyntaxUnit.fromToken(stream._token);
  4139. }
  4140. stream.unget();
  4141. }
  4142. // if it gets here, there wasn't a valid token, so time to explode
  4143. stream.throwUnexpected(stream.LT(1), ['%', "'from'", "'to'"]);
  4144. }
  4145. _skipCruft() {
  4146. while (this._tokenStream.match(TT.cruft)) { /*NOP*/ }
  4147. }
  4148. /**
  4149. * @param {{}} [_]
  4150. * @param {Boolean} [_.checkStart] - check for the left brace at the beginning.
  4151. * @param {Boolean} [_.readMargins] - check for margin patterns.
  4152. * @param {Boolean} [_.stopAfterBrace] - stop after the final } without consuming whitespace
  4153. * @param {{}} [_.Props] - definitions of valid properties
  4154. */
  4155. _readDeclarations({
  4156. checkStart = true,
  4157. readMargins = false,
  4158. stopAfterBrace = false,
  4159. Props,
  4160. } = {}) {
  4161. const stream = this._tokenStream;
  4162. if (checkStart) stream.mustMatch(Tokens.LBRACE);
  4163. let next, tt;
  4164. while ((next = stream.get(true)).value !== '}' && (tt = next.type)) {
  4165. try {
  4166. // Pre-check to avoid calling _ws too much as it's wasteful
  4167. if (tt === Tokens.S ||
  4168. tt === Tokens.COMMENT ||
  4169. tt === Tokens.USO_VAR) {
  4170. this._ws(next, true);
  4171. tt = 0;
  4172. }
  4173. if (tt === Tokens.SEMICOLON ||
  4174. readMargins && this._margin() ||
  4175. (tt && stream.unget(), this._declaration(true, Props)) ||
  4176. (next = stream.LT(1)).value === ';' ||
  4177. this._ws(next, true)) {
  4178. continue;
  4179. }
  4180. break;
  4181. } catch (ex) {
  4182. this._readDeclarationsRecovery(ex, arguments[0]);
  4183. }
  4184. }
  4185. if (next.value !== '}') stream.mustMatch(Tokens.RBRACE);
  4186. if (!stopAfterBrace) this._ws();
  4187. }
  4188. _readDeclarationsRecovery(ex) {
  4189. if (ex) {
  4190. if (this.options.strict || !(ex instanceof SyntaxError)) {
  4191. throw ex; // if not a syntax error, rethrow it
  4192. }
  4193. this.fire(Object.assign({}, ex, {
  4194. type: ex.type || 'error',
  4195. recoverable: true,
  4196. error: ex,
  4197. }));
  4198. }
  4199. switch (this._tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE])) {
  4200. case Tokens.SEMICOLON:
  4201. return true; // continue to the next declaration
  4202. case Tokens.RBRACE:
  4203. this._tokenStream.unget();
  4204. return;
  4205. default:
  4206. throw ex;
  4207. }
  4208. }
  4209. _ws(start, skipUsoVar) {
  4210. const stream = this._tokenStream;
  4211. const tokens = skipUsoVar ? TT.usoS : Tokens.S;
  4212. let ws = start ? start.value : '';
  4213. for (let t; (t = stream.LT(1, true)) && t.type === Tokens.S;) {
  4214. ws += stream.get(true).value;
  4215. }
  4216. if (stream._ltIndex === stream._ltAhead) {
  4217. ws += stream._reader.readMatch(/\s+/y) || '';
  4218. if (!stream._reader.peekTest(/\/\*/y)) {
  4219. return ws;
  4220. }
  4221. }
  4222. while (stream.match(tokens)) {
  4223. ws += stream._token.value;
  4224. }
  4225. return ws;
  4226. }
  4227. _unknownSym(start) {
  4228. if (this.options.strict) {
  4229. throw new SyntaxError('Unknown @ rule.', start);
  4230. }
  4231. const {prelude, block} = this._tokenStream.readUnknownSym();
  4232. this.fire({type: 'unknown-at-rule', name: start.value, prelude, block}, start);
  4233. this._ws();
  4234. }
  4235. _verifyEnd() {
  4236. const stream = this._tokenStream;
  4237. if (stream.peek() !== Tokens.EOF) {
  4238. stream.throwUnexpected(stream.LT(1));
  4239. }
  4240. }
  4241. parse(input, {reuseCache} = {}) {
  4242. this._tokenStream = new TokenStream(input);
  4243. parserCache.start(reuseCache && this);
  4244. this._stylesheet();
  4245. }
  4246. parseStyleSheet(input) {
  4247. return this.parse(input);
  4248. }
  4249. parseMediaQuery(input, {reuseCache} = {}) {
  4250. this._tokenStream = new TokenStream(input);
  4251. parserCache.start(reuseCache && this);
  4252. const result = this._mediaQuery();
  4253. this._verifyEnd();
  4254. return result;
  4255. }
  4256. /**
  4257. * Parses a property value (everything after the semicolon).
  4258. * @return {PropertyValue} The property value.
  4259. * @throws parserlib.util.SyntaxError If an unexpected token is found.
  4260. */
  4261. parsePropertyValue(input) {
  4262. this._tokenStream = new TokenStream(input);
  4263. this._ws();
  4264. const result = this._expr();
  4265. this._ws();
  4266. this._verifyEnd();
  4267. return result;
  4268. }
  4269. /**
  4270. * Parses a complete CSS rule, including selectors and
  4271. * properties.
  4272. * @param {String} input The text to parser.
  4273. * @return {Boolean} True if the parse completed successfully, false if not.
  4274. */
  4275. parseRule(input, {reuseCache} = {}) {
  4276. this._tokenStream = new TokenStream(input);
  4277. parserCache.start(reuseCache && this);
  4278. this._ws();
  4279. const result = this._ruleset();
  4280. this._ws();
  4281. this._verifyEnd();
  4282. return result;
  4283. }
  4284. /**
  4285. * Parses a single CSS selector (no comma)
  4286. * @param {String} input The text to parse as a CSS selector.
  4287. * @return {Selector} An object representing the selector.
  4288. * @throws parserlib.util.SyntaxError If an unexpected token is found.
  4289. */
  4290. parseSelector(input) {
  4291. this._tokenStream = new TokenStream(input);
  4292. this._ws();
  4293. const result = this._selector();
  4294. this._ws();
  4295. this._verifyEnd();
  4296. return result;
  4297. }
  4298. /**
  4299. * Parses an HTML style attribute: a set of CSS declarations
  4300. * separated by semicolons.
  4301. * @param {String} input The text to parse as a style attribute
  4302. * @return {void}
  4303. */
  4304. parseStyleAttribute(input) {
  4305. // help error recovery in _readDeclarations()
  4306. this._tokenStream = new TokenStream(input + '}');
  4307. this._readDeclarations({checkStart: false});
  4308. }
  4309. }
  4310. Object.assign(Parser, TYPES);
  4311. Object.assign(Parser.prototype, TYPES);
  4312. Parser.prototype._readWhitespace = Parser.prototype._ws;
  4313. const symDocument = [Tokens.DOCUMENT_SYM, Parser.prototype._document];
  4314. const symDocMisplaced = [Tokens.DOCUMENT_SYM, Parser.prototype._documentMisplaced];
  4315. const symFontFace = [Tokens.FONT_FACE_SYM, Parser.prototype._fontFace];
  4316. const symKeyframes = [Tokens.KEYFRAMES_SYM, Parser.prototype._keyframes];
  4317. const symMedia = [Tokens.MEDIA_SYM, Parser.prototype._media];
  4318. const symPage = [Tokens.PAGE_SYM, Parser.prototype._page];
  4319. const symSupports = [Tokens.SUPPORTS_SYM, Parser.prototype._supports];
  4320. const symUnknown = [Tokens.UNKNOWN_SYM, Parser.prototype._unknownSym];
  4321. const symViewport = [Tokens.VIEWPORT_SYM, Parser.prototype._viewport];
  4322. Parser.ACTIONS = {
  4323. stylesheet: new Map([
  4324. symMedia,
  4325. symDocument,
  4326. symSupports,
  4327. symPage,
  4328. symFontFace,
  4329. symKeyframes,
  4330. symViewport,
  4331. symUnknown,
  4332. [Tokens.S, Parser.prototype._ws],
  4333. ]),
  4334. topDoc: new Map([
  4335. symDocument,
  4336. symUnknown,
  4337. [Tokens.S, Parser.prototype._ws],
  4338. ]),
  4339. document: new Map([
  4340. symMedia,
  4341. symDocMisplaced,
  4342. symSupports,
  4343. symPage,
  4344. symFontFace,
  4345. symViewport,
  4346. symKeyframes,
  4347. symUnknown,
  4348. ]),
  4349. supports: new Map([
  4350. symKeyframes,
  4351. symMedia,
  4352. symSupports,
  4353. symDocMisplaced,
  4354. symViewport,
  4355. symUnknown,
  4356. ]),
  4357. media: new Map([
  4358. symKeyframes,
  4359. symMedia,
  4360. symDocMisplaced,
  4361. symSupports,
  4362. symPage,
  4363. symFontFace,
  4364. symViewport,
  4365. symUnknown,
  4366. ]),
  4367. simpleSelectorSequence: new Map([
  4368. [Tokens.HASH, Parser.prototype._hash],
  4369. [Tokens.DOT, Parser.prototype._class],
  4370. [Tokens.LBRACKET, Parser.prototype._attrib],
  4371. [Tokens.COLON, Parser.prototype._pseudo],
  4372. [Tokens.IS, Parser.prototype._is],
  4373. [Tokens.ANY, Parser.prototype._is],
  4374. [Tokens.WHERE, Parser.prototype._is],
  4375. [Tokens.NOT, Parser.prototype._negation],
  4376. ]),
  4377. };
  4378. //#endregion
  4379. //#region Helper functions
  4380. function escapeChar(c) {
  4381. return c === '"' ? '\\' + c : `\\${c.codePointAt(0).toString(16)} `;
  4382. }
  4383. function fastJoin(arr) {
  4384. return !arr.length ? '' :
  4385. arr.length === 1 ? `${arr[0]}` :
  4386. arr.length === 2 ? `${arr[0]}${arr[1]}` :
  4387. arr.join('');
  4388. }
  4389. /**
  4390. * vars can span any number of grammar parts so not gonna try to guess. KISS.
  4391. * @param {PropertyValue} value
  4392. */
  4393. function hasVarParts(value) {
  4394. return value.parts.some(p => p.isVar);
  4395. }
  4396. function isPseudoElement(pseudo) {
  4397. return pseudo.startsWith('::') ||
  4398. /^:(first-(letter|line)|before|after)$/i.test(pseudo);
  4399. }
  4400. function lower(text) {
  4401. if (typeof text !== 'string') text = `${text}`;
  4402. let result = lowercaseCache.get(text);
  4403. if (result) return result;
  4404. result = text.toLowerCase();
  4405. lowercaseCache.set(text, result);
  4406. return result;
  4407. }
  4408. function lowerCmp(a, b) {
  4409. return a.length === b.length && (a === b || lower(a) === lower(b));
  4410. }
  4411. /** @this {String} */
  4412. function lowerCmpThis(a) {
  4413. return a.length === this.length && (a === this || lower(a) === lower(this));
  4414. }
  4415. function parseString(str) {
  4416. return str.slice(1, -1) // strip surrounding quotes
  4417. .replace(/\\(\r\n|[^\r0-9a-f]|[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)/ig, unescapeChar);
  4418. }
  4419. function serializeString(value) {
  4420. return `"${value.replace(/["\r\n\f]/g, escapeChar)}"`;
  4421. }
  4422. function unescapeChar(m, c) {
  4423. if (c === '\n' || c === '\r\n' || c === '\r' || c === '\f') {
  4424. return '';
  4425. }
  4426. m = /^[0-9a-f]{1,6}/i.exec(c);
  4427. return m ? String.fromCodePoint(parseInt(m[0], 16)) : c;
  4428. }
  4429. //#endregion
  4430. //#region PUBLIC API
  4431. /** @namespace parserlib */
  4432. return {
  4433. css: {
  4434. Colors,
  4435. Combinator,
  4436. GlobalKeywords,
  4437. Matcher,
  4438. MediaFeature,
  4439. MediaQuery,
  4440. Parser,
  4441. Properties,
  4442. PropertyName,
  4443. PropertyValue,
  4444. PropertyValuePart,
  4445. ScopedProperties,
  4446. Selector,
  4447. SelectorPart,
  4448. SelectorSubPart,
  4449. Specificity,
  4450. TokenStream,
  4451. Tokens,
  4452. ValidationError,
  4453. },
  4454. util: {
  4455. EventTarget,
  4456. StringReader,
  4457. SyntaxError,
  4458. SyntaxUnit,
  4459. TokenStreamBase,
  4460. fastJoin,
  4461. isPseudoElement,
  4462. lower,
  4463. rxVendorPrefix,
  4464. describeProp: vtExplode,
  4465. },
  4466. cache: parserCache,
  4467. };
  4468. //#endregion
  4469. })();