Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
refactor class name extraction and stringify
Brad Cornes <brad@parall.ax>
4 years ago
7 changed files, 286 additions(+), 213 deletions(-)
M packages/tailwindcss-class-names/src/extractClassNames.mjs -> packages/tailwindcss-class-names/src/extractClassNames.mjs
diff --git a/packages/tailwindcss-class-names/src/extractClassNames.mjs b/packages/tailwindcss-class-names/src/extractClassNames.mjs
index c802e4e6bee916c81bf4ac80acf0f3439471a671..4ccb6622101ef1538bfffa21842e3265d0706a23 100644
--- a/packages/tailwindcss-class-names/src/extractClassNames.mjs
+++ b/packages/tailwindcss-class-names/src/extractClassNames.mjs
@@ -18,20 +18,6 @@   const classNames = []
   const { nodes: subSelectors } = selectorParser().astSync(selector)
 
   for (let i = 0; i < subSelectors.length; i++) {
-    // const final = subSelectors[i].nodes[subSelectors[i].nodes.length - 1]
-
-    // if (final.type === 'class') {
-    //   const scope = subSelectors[i].nodes.slice(
-    //     0,
-    //     subSelectors[i].nodes.length - 1
-    //   )
-
-    //   classNames.push({
-    //     className: String(final).trim(),
-    //     scope: createSelectorFromNodes(scope)
-    //   })
-    // }
-
     let scope = []
     for (let j = 0; j < subSelectors[i].nodes.length; j++) {
       let node = subSelectors[i].nodes[j]
@@ -47,39 +33,28 @@           next = subSelectors[i].nodes[j + 1]
         }
 
         classNames.push({
-          className: String(node)
-            .trim()
-            .substr(1),
+          className: node.value.trim(),
           scope: createSelectorFromNodes(scope),
           __rule: j === subSelectors[i].nodes.length - 1,
-          // __pseudo: createSelectorFromNodes(pseudo)
-          __pseudo: pseudo.length === 0 ? null : pseudo.map(String)
+          __pseudo: pseudo.length === 0 ? null : pseudo.map(String),
         })
       }
       scope.push(node, ...pseudo)
     }
   }
 
-  // console.log(classNames)
-
   return classNames
 }
 
-// console.log(getClassNamesFromSelector('h1, h2, h3, .foo .bar, .baz'))
-
-// const css = fs.readFileSync(path.resolve(__dirname, 'tailwind.css'), 'utf8')
-
 async function process(ast) {
-  const start = new Date()
-
   const tree = {}
   const commonContext = {}
 
-  ast.root.walkRules(rule => {
+  ast.root.walkRules((rule) => {
     const classNames = getClassNamesFromSelector(rule.selector)
 
-    const decls = { __decls: true }
-    rule.walkDecls(decl => {
+    const decls = {}
+    rule.walkDecls((decl) => {
       decls[decl.prop] = decl.value
     })
 
@@ -96,49 +71,48 @@     for (let i = 0; i < classNames.length; i++) {
       const context = keys.concat([])
       const baseKeys = classNames[i].className.split('__TAILWIND_SEPARATOR__')
       const contextKeys = baseKeys.slice(0, baseKeys.length - 1)
+      const index = []
 
-      if (classNames[i].scope) {
-        let index = []
-        const existing = dlv(tree, baseKeys)
-        if (typeof existing !== 'undefined') {
-          if (Array.isArray(existing)) {
-            const scopeIndex = existing.findIndex(
-              x => x.__scope === classNames[i].scope
-            )
-            if (scopeIndex > -1) {
-              keys.unshift(scopeIndex)
-              index.push(scopeIndex)
-            } else {
-              keys.unshift(existing.length)
-              index.push(existing.length)
-            }
+      const existing = dlv(tree, baseKeys)
+      if (typeof existing !== 'undefined') {
+        if (Array.isArray(existing)) {
+          const scopeIndex = existing.findIndex(
+            (x) =>
+              x.__scope === classNames[i].scope &&
+              arraysEqual(existing.__context, context)
+          )
+          if (scopeIndex > -1) {
+            keys.unshift(scopeIndex)
+            index.push(scopeIndex)
           } else {
-            if (existing.__scope !== classNames[i].scope) {
-              dset(tree, baseKeys, [existing])
-              keys.unshift(1)
-              index.push(1)
-            }
+            keys.unshift(existing.length)
+            index.push(existing.length)
           }
-        }
-        if (classNames[i].__rule) {
-          dset(tree, [...baseKeys, ...index, '__rule'], true)
-          dsetEach(tree, [...baseKeys, ...keys], decls)
-        }
-        if (classNames[i].__pseudo) {
-          dset(tree, [...baseKeys, ...keys, '__pseudo'], classNames[i].__pseudo)
-        }
-        dset(tree, [...baseKeys, ...index, '__scope'], classNames[i].scope)
-      } else {
-        if (classNames[i].__rule) {
-          dset(tree, [...baseKeys, '__rule'], true)
-          dsetEach(tree, [...baseKeys, ...keys], decls)
         } else {
-          dset(tree, [...baseKeys, ...keys], {})
+          if (
+            existing.__scope !== classNames[i].scope ||
+            !arraysEqual(existing.__context, context)
+          ) {
+            dset(tree, baseKeys, [existing])
+            keys.unshift(1)
+            index.push(1)
+          }
         }
-        if (classNames[i].__pseudo) {
-          dset(tree, [...baseKeys, ...keys, '__pseudo'], classNames[i].__pseudo)
-        }
+      }
+      if (classNames[i].__rule) {
+        dset(tree, [...baseKeys, ...index, '__rule'], true)
+
+        dsetEach(tree, [...baseKeys, ...index], decls)
+      }
+      if (classNames[i].__pseudo) {
+        dset(tree, [...baseKeys, '__pseudo'], classNames[i].__pseudo)
       }
+      dset(tree, [...baseKeys, ...index, '__scope'], classNames[i].scope)
+      dset(
+        tree,
+        [...baseKeys, ...index, '__context'],
+        context.concat([]).reverse()
+      )
 
       // common context
       if (classNames[i].__pseudo) {
@@ -157,15 +131,12 @@         }
       }
     }
   })
-  //   console.log(`${new Date() - start}ms`)
-  // console.log(tree)
-  // console.log(commonContext)
 
   return { classNames: tree, context: commonContext }
 }
 
 function intersection(arr1, arr2) {
-  return arr1.filter(value => arr2.indexOf(value) !== -1)
+  return arr1.filter((value) => arr2.indexOf(value) !== -1)
 }
 
 function dsetEach(obj, keys, values) {
@@ -175,14 +146,15 @@     dset(obj, [...keys, k[i]], values[k[i]])
   }
 }
 
-export default process
+function arraysEqual(a, b) {
+  if (a === b) return true
+  if (a == null || b == null) return false
+  if (a.length !== b.length) return false
+
+  for (let i = 0; i < a.length; ++i) {
+    if (a[i] !== b[i]) return false
+  }
+  return true
+}
 
-// process(`
-// .bg-red {
-//     background-color: red;
-//   }
-//   .bg-red {
-//     color: white;
-//   }`).then(x => {
-//   console.log(x)
-// })
+export default process
M packages/tailwindcss-class-names/tests/extractClassNames.test.js -> packages/tailwindcss-class-names/tests/extractClassNames.test.js
diff --git a/packages/tailwindcss-class-names/tests/extractClassNames.test.js b/packages/tailwindcss-class-names/tests/extractClassNames.test.js
index b276787c05b815552a7f1ca835d2efa5ac5fe396..19290e671a71bea06ee61060cba5c41002db3ce1 100644
--- a/packages/tailwindcss-class-names/tests/extractClassNames.test.js
+++ b/packages/tailwindcss-class-names/tests/extractClassNames.test.js
@@ -3,9 +3,73 @@ const esmImport = require('esm')(module)
 const process = esmImport('../src/extractClassNames.mjs').default
 postcss = postcss([postcss.plugin('no-op', () => () => {})])
 
-const processCss = async css =>
+const processCss = async (css) =>
   process(await postcss.process(css, { from: undefined }))
 
+test('processes default container plugin', async () => {
+  const result = await processCss(`
+    .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
+      }
+    }
+  `)
+  expect(result).toEqual({
+    context: {},
+    classNames: {
+      container: [
+        { __context: [], __rule: true, __scope: null, width: '100%' },
+        {
+          __rule: true,
+          __scope: null,
+          __context: ['@media (min-width: 640px)'],
+          'max-width': '640px',
+        },
+        {
+          __rule: true,
+          __scope: null,
+          __context: ['@media (min-width: 768px)'],
+          'max-width': '768px',
+        },
+        {
+          __rule: true,
+          __scope: null,
+          __context: ['@media (min-width: 1024px)'],
+          'max-width': '1024px',
+        },
+        {
+          __rule: true,
+          __scope: null,
+          __context: ['@media (min-width: 1280px)'],
+          'max-width': '1280px',
+        },
+      ],
+    },
+  })
+})
+
 test('foo', async () => {
   const result = await processCss(`
     @media (min-width: 640px) {
@@ -24,43 +88,42 @@
   expect(result).toEqual({
     context: {
       sm: ['@media (min-width: 640px)'],
-      hover: [':hover']
+      hover: [':hover'],
     },
     classNames: {
       sm: {
         'bg-red': {
           __rule: true,
-          '@media (min-width: 640px)': {
-            __decls: true,
-            'background-color': 'red'
-          }
+          __scope: null,
+          __context: ['@media (min-width: 640px)'],
+          'background-color': 'red',
         },
         hover: {
           'bg-red': {
             __rule: true,
-            '@media (min-width: 640px)': {
-              __decls: true,
-              __pseudo: [':hover'],
-              'background-color': 'red'
-            }
-          }
-        }
+            __scope: null,
+            __context: ['@media (min-width: 640px)'],
+            __pseudo: [':hover'],
+            'background-color': 'red',
+          },
+        },
       },
       hover: {
         'bg-red': {
           __rule: true,
-          __decls: true,
+          __scope: null,
           __pseudo: [':hover'],
-          'background-color': 'red'
-        }
-      }
-    }
+          __context: [],
+          'background-color': 'red',
+        },
+      },
+    },
   })
 })
 
-test('processes basic css', async () => {
+test.only('processes basic css', async () => {
   const result = await processCss(`
-    .bg-red {
+    .bg-red\\:foo {
       background-color: red;
     }
   `)
@@ -70,10 +133,11 @@     context: {},
     classNames: {
       'bg-red': {
         __rule: true,
-        __decls: true,
-        'background-color': 'red'
-      }
-    }
+        __scope: null,
+        __context: [],
+        'background-color': 'red',
+      },
+    },
   })
 })
 
@@ -89,11 +153,12 @@     context: {},
     classNames: {
       'bg-red': {
         __rule: true,
-        __decls: true,
+        __scope: null,
+        __context: [],
         __pseudo: [':first-child', '::after'],
-        'background-color': 'red'
-      }
-    }
+        'background-color': 'red',
+      },
+    },
   })
 })
 
@@ -108,15 +173,17 @@   expect(result).toEqual({
     context: {},
     classNames: {
       scope: {
-        __pseudo: [':hover']
+        __context: [],
+        __pseudo: [':hover'],
+        __scope: null,
       },
       'bg-red': {
+        __context: [],
         __rule: true,
-        __decls: true,
         __scope: '.scope:hover',
-        'background-color': 'red'
-      }
-    }
+        'background-color': 'red',
+      },
+    },
   })
 })
 
@@ -133,15 +200,17 @@     context: {},
     classNames: {
       'bg-red': {
         __rule: true,
-        __decls: true,
-        'background-color': 'red'
+        __scope: null,
+        __context: [],
+        'background-color': 'red',
       },
       'bg-red-again': {
         __rule: true,
-        __decls: true,
-        'background-color': 'red'
-      }
-    }
+        __scope: null,
+        __context: [],
+        'background-color': 'red',
+      },
+    },
   })
 })
 
@@ -159,12 +228,35 @@     context: {},
     classNames: {
       'bg-red': {
         __rule: true,
-        '@media (min-width: 768px)': {
-          __decls: true,
-          'background-color': 'red'
+        __scope: null,
+        __context: ['@media (min-width: 768px)'],
+        'background-color': 'red',
+      },
+    },
+  })
+})
+
+test('processes nested at-rules', async () => {
+  const result = await processCss(`
+    @supports (display: grid) {
+      @media (min-width: 768px) {
+        .bg-red {
+          background-color: red;
         }
       }
     }
+  `)
+
+  expect(result).toEqual({
+    context: {},
+    classNames: {
+      'bg-red': {
+        __rule: true,
+        __scope: null,
+        __context: ['@supports (display: grid)', '@media (min-width: 768px)'],
+        'background-color': 'red',
+      },
+    },
   })
 })
 
@@ -183,11 +275,12 @@     context: {},
     classNames: {
       'bg-red': {
         __rule: true,
-        __decls: true,
+        __scope: null,
+        __context: [],
         'background-color': 'red',
-        color: 'white'
-      }
-    }
+        color: 'white',
+      },
+    },
   })
 })
 
@@ -201,14 +294,17 @@
   expect(result).toEqual({
     context: {},
     classNames: {
-      scope: {},
+      scope: {
+        __context: [],
+        __scope: null,
+      },
       'bg-red': {
         __rule: true,
-        __decls: true,
+        __context: [],
         __scope: '.scope',
-        'background-color': 'red'
-      }
-    }
+        'background-color': 'red',
+      },
+    },
   })
 })
 
@@ -228,29 +324,29 @@
   expect(result).toEqual({
     context: {},
     classNames: {
-      scope1: {},
-      scope2: {},
-      scope3: {},
+      scope1: { __context: [], __scope: null },
+      scope2: { __context: [], __scope: null },
+      scope3: { __context: [], __scope: null },
       'bg-red': [
         {
           __rule: true,
-          __decls: true,
+          __context: [],
           __scope: '.scope1',
-          'background-color': 'red'
+          'background-color': 'red',
         },
         {
           __rule: true,
-          __decls: true,
+          __context: [],
           __scope: '.scope2 +',
-          'background-color': 'red'
+          'background-color': 'red',
         },
         {
           __rule: true,
-          __decls: true,
+          __context: [],
           __scope: '.scope3 >',
-          'background-color': 'red'
-        }
-      ]
-    }
+          'background-color': 'red',
+        },
+      ],
+    },
   })
 })
M packages/tailwindcss-language-server/src/providers/completionProvider.ts -> packages/tailwindcss-language-server/src/providers/completionProvider.ts
diff --git a/packages/tailwindcss-language-server/src/providers/completionProvider.ts b/packages/tailwindcss-language-server/src/providers/completionProvider.ts
index 84c54c72463959fc711571a6c0c08b6845058f4f..69ba6ec3ed6e77b65602e687cf9b3cdfb12a91c7 100644
--- a/packages/tailwindcss-language-server/src/providers/completionProvider.ts
+++ b/packages/tailwindcss-language-server/src/providers/completionProvider.ts
@@ -27,6 +27,7 @@   // TODO
   let sep = ':'
   let parts = partialClassName.split(sep)
   let subset: any
+  let subsetKey: string[] = []
   let isSubset: boolean = false
 
   let replacementRange = {
@@ -42,6 +43,7 @@     let keys = parts.slice(0, i).filter(Boolean)
     subset = dlv(state.classNames.classNames, keys)
     if (typeof subset !== 'undefined' && typeof subset.__rule === 'undefined') {
       isSubset = true
+      subsetKey = keys
       replacementRange = {
         ...replacementRange,
         start: {
@@ -62,7 +64,7 @@     items: Object.keys(isSubset ? subset : state.classNames.classNames).map(
       (className) => {
         let kind: CompletionItemKind = CompletionItemKind.Constant
         let documentation: string = null
-        if (isContextItem(state, [className])) {
+        if (isContextItem(state, [...subsetKey, className])) {
           kind = CompletionItemKind.Module
         } else {
           const color = getColor(state, [className])
@@ -76,6 +78,7 @@         return {
           label: className,
           kind,
           documentation,
+          data: [...subsetKey, className],
           textEdit: {
             newText: className,
             range: replacementRange,
@@ -514,20 +517,20 @@   ) {
     return item
   }
 
-  const className = state.classNames.classNames[item.label]
-  if (isContextItem(state, [item.label])) {
-    item.detail = state.classNames.context[item.label].join(', ')
+  const className = dlv(state.classNames.classNames, item.data)
+  if (isContextItem(state, item.data)) {
+    item.detail = state.classNames.context[
+      item.data[item.data.length - 1]
+    ].join(', ')
   } else {
     item.detail = getCssDetail(state, className)
     if (!item.documentation) {
-      item.documentation = stringifyCss(className)
-      if (item.detail === item.documentation) {
-        item.documentation = null
-      } else {
-        // item.documentation = {
-        //   kind: MarkupKind.Markdown,
-        //   value: ['```css', item.documentation, '```'].join('\n')
-        // }
+      const css = stringifyCss(item.data.join(':'), className)
+      if (css) {
+        item.documentation = {
+          kind: MarkupKind.Markdown,
+          value: ['```css', css, '```'].join('\n'),
+        }
       }
     }
   }
@@ -537,7 +540,8 @@
 function isContextItem(state: State, keys: string[]): boolean {
   const item = dlv(state.classNames.classNames, keys)
   return Boolean(
-    !item.__rule &&
+    isObject(item) &&
+      !item.__rule &&
       !Array.isArray(item) &&
       state.classNames.context[keys[keys.length - 1]]
   )
@@ -555,13 +559,8 @@ function getCssDetail(state: State, className: any): string {
   if (Array.isArray(className)) {
     return `${className.length} rules`
   }
-  let withoutMeta = removeMeta(className)
-  if (className.__decls === true) {
-    return stringifyDecls(withoutMeta)
+  if (className.__rule === true) {
+    return stringifyDecls(removeMeta(className))
   }
-  let keys = Object.keys(withoutMeta)
-  if (keys.length === 1) {
-    return getCssDetail(state, className[keys[0]])
-  }
-  return `${keys.length} rules`
+  return null
 }
M packages/tailwindcss-language-server/src/providers/hoverProvider.ts -> packages/tailwindcss-language-server/src/providers/hoverProvider.ts
diff --git a/packages/tailwindcss-language-server/src/providers/hoverProvider.ts b/packages/tailwindcss-language-server/src/providers/hoverProvider.ts
index 5ce792101bf49e000c10ce9bfb4523f44eda5827..20147baff96b89fdf928f536d26634892e9ca10e 100644
--- a/packages/tailwindcss-language-server/src/providers/hoverProvider.ts
+++ b/packages/tailwindcss-language-server/src/providers/hoverProvider.ts
@@ -6,7 +6,6 @@   getClassNameParts,
 } from '../util/getClassNameAtPosition'
 import { stringifyCss, stringifyConfigValue } from '../util/stringify'
 const dlv = require('dlv')
-import escapeClassName from 'css.escape'
 import { isHtmlContext } from '../util/html'
 import { isCssContext } from '../util/css'
 
@@ -90,21 +89,11 @@
   return {
     contents: {
       language: 'css',
-      value: stringifyCss(dlv(state.classNames.classNames, parts), {
-        selector: augmentClassName(parts, state),
-      }),
+      value: stringifyCss(
+        hovered.className,
+        dlv(state.classNames.classNames, parts)
+      ),
     },
     range: hovered.range,
   }
 }
-
-// TODO
-function augmentClassName(className: string | string[], state: State): string {
-  const parts = Array.isArray(className)
-    ? className
-    : getClassNameParts(state, className)
-  const obj = dlv(state.classNames.classNames, parts)
-  const pseudo = obj.__pseudo ? obj.__pseudo.join('') : ''
-  const scope = obj.__scope ? `${obj.__scope} ` : ''
-  return `${scope}.${escapeClassName(parts.join(state.separator))}${pseudo}`
-}
M packages/tailwindcss-language-server/src/util/color.ts -> packages/tailwindcss-language-server/src/util/color.ts
diff --git a/packages/tailwindcss-language-server/src/util/color.ts b/packages/tailwindcss-language-server/src/util/color.ts
index 0ac2f8a32fdda94058a64b233e2b834f5dd9ef4e..bb44bba0b14ca9d8ed3c17e67d8f16186164a834 100644
--- a/packages/tailwindcss-language-server/src/util/color.ts
+++ b/packages/tailwindcss-language-server/src/util/color.ts
@@ -16,7 +16,7 @@   'fill',
   'outline-color',
   'stop-color',
   'stroke',
-  'text-decoration-color'
+  'text-decoration-color',
 ]
 
 const COLOR_NAMES = {
@@ -169,12 +169,12 @@   wheat: '#f5deb3',
   white: '#fff',
   whitesmoke: '#f5f5f5',
   yellow: '#ff0',
-  yellowgreen: '#9acd32'
+  yellowgreen: '#9acd32',
 }
 
 export function getColor(state: State, keys: string[]): string {
   const item = dlv(state.classNames.classNames, keys)
-  if (!item.__decls) return null
+  if (!item.__rule) return null
   const props = Object.keys(removeMeta(item))
   if (props.length === 0 || props.length > 1) return null
   const prop = props[0]
M packages/tailwindcss-language-server/src/util/getClassNameAtPosition.ts -> packages/tailwindcss-language-server/src/util/getClassNameAtPosition.ts
diff --git a/packages/tailwindcss-language-server/src/util/getClassNameAtPosition.ts b/packages/tailwindcss-language-server/src/util/getClassNameAtPosition.ts
index 709ca96fa29845b5d89327b67a8866f1211fb520..e7e0cf2cf4255e2e420c816dfc9af8e747ed265c 100644
--- a/packages/tailwindcss-language-server/src/util/getClassNameAtPosition.ts
+++ b/packages/tailwindcss-language-server/src/util/getClassNameAtPosition.ts
@@ -49,7 +49,8 @@   className = className.replace(/^\./, '')
   let parts: string[] = className.split(separator)
 
   if (parts.length === 1) {
-    return dlv(state.classNames.classNames, [className, '__rule']) === true
+    return dlv(state.classNames.classNames, [className, '__rule']) === true ||
+      Array.isArray(dlv(state.classNames.classNames, [className]))
       ? [className]
       : null
   }
@@ -73,7 +74,10 @@     }),
   ]
 
   return possibilities.find((key) => {
-    if (dlv(state.classNames.classNames, [...key, '__rule']) === true) {
+    if (
+      dlv(state.classNames.classNames, [...key, '__rule']) === true ||
+      Array.isArray(dlv(state.classNames.classNames, [...key]))
+    ) {
       return true
     }
     return false
M packages/tailwindcss-language-server/src/util/stringify.ts -> packages/tailwindcss-language-server/src/util/stringify.ts
diff --git a/packages/tailwindcss-language-server/src/util/stringify.ts b/packages/tailwindcss-language-server/src/util/stringify.ts
index e8b1b7d7bb5ba7eb3c4a25f4de5ed709e463c313..5433e90926433db92e6f14389dd4c1d796df38a3 100644
--- a/packages/tailwindcss-language-server/src/util/stringify.ts
+++ b/packages/tailwindcss-language-server/src/util/stringify.ts
@@ -1,4 +1,6 @@
 import removeMeta from './removeMeta'
+const dlv = require('dlv')
+import escapeClassName from 'css.escape'
 
 export function stringifyConfigValue(x: any): string {
   if (typeof x === 'string') return x
@@ -12,34 +14,45 @@   }
   return null
 }
 
-export function stringifyCss(
-  obj: any,
-  { indent = 0, selector }: { indent?: number; selector?: string } = {}
-): string {
-  let indentStr = '\t'.repeat(indent)
-  if (obj.__decls === true) {
-    let before = ''
-    let after = ''
-    if (selector) {
-      before = `${indentStr}${selector} {\n`
-      after = `\n${indentStr}}`
-      indentStr += '\t'
-    }
-    return (
-      before +
-      Object.keys(removeMeta(obj)).reduce((acc, curr, i) => {
-        return `${acc}${i === 0 ? '' : '\n'}${indentStr}${curr}: ${obj[curr]};`
-      }, '') +
-      after
-    )
+export function stringifyCss(className: string, obj: any): string {
+  if (obj.__rule !== true && !Array.isArray(obj)) return null
+
+  if (Array.isArray(obj)) {
+    const rules = obj.map((x) => stringifyCss(className, x)).filter(Boolean)
+    if (rules.length === 0) return null
+    return rules.join('\n\n')
+  }
+
+  let css = ``
+
+  const context = dlv(obj, '__context', [])
+  const props = Object.keys(removeMeta(obj))
+  if (props.length === 0) return null
+
+  for (let i = 0; i < context.length; i++) {
+    css += `${'\t'.repeat(i)}${context[i]} {\n`
   }
-  return Object.keys(removeMeta(obj)).reduce((acc, curr, i) => {
-    return `${acc}${i === 0 ? '' : '\n'}${indentStr}${curr} {\n${stringifyCss(
-      obj[curr],
-      {
-        indent: indent + 1,
-        selector,
-      }
-    )}\n${indentStr}}`
+
+  const indentStr = '\t'.repeat(context.length)
+  const decls = props.reduce((acc, curr, i) => {
+    return `${acc}${i === 0 ? '' : '\n'}${indentStr + '\t'}${curr}: ${
+      obj[curr]
+    };`
   }, '')
+  css += `${indentStr}${augmentClassName(
+    className,
+    obj
+  )} {\n${decls}\n${indentStr}}`
+
+  for (let i = context.length - 1; i >= 0; i--) {
+    css += `${'\t'.repeat(i)}\n}`
+  }
+
+  return css
+}
+
+function augmentClassName(className: string, obj: any): string {
+  const pseudo = obj.__pseudo ? obj.__pseudo.join('') : ''
+  const scope = obj.__scope ? `${obj.__scope} ` : ''
+  return `${scope}.${escapeClassName(className)}${pseudo}`
 }