Skip to content

Commit

Permalink
fix(preset-mini): parse bracket correctly in variable variant (#1207)
Browse files Browse the repository at this point in the history
  • Loading branch information
chu121su12 committed Jul 2, 2022
1 parent 473b1af commit b154b64
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 83 deletions.
70 changes: 8 additions & 62 deletions packages/preset-mini/src/utils/colors.ts
@@ -1,5 +1,6 @@
import type { CSSColorValue, RGBAColorValue } from '@unocss/core'
import { escapeRegExp } from '@unocss/core'
import { getComponents } from './utilities'

/* eslint-disable no-case-declarations */

Expand Down Expand Up @@ -34,7 +35,11 @@ export function parseCssColor(str = ''): CSSColorValue | undefined {
if (cssColorFunctions.includes(type) && ![1, 3].includes(components.length))
return

return { type, components, alpha }
return {
type,
components: components.map(c => typeof c === 'string' ? c.trim() : c),
alpha: typeof alpha === 'string' ? alpha.trim() : alpha,
}
}

export function colorOpacityToString(color: CSSColorValue) {
Expand Down Expand Up @@ -189,7 +194,7 @@ function parseCssColorFunction(color: string): CSSColorValue | undefined {
}

function parseCssSpaceColorValues(componentString: string) {
const components = getComponents(componentString)
const components = getComponents(componentString, ' ')
if (!components)
return

Expand All @@ -211,7 +216,7 @@ function parseCssSpaceColorValues(componentString: string) {
}

// maybe (fn 1 2 3/4)
const withAlpha = getComponents(components[totalComponents - 1], '/', 3)
const withAlpha = getComponents(components[totalComponents - 1], '/', 2)
if (!withAlpha)
return

Expand All @@ -226,62 +231,3 @@ function parseCssSpaceColorValues(componentString: string) {
alpha,
}
}

function getComponent(str: string, separator: string) {
str = str.trim()
if (str === '')
return

const l = str.length
let parenthesis = 0
for (let i = 0; i < l; i++) {
switch (str[i]) {
case '(':
parenthesis++
break

case ')':
if (--parenthesis < 0)
return
break

case separator:
if (parenthesis === 0) {
const component = str.slice(0, i).trim()
if (component === '')
return

return [
component,
str.slice(i + 1).trim(),
]
}
}
}

return [
str,
'',
]
}

export function getComponents(str: string, separator?: string, limit?: number) {
separator = separator ?? ' '
if (separator.length !== 1)
return
limit = limit ?? 10
const components = []
let i = 0
while (str !== '') {
if (++i > limit)
return
const componentPair = getComponent(str, separator)
if (!componentPair)
return
const [component, rest] = componentPair
components.push(component)
str = rest
}
if (components.length > 0)
return components
}
56 changes: 55 additions & 1 deletion packages/preset-mini/src/utils/utilities.ts
@@ -1,7 +1,7 @@
import type { CSSEntries, CSSObject, ParsedColorValue, Rule, RuleContext, VariantContext } from '@unocss/core'
import { toArray } from '@unocss/core'
import type { Theme } from '../theme'
import { colorOpacityToString, colorToString, getComponents, parseCssColor } from './colors'
import { colorOpacityToString, colorToString, parseCssColor } from './colors'
import { handler as h } from './handlers'
import { directionMap, globalKeywords } from './mappings'

Expand Down Expand Up @@ -205,3 +205,57 @@ export const makeGlobalStaticRules = (prefix: string, property?: string) => {
return globalKeywords.map(keyword => [`${prefix}-${keyword}`, { [property ?? prefix]: keyword }] as Rule)
}

export function getComponent(str: string, open: string, close: string, separator: string) {
if (str === '')
return

const l = str.length
let parenthesis = 0
for (let i = 0; i < l; i++) {
switch (str[i]) {
case open:
parenthesis++
break

case close:
if (--parenthesis < 0)
return
break

case separator:
if (parenthesis === 0) {
if (i === 0 || i === l - 1)
return
return [
str.slice(0, i),
str.slice(i + 1),
]
}
}
}

return [
str,
'',
]
}

export function getComponents(str: string, separator: string, limit?: number) {
if (separator.length !== 1)
return
limit = limit ?? 10
const components = []
let i = 0
while (str !== '') {
if (++i > limit)
return
const componentPair = getComponent(str, '(', ')', separator)
if (!componentPair)
return
const [component, rest] = componentPair
components.push(component)
str = rest
}
if (components.length > 0)
return components
}
47 changes: 27 additions & 20 deletions packages/preset-mini/src/variants/misc.ts
@@ -1,5 +1,5 @@
import type { Variant } from '@unocss/core'
import { handler as h } from '../utils'
import { getComponent, handler as h } from '../utils'

export const variantSelector: Variant = {
name: 'selector',
Expand Down Expand Up @@ -59,25 +59,32 @@ export const variantScope: Variant = {
export const variantVariables: Variant = {
name: 'variables',
match(matcher) {
const match = matcher.match(/^(\[.+?\]):/)
if (match) {
const variant = h.bracket(match[1]) ?? ''
return {
matcher: matcher.slice(match[0].length),
handle(input, next) {
const updates = variant.startsWith('@')
? {
parent: `${input.parent ? `${input.parent} $$ ` : ''}${variant}`,
}
: {
selector: variant.replace(/&/g, input.selector),
}
return next({
...input,
...updates,
})
},
}
if (!matcher.startsWith('['))
return

const [match, rest] = getComponent(matcher, '[', ']', ':') ?? []
if (!(match && rest && rest !== ''))
return

const variant = h.bracket(match) ?? ''
if (!(variant.startsWith('@') || variant.includes('&')))
return

return {
matcher: rest,
handle(input, next) {
const updates = variant.startsWith('@')
? {
parent: `${input.parent ? `${input.parent} $$ ` : ''}${variant}`,
}
: {
selector: variant.replace(/&/g, input.selector),
}
return next({
...input,
...updates,
})
},
}
},
multiPass: true,
Expand Down
2 changes: 2 additions & 0 deletions test/__snapshots__/preset-mini.test.ts.snap
Expand Up @@ -74,6 +74,7 @@ div:hover .group-\\\\[div\\\\:hover\\\\]-\\\\[combinator\\\\:test-4\\\\]{combina
.\\\\!m-\\\\$c-m{margin:var(--c-m) !important;}
.\\\\[\\\\&_\\\\&\\\\]\\\\:m-13 .\\\\[\\\\&_\\\\&\\\\]\\\\:m-13{margin:3.25rem;}
.\\\\[\\\\&\\\\:nth-child\\\\(2\\\\)\\\\]\\\\:m-10:nth-child(2){margin:2.5rem;}
.\\\\[\\\\&\\\\[open\\\\]\\\\:readonly\\\\]\\\\:m-16[open]:readonly{margin:4rem;}
.\\\\[\\\\&\\\\[open\\\\]\\\\]\\\\:m-14[open]{margin:3.5rem;}
.\\\\[\\\\&\\\\[readonly\\\\]\\\\[disabled\\\\]\\\\]\\\\:m-15[readonly][disabled]{margin:3.75rem;}
.\\\\[\\\\&\\\\>\\\\*\\\\]\\\\:m-11>*{margin:2.75rem;}
Expand All @@ -85,6 +86,7 @@ div:hover .group-\\\\[div\\\\:hover\\\\]-\\\\[combinator\\\\:test-4\\\\]{combina
.m-none{margin:0rem;}
.m-1\\\\/2{margin:50%;}
.m-inherit{margin:inherit;}
*[open]:readonly .\\\\[\\\\*\\\\[open\\\\]\\\\:readonly_\\\\&\\\\]\\\\:\\\\[\\\\&\\\\[open\\\\]\\\\:disabled\\\\]\\\\:m-17[open]:disabled{margin:4.25rem;}
*>.\\\\[\\\\*\\\\>\\\\&\\\\]\\\\:m-12{margin:3rem;}
.m-xy,
.mxy{margin:1rem;}
Expand Down
2 changes: 2 additions & 0 deletions test/assets/preset-mini-targets.ts
Expand Up @@ -953,6 +953,8 @@ export const presetMiniTargets: string[] = [
'[&_&]:m-13',
'[&[open]]:m-14',
'[&[readonly][disabled]]:m-15',
'[&[open]:readonly]:m-16',
'[*[open]:readonly_&]:[&[open]:disabled]:m-17',
'[@supports(display:grid)]:bg-red/33',
'[@supports(display:grid)]:[*+&]:bg-red/34',

Expand Down
1 change: 1 addition & 0 deletions test/color.test.ts
Expand Up @@ -32,6 +32,7 @@ describe('color utils', () => {
expect(parseCssColor('rgba(0 1 2 /3)')).eql({ type: 'rgba', components: ['0', '1', '2'], alpha: '3' })
expect(parseCssColor('rgba(0 1 2/3)')).eql({ type: 'rgba', components: ['0', '1', '2'], alpha: '3' })
expect(parseCssColor('rgba(0 1 2//3)')).eql(undefined)
expect(parseCssColor('rgba(0 1 2/ /3)')).eql(undefined)
expect(parseCssColor('rgb(0)')).eql({ type: 'rgb', components: ['0'], alpha: undefined })
expect(parseCssColor('rgba(0 / 1)')).eql({ type: 'rgba', components: ['0'], alpha: '1' })
expect(parseCssColor('rgba(0 1)')).eql(undefined)
Expand Down
18 changes: 18 additions & 0 deletions test/utils.test.ts
@@ -1,4 +1,5 @@
import { mergeDeep } from '@unocss/core'
import { getComponent } from '@unocss/preset-mini/utils'
import { expect, it } from 'vitest'

it('mergeDeep', () => {
Expand All @@ -21,3 +22,20 @@ it('mergeDeep', () => {
}
`)
})

it('getComponents', () => {
const fn1 = (s: string) => getComponent(s, '(', ')', ',')

expect(fn1('comma,separated')).eql(['comma', 'separated'])
expect(fn1('comma ,separated')).eql(['comma ', 'separated'])
expect(fn1('comma, separated')).eql(['comma', ' separated'])
expect(fn1('comma , separated ')).eql(['comma ', ' separated '])

expect(fn1('first,')).eql(undefined)
expect(fn1(',last')).eql(undefined)

expect(fn1('comma,separated,')).eql(['comma', 'separated,'])
expect(fn1('comma,separated,once')).eql(['comma', 'separated,once'])
expect(fn1('comma(),separated(value)')).eql(['comma()', 'separated(value)'])
expect(fn1('not(comma,separated)')).eql(['not(comma,separated)', ''])
})

0 comments on commit b154b64

Please sign in to comment.