refactor class name extraction and stringify
parent
9caa94fcb8
commit
3b50a445a3
|
@ -18,20 +18,6 @@ function getClassNamesFromSelector(selector) {
|
||||||
const { nodes: subSelectors } = selectorParser().astSync(selector)
|
const { nodes: subSelectors } = selectorParser().astSync(selector)
|
||||||
|
|
||||||
for (let i = 0; i < subSelectors.length; i++) {
|
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 = []
|
let scope = []
|
||||||
for (let j = 0; j < subSelectors[i].nodes.length; j++) {
|
for (let j = 0; j < subSelectors[i].nodes.length; j++) {
|
||||||
let node = subSelectors[i].nodes[j]
|
let node = subSelectors[i].nodes[j]
|
||||||
|
@ -47,39 +33,28 @@ function getClassNamesFromSelector(selector) {
|
||||||
}
|
}
|
||||||
|
|
||||||
classNames.push({
|
classNames.push({
|
||||||
className: String(node)
|
className: node.value.trim(),
|
||||||
.trim()
|
|
||||||
.substr(1),
|
|
||||||
scope: createSelectorFromNodes(scope),
|
scope: createSelectorFromNodes(scope),
|
||||||
__rule: j === subSelectors[i].nodes.length - 1,
|
__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)
|
scope.push(node, ...pseudo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(classNames)
|
|
||||||
|
|
||||||
return 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) {
|
async function process(ast) {
|
||||||
const start = new Date()
|
|
||||||
|
|
||||||
const tree = {}
|
const tree = {}
|
||||||
const commonContext = {}
|
const commonContext = {}
|
||||||
|
|
||||||
ast.root.walkRules(rule => {
|
ast.root.walkRules((rule) => {
|
||||||
const classNames = getClassNamesFromSelector(rule.selector)
|
const classNames = getClassNamesFromSelector(rule.selector)
|
||||||
|
|
||||||
const decls = { __decls: true }
|
const decls = {}
|
||||||
rule.walkDecls(decl => {
|
rule.walkDecls((decl) => {
|
||||||
decls[decl.prop] = decl.value
|
decls[decl.prop] = decl.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -96,49 +71,48 @@ async function process(ast) {
|
||||||
const context = keys.concat([])
|
const context = keys.concat([])
|
||||||
const baseKeys = classNames[i].className.split('__TAILWIND_SEPARATOR__')
|
const baseKeys = classNames[i].className.split('__TAILWIND_SEPARATOR__')
|
||||||
const contextKeys = baseKeys.slice(0, baseKeys.length - 1)
|
const contextKeys = baseKeys.slice(0, baseKeys.length - 1)
|
||||||
|
const index = []
|
||||||
|
|
||||||
if (classNames[i].scope) {
|
const existing = dlv(tree, baseKeys)
|
||||||
let index = []
|
if (typeof existing !== 'undefined') {
|
||||||
const existing = dlv(tree, baseKeys)
|
if (Array.isArray(existing)) {
|
||||||
if (typeof existing !== 'undefined') {
|
const scopeIndex = existing.findIndex(
|
||||||
if (Array.isArray(existing)) {
|
(x) =>
|
||||||
const scopeIndex = existing.findIndex(
|
x.__scope === classNames[i].scope &&
|
||||||
x => x.__scope === classNames[i].scope
|
arraysEqual(existing.__context, context)
|
||||||
)
|
)
|
||||||
if (scopeIndex > -1) {
|
if (scopeIndex > -1) {
|
||||||
keys.unshift(scopeIndex)
|
keys.unshift(scopeIndex)
|
||||||
index.push(scopeIndex)
|
index.push(scopeIndex)
|
||||||
} else {
|
|
||||||
keys.unshift(existing.length)
|
|
||||||
index.push(existing.length)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (existing.__scope !== classNames[i].scope) {
|
keys.unshift(existing.length)
|
||||||
dset(tree, baseKeys, [existing])
|
index.push(existing.length)
|
||||||
keys.unshift(1)
|
}
|
||||||
index.push(1)
|
} else {
|
||||||
}
|
if (
|
||||||
|
existing.__scope !== classNames[i].scope ||
|
||||||
|
!arraysEqual(existing.__context, context)
|
||||||
|
) {
|
||||||
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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
|
// common context
|
||||||
if (classNames[i].__pseudo) {
|
if (classNames[i].__pseudo) {
|
||||||
|
@ -157,15 +131,12 @@ async function process(ast) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// console.log(`${new Date() - start}ms`)
|
|
||||||
// console.log(tree)
|
|
||||||
// console.log(commonContext)
|
|
||||||
|
|
||||||
return { classNames: tree, context: commonContext }
|
return { classNames: tree, context: commonContext }
|
||||||
}
|
}
|
||||||
|
|
||||||
function intersection(arr1, arr2) {
|
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) {
|
function dsetEach(obj, keys, values) {
|
||||||
|
@ -175,14 +146,15 @@ function dsetEach(obj, keys, values) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
// process(`
|
for (let i = 0; i < a.length; ++i) {
|
||||||
// .bg-red {
|
if (a[i] !== b[i]) return false
|
||||||
// background-color: red;
|
}
|
||||||
// }
|
return true
|
||||||
// .bg-red {
|
}
|
||||||
// color: white;
|
|
||||||
// }`).then(x => {
|
export default process
|
||||||
// console.log(x)
|
|
||||||
// })
|
|
||||||
|
|
|
@ -3,9 +3,73 @@ const esmImport = require('esm')(module)
|
||||||
const process = esmImport('../src/extractClassNames.mjs').default
|
const process = esmImport('../src/extractClassNames.mjs').default
|
||||||
postcss = postcss([postcss.plugin('no-op', () => () => {})])
|
postcss = postcss([postcss.plugin('no-op', () => () => {})])
|
||||||
|
|
||||||
const processCss = async css =>
|
const processCss = async (css) =>
|
||||||
process(await postcss.process(css, { from: undefined }))
|
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 () => {
|
test('foo', async () => {
|
||||||
const result = await processCss(`
|
const result = await processCss(`
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
|
@ -24,43 +88,42 @@ test('foo', async () => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
context: {
|
context: {
|
||||||
sm: ['@media (min-width: 640px)'],
|
sm: ['@media (min-width: 640px)'],
|
||||||
hover: [':hover']
|
hover: [':hover'],
|
||||||
},
|
},
|
||||||
classNames: {
|
classNames: {
|
||||||
sm: {
|
sm: {
|
||||||
'bg-red': {
|
'bg-red': {
|
||||||
__rule: true,
|
__rule: true,
|
||||||
'@media (min-width: 640px)': {
|
__scope: null,
|
||||||
__decls: true,
|
__context: ['@media (min-width: 640px)'],
|
||||||
'background-color': 'red'
|
'background-color': 'red',
|
||||||
}
|
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
'bg-red': {
|
'bg-red': {
|
||||||
__rule: true,
|
__rule: true,
|
||||||
'@media (min-width: 640px)': {
|
__scope: null,
|
||||||
__decls: true,
|
__context: ['@media (min-width: 640px)'],
|
||||||
__pseudo: [':hover'],
|
__pseudo: [':hover'],
|
||||||
'background-color': 'red'
|
'background-color': 'red',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
'bg-red': {
|
'bg-red': {
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
__scope: null,
|
||||||
__pseudo: [':hover'],
|
__pseudo: [':hover'],
|
||||||
'background-color': 'red'
|
__context: [],
|
||||||
}
|
'background-color': 'red',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('processes basic css', async () => {
|
test.only('processes basic css', async () => {
|
||||||
const result = await processCss(`
|
const result = await processCss(`
|
||||||
.bg-red {
|
.bg-red\\:foo {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
@ -70,10 +133,11 @@ test('processes basic css', async () => {
|
||||||
classNames: {
|
classNames: {
|
||||||
'bg-red': {
|
'bg-red': {
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
__scope: null,
|
||||||
'background-color': 'red'
|
__context: [],
|
||||||
}
|
'background-color': 'red',
|
||||||
}
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -89,11 +153,12 @@ test('processes pseudo selectors', async () => {
|
||||||
classNames: {
|
classNames: {
|
||||||
'bg-red': {
|
'bg-red': {
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
__scope: null,
|
||||||
|
__context: [],
|
||||||
__pseudo: [':first-child', '::after'],
|
__pseudo: [':first-child', '::after'],
|
||||||
'background-color': 'red'
|
'background-color': 'red',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -108,15 +173,17 @@ test('processes pseudo selectors in scope', async () => {
|
||||||
context: {},
|
context: {},
|
||||||
classNames: {
|
classNames: {
|
||||||
scope: {
|
scope: {
|
||||||
__pseudo: [':hover']
|
__context: [],
|
||||||
|
__pseudo: [':hover'],
|
||||||
|
__scope: null,
|
||||||
},
|
},
|
||||||
'bg-red': {
|
'bg-red': {
|
||||||
|
__context: [],
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
|
||||||
__scope: '.scope:hover',
|
__scope: '.scope:hover',
|
||||||
'background-color': 'red'
|
'background-color': 'red',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -133,15 +200,17 @@ test('processes multiple class names in the same rule', async () => {
|
||||||
classNames: {
|
classNames: {
|
||||||
'bg-red': {
|
'bg-red': {
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
__scope: null,
|
||||||
'background-color': 'red'
|
__context: [],
|
||||||
|
'background-color': 'red',
|
||||||
},
|
},
|
||||||
'bg-red-again': {
|
'bg-red-again': {
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
__scope: null,
|
||||||
'background-color': 'red'
|
__context: [],
|
||||||
}
|
'background-color': 'red',
|
||||||
}
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -159,12 +228,35 @@ test('processes media queries', async () => {
|
||||||
classNames: {
|
classNames: {
|
||||||
'bg-red': {
|
'bg-red': {
|
||||||
__rule: true,
|
__rule: true,
|
||||||
'@media (min-width: 768px)': {
|
__scope: null,
|
||||||
__decls: true,
|
__context: ['@media (min-width: 768px)'],
|
||||||
'background-color': 'red'
|
'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 @@ test('merges declarations', async () => {
|
||||||
classNames: {
|
classNames: {
|
||||||
'bg-red': {
|
'bg-red': {
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
__scope: null,
|
||||||
|
__context: [],
|
||||||
'background-color': 'red',
|
'background-color': 'red',
|
||||||
color: 'white'
|
color: 'white',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -201,14 +294,17 @@ test('processes class name scope', async () => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
context: {},
|
context: {},
|
||||||
classNames: {
|
classNames: {
|
||||||
scope: {},
|
scope: {
|
||||||
|
__context: [],
|
||||||
|
__scope: null,
|
||||||
|
},
|
||||||
'bg-red': {
|
'bg-red': {
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
__context: [],
|
||||||
__scope: '.scope',
|
__scope: '.scope',
|
||||||
'background-color': 'red'
|
'background-color': 'red',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -228,29 +324,29 @@ test('processes multiple scopes for the same class name', async () => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
context: {},
|
context: {},
|
||||||
classNames: {
|
classNames: {
|
||||||
scope1: {},
|
scope1: { __context: [], __scope: null },
|
||||||
scope2: {},
|
scope2: { __context: [], __scope: null },
|
||||||
scope3: {},
|
scope3: { __context: [], __scope: null },
|
||||||
'bg-red': [
|
'bg-red': [
|
||||||
{
|
{
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
__context: [],
|
||||||
__scope: '.scope1',
|
__scope: '.scope1',
|
||||||
'background-color': 'red'
|
'background-color': 'red',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
__context: [],
|
||||||
__scope: '.scope2 +',
|
__scope: '.scope2 +',
|
||||||
'background-color': 'red'
|
'background-color': 'red',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__rule: true,
|
__rule: true,
|
||||||
__decls: true,
|
__context: [],
|
||||||
__scope: '.scope3 >',
|
__scope: '.scope3 >',
|
||||||
'background-color': 'red'
|
'background-color': 'red',
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -27,6 +27,7 @@ function completionsFromClassList(
|
||||||
let sep = ':'
|
let sep = ':'
|
||||||
let parts = partialClassName.split(sep)
|
let parts = partialClassName.split(sep)
|
||||||
let subset: any
|
let subset: any
|
||||||
|
let subsetKey: string[] = []
|
||||||
let isSubset: boolean = false
|
let isSubset: boolean = false
|
||||||
|
|
||||||
let replacementRange = {
|
let replacementRange = {
|
||||||
|
@ -42,6 +43,7 @@ function completionsFromClassList(
|
||||||
subset = dlv(state.classNames.classNames, keys)
|
subset = dlv(state.classNames.classNames, keys)
|
||||||
if (typeof subset !== 'undefined' && typeof subset.__rule === 'undefined') {
|
if (typeof subset !== 'undefined' && typeof subset.__rule === 'undefined') {
|
||||||
isSubset = true
|
isSubset = true
|
||||||
|
subsetKey = keys
|
||||||
replacementRange = {
|
replacementRange = {
|
||||||
...replacementRange,
|
...replacementRange,
|
||||||
start: {
|
start: {
|
||||||
|
@ -62,7 +64,7 @@ function completionsFromClassList(
|
||||||
(className) => {
|
(className) => {
|
||||||
let kind: CompletionItemKind = CompletionItemKind.Constant
|
let kind: CompletionItemKind = CompletionItemKind.Constant
|
||||||
let documentation: string = null
|
let documentation: string = null
|
||||||
if (isContextItem(state, [className])) {
|
if (isContextItem(state, [...subsetKey, className])) {
|
||||||
kind = CompletionItemKind.Module
|
kind = CompletionItemKind.Module
|
||||||
} else {
|
} else {
|
||||||
const color = getColor(state, [className])
|
const color = getColor(state, [className])
|
||||||
|
@ -76,6 +78,7 @@ function completionsFromClassList(
|
||||||
label: className,
|
label: className,
|
||||||
kind,
|
kind,
|
||||||
documentation,
|
documentation,
|
||||||
|
data: [...subsetKey, className],
|
||||||
textEdit: {
|
textEdit: {
|
||||||
newText: className,
|
newText: className,
|
||||||
range: replacementRange,
|
range: replacementRange,
|
||||||
|
@ -514,20 +517,20 @@ export function resolveCompletionItem(
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
const className = state.classNames.classNames[item.label]
|
const className = dlv(state.classNames.classNames, item.data)
|
||||||
if (isContextItem(state, [item.label])) {
|
if (isContextItem(state, item.data)) {
|
||||||
item.detail = state.classNames.context[item.label].join(', ')
|
item.detail = state.classNames.context[
|
||||||
|
item.data[item.data.length - 1]
|
||||||
|
].join(', ')
|
||||||
} else {
|
} else {
|
||||||
item.detail = getCssDetail(state, className)
|
item.detail = getCssDetail(state, className)
|
||||||
if (!item.documentation) {
|
if (!item.documentation) {
|
||||||
item.documentation = stringifyCss(className)
|
const css = stringifyCss(item.data.join(':'), className)
|
||||||
if (item.detail === item.documentation) {
|
if (css) {
|
||||||
item.documentation = null
|
item.documentation = {
|
||||||
} else {
|
kind: MarkupKind.Markdown,
|
||||||
// item.documentation = {
|
value: ['```css', css, '```'].join('\n'),
|
||||||
// kind: MarkupKind.Markdown,
|
}
|
||||||
// value: ['```css', item.documentation, '```'].join('\n')
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -537,7 +540,8 @@ export function resolveCompletionItem(
|
||||||
function isContextItem(state: State, keys: string[]): boolean {
|
function isContextItem(state: State, keys: string[]): boolean {
|
||||||
const item = dlv(state.classNames.classNames, keys)
|
const item = dlv(state.classNames.classNames, keys)
|
||||||
return Boolean(
|
return Boolean(
|
||||||
!item.__rule &&
|
isObject(item) &&
|
||||||
|
!item.__rule &&
|
||||||
!Array.isArray(item) &&
|
!Array.isArray(item) &&
|
||||||
state.classNames.context[keys[keys.length - 1]]
|
state.classNames.context[keys[keys.length - 1]]
|
||||||
)
|
)
|
||||||
|
@ -555,13 +559,8 @@ function getCssDetail(state: State, className: any): string {
|
||||||
if (Array.isArray(className)) {
|
if (Array.isArray(className)) {
|
||||||
return `${className.length} rules`
|
return `${className.length} rules`
|
||||||
}
|
}
|
||||||
let withoutMeta = removeMeta(className)
|
if (className.__rule === true) {
|
||||||
if (className.__decls === true) {
|
return stringifyDecls(removeMeta(className))
|
||||||
return stringifyDecls(withoutMeta)
|
|
||||||
}
|
}
|
||||||
let keys = Object.keys(withoutMeta)
|
return null
|
||||||
if (keys.length === 1) {
|
|
||||||
return getCssDetail(state, className[keys[0]])
|
|
||||||
}
|
|
||||||
return `${keys.length} rules`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import {
|
||||||
} from '../util/getClassNameAtPosition'
|
} from '../util/getClassNameAtPosition'
|
||||||
import { stringifyCss, stringifyConfigValue } from '../util/stringify'
|
import { stringifyCss, stringifyConfigValue } from '../util/stringify'
|
||||||
const dlv = require('dlv')
|
const dlv = require('dlv')
|
||||||
import escapeClassName from 'css.escape'
|
|
||||||
import { isHtmlContext } from '../util/html'
|
import { isHtmlContext } from '../util/html'
|
||||||
import { isCssContext } from '../util/css'
|
import { isCssContext } from '../util/css'
|
||||||
|
|
||||||
|
@ -90,21 +89,11 @@ function provideClassNameHover(
|
||||||
return {
|
return {
|
||||||
contents: {
|
contents: {
|
||||||
language: 'css',
|
language: 'css',
|
||||||
value: stringifyCss(dlv(state.classNames.classNames, parts), {
|
value: stringifyCss(
|
||||||
selector: augmentClassName(parts, state),
|
hovered.className,
|
||||||
}),
|
dlv(state.classNames.classNames, parts)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
range: hovered.range,
|
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}`
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ const COLOR_PROPS = [
|
||||||
'outline-color',
|
'outline-color',
|
||||||
'stop-color',
|
'stop-color',
|
||||||
'stroke',
|
'stroke',
|
||||||
'text-decoration-color'
|
'text-decoration-color',
|
||||||
]
|
]
|
||||||
|
|
||||||
const COLOR_NAMES = {
|
const COLOR_NAMES = {
|
||||||
|
@ -169,12 +169,12 @@ const COLOR_NAMES = {
|
||||||
white: '#fff',
|
white: '#fff',
|
||||||
whitesmoke: '#f5f5f5',
|
whitesmoke: '#f5f5f5',
|
||||||
yellow: '#ff0',
|
yellow: '#ff0',
|
||||||
yellowgreen: '#9acd32'
|
yellowgreen: '#9acd32',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getColor(state: State, keys: string[]): string {
|
export function getColor(state: State, keys: string[]): string {
|
||||||
const item = dlv(state.classNames.classNames, keys)
|
const item = dlv(state.classNames.classNames, keys)
|
||||||
if (!item.__decls) return null
|
if (!item.__rule) return null
|
||||||
const props = Object.keys(removeMeta(item))
|
const props = Object.keys(removeMeta(item))
|
||||||
if (props.length === 0 || props.length > 1) return null
|
if (props.length === 0 || props.length > 1) return null
|
||||||
const prop = props[0]
|
const prop = props[0]
|
||||||
|
|
|
@ -49,7 +49,8 @@ export function getClassNameParts(state: State, className: string): string[] {
|
||||||
let parts: string[] = className.split(separator)
|
let parts: string[] = className.split(separator)
|
||||||
|
|
||||||
if (parts.length === 1) {
|
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]
|
? [className]
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
@ -73,7 +74,10 @@ export function getClassNameParts(state: State, className: string): string[] {
|
||||||
]
|
]
|
||||||
|
|
||||||
return possibilities.find((key) => {
|
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 true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import removeMeta from './removeMeta'
|
import removeMeta from './removeMeta'
|
||||||
|
const dlv = require('dlv')
|
||||||
|
import escapeClassName from 'css.escape'
|
||||||
|
|
||||||
export function stringifyConfigValue(x: any): string {
|
export function stringifyConfigValue(x: any): string {
|
||||||
if (typeof x === 'string') return x
|
if (typeof x === 'string') return x
|
||||||
|
@ -12,34 +14,45 @@ export function stringifyConfigValue(x: any): string {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringifyCss(
|
export function stringifyCss(className: string, obj: any): string {
|
||||||
obj: any,
|
if (obj.__rule !== true && !Array.isArray(obj)) return null
|
||||||
{ indent = 0, selector }: { indent?: number; selector?: string } = {}
|
|
||||||
): string {
|
if (Array.isArray(obj)) {
|
||||||
let indentStr = '\t'.repeat(indent)
|
const rules = obj.map((x) => stringifyCss(className, x)).filter(Boolean)
|
||||||
if (obj.__decls === true) {
|
if (rules.length === 0) return null
|
||||||
let before = ''
|
return rules.join('\n\n')
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return Object.keys(removeMeta(obj)).reduce((acc, curr, i) => {
|
|
||||||
return `${acc}${i === 0 ? '' : '\n'}${indentStr}${curr} {\n${stringifyCss(
|
let css = ``
|
||||||
obj[curr],
|
|
||||||
{
|
const context = dlv(obj, '__context', [])
|
||||||
indent: indent + 1,
|
const props = Object.keys(removeMeta(obj))
|
||||||
selector,
|
if (props.length === 0) return null
|
||||||
}
|
|
||||||
)}\n${indentStr}}`
|
for (let i = 0; i < context.length; i++) {
|
||||||
|
css += `${'\t'.repeat(i)}${context[i]} {\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue