Parsing CSS with TypeScript Types

3 min read Original article ↗

Thought I would write a CSS Parser in TypeScript Types. Like all these exercises there is no practical application, but I thought they are fun.

Maybe there is something to learn, cause I did.

type WS = ' ' | '\n' | '\t';

type Trim<T> = T extends (`${WS}${infer V}` | `${infer V}${WS}`) ? Trim<V> : T;

type AddKey<O extends {}, Key , Value, Z = Trim<Key>> = Trim<Z> extends '' ? Value : Z extends PropertyKey ? {[k in Z]:Value} & (Z extends keyof O ? Exclude<O, Z> : O) : Value;

type _ParseComment<T> = Trim<T> extends `/*${infer Comment}*/${infer Rest}` ? [Comment, Rest] : ['', T]

type _ParseSelector<T> = T extends `${infer Selector}{${infer Rest}` ? Trim<Selector> extends '' ? ['' , T] : [Trim<Selector>, Trim<Rest>] : ['', T];

type _ParseMediaQuery<T, Ret extends {} = {}> = T extends `${infer Query}{${infer Content}` ? 
        _ParseCSS<Content> extends [infer CSSI, infer Rest] ? 
           Trim<Rest> extends `}${infer CRest}` ?  [ AddKey<Ret, Query, CSSI>, CRest ] :
            [Ret, ''] : [Ret, ''] : [Ret, ''];

type _ParseProperties<T, Ret extends {} = {}> =
    _ParseComment<T> extends [string, infer PR] ?
    Trim<PR> extends '' ? [Ret, ''] :
    Trim<PR> extends `}${infer Rest}` ? [Ret, Rest] :
    Trim<PR> extends `${infer Key}:${infer Val};${infer Rest}` ? _ParseProperties<Rest, AddKey<Ret, Key, Trim<Val>>> :
    Trim<PR> extends `${infer Key}:${infer Val}}${infer Rest}` ? [ AddKey<Ret, Key, Trim<Val>>, Rest] :
    [Ret, PR]: [Ret, T];

type _ParseCSS<S, Ret extends {} = {}> = 
    Trim<S> extends '' ? [Ret, ''] :
    Trim<S> extends `}${string}` ? [ Ret, S ] :
    _ParseComment<S> extends [string, infer CRest] ? 
     _ParseSelector<CRest> extends [infer Selector, infer Rest] ?
        _ParseProperties<Rest> extends [infer Props, infer PRest] ? _ParseCSS<PRest, AddKey<Ret, Selector, Props>> : never : never : never ;

type ParseCSS<S, Ret extends {} = {}> =
    Trim<S> extends '' ? Ret : 
    S extends `${infer Before}@media${infer Media}` ?  
    _ParseCSS<Before, Ret> extends [infer O, string] ?
       _ParseMediaQuery<Media, O> extends [infer R, infer Con] ? ParseCSS<Con, R> : never : ParseCSS<S, Ret> : _ParseCSS<S> extends [infer R, string] ? R : never;
            
type MQ = _ParseMediaQuery<`only screen and (max-width: 600px) { body { background-color: lightblue; } } `>;
type PC1 = ParseCSS<'.stuff { color:white }'>;
type PC1S = PC1['.stuff']['color'];

type PC2 = ParseCSS<'/* what */ .stuff { color:white; }'>;
type PC3 = ParseCSS<'/* what */ .stuff { color:white; background:red }'>;
type PC4 = ParseCSS<' .stuff { color:white; /* what */background:red }'>;
type PC5 = ParseCSS<`.stuff { color:white;background:red } 
    .selector2 {    
         font:sans-serif    
    }`>;
type PC51 = PC5['.selector2']['font'];

type _PC = _ParseCSS<`.stuff { color:white;background:red }`>;


type TQ1 = ParseCSS<`.stuff {  
    background-color: lightblue; 
    }`>;

type MQ1 = ParseCSS<`@media only screen and (max-width: 600px) { body { background-color: lightblue; } }`>;
type MQ2 = ParseCSS<`@media only screen and (max-width: 600px) { body { background-color: lightblue; } }`>;
type MQ3 = ParseCSS<`body { color: pink } @media only screen and (max-width: 600px) { body { background-color: lightblue; } } @media only screen { body { background-color: lightblue; } }`>;

Playground Link