tailwind-ctp-intellisense/packages/tailwindcss-class-names/src/extractClassNames.mjs

189 lines
5.1 KiB
JavaScript

import selectorParser from 'postcss-selector-parser'
import fs from 'fs'
import path from 'path'
import dset from 'dset'
import dlv from 'dlv'
function createSelectorFromNodes(nodes) {
if (nodes.length === 0) return null
const selector = selectorParser.selector()
for (let i = 0; i < nodes.length; i++) {
selector.append(nodes[i])
}
return String(selector).trim()
}
function getClassNamesFromSelector(selector) {
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]
let pseudo = []
if (node.type === 'class') {
let next = subSelectors[i].nodes[j + 1]
while (next && next.type === 'pseudo') {
pseudo.push(next)
j++
next = subSelectors[i].nodes[j + 1]
}
classNames.push({
className: String(node)
.trim()
.substr(1),
scope: createSelectorFromNodes(scope),
__rule: j === subSelectors[i].nodes.length - 1,
// __pseudo: createSelectorFromNodes(pseudo)
__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 => {
const classNames = getClassNamesFromSelector(rule.selector)
const decls = { __decls: true }
rule.walkDecls(decl => {
decls[decl.prop] = decl.value
})
let p = rule
const keys = []
while (p.parent.type !== 'root') {
p = p.parent
if (p.type === 'atrule') {
keys.push(`@${p.name} ${p.params}`)
}
}
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)
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)
}
} else {
if (existing.__scope !== classNames[i].scope) {
dset(tree, baseKeys, [existing])
keys.unshift(1)
index.push(1)
}
}
}
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 (classNames[i].__pseudo) {
dset(tree, [...baseKeys, ...keys, '__pseudo'], classNames[i].__pseudo)
}
}
// common context
if (classNames[i].__pseudo) {
context.push(...classNames[i].__pseudo)
}
for (let i = 0; i < contextKeys.length; i++) {
if (typeof commonContext[contextKeys[i]] === 'undefined') {
commonContext[contextKeys[i]] = context
} else {
commonContext[contextKeys[i]] = intersection(
commonContext[contextKeys[i]],
context
)
}
}
}
})
// 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)
}
function dsetEach(obj, keys, values) {
const k = Object.keys(values)
for (let i = 0; i < k.length; i++) {
dset(obj, [...keys, k[i]], values[k[i]])
}
}
export default process
// process(`
// .bg-red {
// background-color: red;
// }
// .bg-red {
// color: white;
// }`).then(x => {
// console.log(x)
// })