Skip to content

Instantly share code, notes, and snippets.

@jspears
Created March 28, 2022 17:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jspears/15dc0974ced4b562e8200861788f2082 to your computer and use it in GitHub Desktop.
Save jspears/15dc0974ced4b562e8200861788f2082 to your computer and use it in GitHub Desktop.
Parsing CSS with TypeScript Types

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

@nsaunders
Copy link

Thanks for sharing @jspears. I do think there's a practical application for this. Would you be able to offer a license?

@jspears
Copy link
Author

jspears commented Jan 3, 2023

Thanks for sharing @jspears. I do think there's a practical application for this. Would you be able to offer a license?

These are all for examples for educational purposes so no license is necessary. I am super curious as to the usage though. I must warn you however this is not a complete CSS parser.

However if you would like a more explicit license let me know what works best for you. Otherwise MIT is my goto.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment