let tailwindExcerpt = `/*! modern-normalize v1.0.0 | MIT License | https://github.com/sindresorhus/modern-normalize */*,::after,::before{box-sizing:border-box}:root{-moz-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji'}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:transparent;background-image:none}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}fieldset{margin:0;padding:0}ol,ul{list-style:none;margin:0;padding:0}html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";line-height:1.5}body{font-family:inherit;line-height:inherit}*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::placeholder,textarea::placeholder{color:#9ca3af}[role=button],button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.space-x-0>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(0px * var(--tw-space-x-reverse));margin-left:calc(0px * calc(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem * var(--tw-space-x-reverse));margin-left:calc(.75rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}` ; import { parse , hasMore , has , lookAhead , ParseGenerator } from './index' ; interface CSSComment { type : 'comment' ; content : string ; } interface CSSDeclaration { name : string ; rawValue : string ; } interface CSSRule { type : 'rule' ; selectors : Array < string > ; declarations : Array < CSSDeclaration > ; } interface CSSMedia { type : 'media' ; rawFeatures : string ; rules : Array < CSSRule > ; } const whitespaceMay = /^\s*/ ; function * PropertyParser ( ) { const [ name ] = yield /^[-a-z]+/ ; return name ; } function * ValueParser ( ) { const [ rawValue ] = yield /^[^;}]+/ ; return rawValue ; } function * DeclarationParser ( ) { const name = yield PropertyParser ; yield whitespaceMay ; yield ':' ; yield whitespaceMay ; const rawValue = yield ValueParser ; yield whitespaceMay ; yield has ( ';' ) ; return { name , rawValue } ; } function * SelectorComponentParser ( ) { const [ selector ] = yield /^([.#]?[-:=~*>.#\(\)\w\[\]]+)/ ; return selector ; } function * RuleParser ( ) { const declarations = [ ] ; const selectors = [ ] ; yield whitespaceMay ; while ( true ) { selectors . push ( yield SelectorComponentParser ) ; yield whitespaceMay ; if ( yield has ( ',' ) ) { yield whitespaceMay ; continue ; } if ( yield has ( '{' ) ) break ; } yield whitespaceMay ; while ( ( yield has ( '}' ) ) === false ) { declarations . push ( yield DeclarationParser ) ; yield whitespaceMay ; } return { type : 'rule' , selectors : selectors , declarations } ; } function * MediaQueryParser ( ) { yield '@media' ; yield whitespaceMay ; yield '(' ; const [ rawFeatures ] = yield /^[^)]+/ ; yield ')' ; yield whitespaceMay ; yield '{' ; const rules = yield RulesParser ; yield '}' ; return { type : 'media' , rawFeatures , rules } ; } function * CommentParser ( ) { yield '/*' ; const [ , content ] = yield /^(.*?)\*\// ; return { type : 'comment' , content } ; } function * RulesParser ( ) { const rules = [ ] ; const hasClosingParent = has ( lookAhead ( /}/ ) ) ; yield whitespaceMay ; while ( yield hasMore ) { if ( yield hasClosingParent ) break ; rules . push ( yield [ RuleParser , CommentParser ] ) ; yield whitespaceMay ; } return rules ; } function * StylesheetParser ( ) { const elements = [ ] ; yield whitespaceMay ; while ( yield hasMore ) { elements . push ( yield [ RuleParser , CommentParser , MediaQueryParser ] ) ; yield whitespaceMay ; } return elements ; } function parseCSS ( cssSource ) { return parse ( cssSource , StylesheetParser ( ) ) ; } function * generateComment ( item ) { yield '/*' ; yield item . content ; yield '*/' ; } function * generateDeclaration ( declaration ) { yield declaration . name ; yield ':' ; yield declaration . rawValue ; } function * generateRule ( item ) { yield item . selectors . join ( ',' ) ; yield '{' ; for ( const [ index , declaration ] of item . declarations . entries ( ) ) { yield * generateDeclaration ( declaration ) ; if ( index < item . declarations . length - 1 ) { yield ';' ; } } yield '}' ; } function * generateMediaSource ( item ) { yield '@media ' ; yield '(' ; yield item . rawFeatures ; yield ')' ; yield '{' ; for ( const rule of item . rules ) { yield * generateRule ( rule ) ; } yield '}' ; } function * generateCSSSource ( items ) { for ( const item of items ) { if ( item . type === 'media' ) { yield * generateMediaSource ( item ) ; } else if ( item . type === 'rule' ) { yield * generateRule ( item ) ; } else if ( item . type === 'comment' ) { yield * generateComment ( item ) ; } } } function stringifyCSS ( items ) { return Array . from ( generateCSSSource ( items ) ) . join ( '' ) ; } describe ( 'CSS values' , ( ) => { it ( 'parses 42' , ( ) => { expect ( parse ( '42' , ValueParser ( ) ) ) . toMatchObject ( { remaining : '' , result : '42' , success : true , } ) ; } ) ; it ( 'parses 1.15' , ( ) => { expect ( parse ( '1.15' , ValueParser ( ) ) ) . toMatchObject ( { remaining : '' , result : '1.15' , success : true , } ) ; } ) ; it ( 'parses 1.' , ( ) => { expect ( parse ( '1.' , ValueParser ( ) ) ) . toMatchObject ( { remaining : '' , result : '1.' , success : true , } ) ; } ) ; it ( 'parses .1' , ( ) => { expect ( parse ( '.1' , ValueParser ( ) ) ) . toMatchObject ( { remaining : '' , result : '.1' , success : true , } ) ; } ) ; it ( 'parses hex color' , ( ) => { expect ( parse ( '#e5e7eb' , ValueParser ( ) ) ) . toMatchObject ( { remaining : '' , result : '#e5e7eb' , success : true , } ) ; } ) ; it ( 'parses 100%' , ( ) => { expect ( parse ( '100%' , ValueParser ( ) ) ) . toMatchObject ( { remaining : '' , result : '100%' , success : true , } ) ; } ) ; it ( 'parses font stack' , ( ) => { expect ( parse ( `system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji'` , ValueParser ( ) ) ) . toMatchObject ( { remaining : '' , result : `system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji'` , success : true , } ) ; } ) ; it ( 'parses border long-form' , ( ) => { expect ( parse ( `1px solid black` , ValueParser ( ) ) ) . toMatchObject ( { remaining : '' , result : `1px solid black` , success : true , } ) ; } ) ; it ( 'parses var' , ( ) => { expect ( parse ( `var(--primary)` , ValueParser ( ) ) ) . toMatchObject ( { remaining : '' , result : `var(--primary)` , success : true , } ) ; } ) ; } ) ; describe ( 'selectors' , ( ) => { it ( 'parses .container' , ( ) => { expect ( parse ( '.container' , SelectorComponentParser ( ) ) ) . toEqual ( { remaining : '' , result : '.container' , success : true , } ) ; } ) ; } ) ; describe ( 'media queries' , ( ) => { it ( 'parses empty' , ( ) => { expect ( parse ( `@media (min-width:640px){}` , MediaQueryParser ( ) as any ) ) . toEqual ( { remaining : '' , result : { rawFeatures : 'min-width:640px' , rules : [ ] , type : 'media' , } , success : true , } ) ; } ) ; it ( 'parses with class' , ( ) => { expect ( parse ( `@media (min-width:640px){.container{max-width:640px}}` , MediaQueryParser ( ) as any ) ) . toEqual ( { remaining : '' , result : { rawFeatures : 'min-width:640px' , rules : [ { declarations : [ { name : 'max-width' , rawValue : '640px' , } , ] , selectors : [ '.container' ] , type : 'rule' , } , ] , type : 'media' , } , success : true , } ) ; } ) ; } ) ; it ( 'parses Tailwind excerpt' , ( ) => { const result = parseCSS ( tailwindExcerpt ) ; expect ( result . success ) . toBe ( true ) ; } ) ; it ( 'parses and stringifies Tailwind excerpt' , ( ) => { const result = parseCSS ( tailwindExcerpt ) ; if ( result . success !== true ) { fail ( 'Parsing failed' ) ; } expect ( stringifyCSS ( result . result ) ) . toEqual ( tailwindExcerpt ) ; } ) ;