Skip to content

Commit

Permalink
feat(variantGroup): nested variantGroup
Browse files Browse the repository at this point in the history
  • Loading branch information
zojize committed Apr 16, 2022
1 parent 8df6f62 commit db97240
Show file tree
Hide file tree
Showing 10 changed files with 361 additions and 11 deletions.
124 changes: 124 additions & 0 deletions packages/core/src/utils/extractQuoted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
export type Range = [start: number, end: number]

export interface ExtractStringOptions<Details extends boolean = false> {
deep?: boolean
templateStaticOnly?: boolean
details?: Details
range?: Range
}

export interface DetailString {
value: string
range: Range
quote: '\'' | '"' | '`'
}

export function matchingPair(
text: string,
[left, right]: Iterable<string>,
i: number,
allowEscape?: boolean,
) {
const len = text.length
let stack = +(text[i] === left)
while (++i < len) {
const char = text[i]
if (char === left) {
stack++
}
else if (char === right) {
stack--
if (stack === 0)
return i
}
else if (allowEscape && char === '\\') {
i++
}
}
return -1
}

const QUOTES = ['\'', '"', '`'] as const

export function extractQuoted(str: string, options?: ExtractStringOptions<false>): string[]
export function extractQuoted(str: string, options: ExtractStringOptions<true>): DetailString[]
export function extractQuoted(
str: string,
options: ExtractStringOptions<boolean> = {},
): any[] {
const {
deep = false,
templateStaticOnly = false,
details = false,
range: [rstart, rend] = [0, str.length],
} = options

const result: (string | DetailString)[] = []
let quote: DetailString['quote']
const addResult = (start: number, end: number) => result.push(
details
? {
value: str.slice(start, end),
range: [start, end],
quote,
}
: str.slice(start, end),
)

let i = rstart
while (i < rend) {
const char = str[i]
if ((QUOTES.includes as (c: string) => c is typeof quote)(char)) {
quote = char
const start = i + 1
const isTemplate = quote === '`'
let templateStart = start
let end = start
while (end < rend) {
const nextChar = str[end]
if (nextChar === quote) {
if (isTemplate && templateStaticOnly) {
addResult(templateStart, end)
break
}
addResult(start, end)
break
}
if (nextChar === '\\') {
end += 2
}
else if (isTemplate && nextChar === '$' && str[end + 1] === '{') {
const nestStart = end + 2
end = matchingPair(str, '{}', end + 1, true)
if (templateStaticOnly) {
addResult(
templateStart,
nestStart - 2,
)
}
templateStart = end + 1
if (deep) {
result.push(
...extractQuoted(
str,
{
...options,
range: [nestStart, end],
} as ExtractStringOptions<any>,
),
)
}
}
else {
end += 1
}
}
i = end + 1
}
else {
i++
}
}

return result
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './layer'
export * from './variantGroup'
export * from './warn'
export * from './handlers'
export * from './extractQuoted'
25 changes: 16 additions & 9 deletions packages/core/src/utils/variantGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ export function expandVariantGroup(str: string): string
export function expandVariantGroup(str: MagicString): MagicString
export function expandVariantGroup(str: string | MagicString) {
regexClassGroup.lastIndex = 0
return str.replace(
regexClassGroup,
(_, pre, sep, body: string) => {
return body
.split(/\s/g)
.map(i => i === '~' ? pre : i.replace(/^(!?)(.*)/, `$1${pre}${sep}$2`))
.join(' ')
},
)
let hasChanged = false
do {
const before = str.toString()
str = str.replace(
regexClassGroup,
(_, pre, sep, body: string) => {
return body
.split(/\s/g)
.map(i => i === '~' ? pre : i.replace(/^(!?)(.*)/, `$1${pre}${sep}$2`))
.join(' ')
},
)
hasChanged = str.toString() !== before
} while (hasChanged)

return str
}
13 changes: 11 additions & 2 deletions packages/transformer-variant-group/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import type { SourceCodeTransformer } from '@unocss/core'
import { expandVariantGroup } from '@unocss/core'
import { expandVariantGroup, extractQuoted } from '@unocss/core'

export default function transformerVariantGroup(): SourceCodeTransformer {
return {
name: 'variant-group',
enforce: 'pre',
transform(s) {
expandVariantGroup(s)
extractQuoted(
s.toString(),
{
details: true,
templateStaticOnly: true,
deep: true,
},
)
.filter(({ value: { length } }) => length)
.forEach(({ value, range }) => s.overwrite(...range, expandVariantGroup(value)))
},
}
}
86 changes: 86 additions & 0 deletions test/__snapshots__/extract-quoted.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Vitest Snapshot v1

exports[`extractString > should extract static template only 1`] = `
[
{
"quote": "'",
"range": [
66,
69,
],
"value": "foo",
},
{
"quote": "\\"",
"range": [
75,
78,
],
"value": "bar",
},
{
"quote": "\`",
"range": [
84,
87,
],
"value": "baz",
},
{
"quote": "'",
"range": [
93,
106,
],
"value": "foo\\\\'bar\\\\\\"baz",
},
{
"quote": "\`",
"range": [
112,
115,
],
"value": "foo",
},
{
"quote": "\`",
"range": [
137,
140,
],
"value": "foo",
},
{
"quote": "\`",
"range": [
148,
151,
],
"value": "baz",
},
{
"quote": "\`",
"range": [
160,
168,
],
"value": "ham eggs",
},
{
"quote": "\`",
"range": [
174,
177,
],
"value": "foo",
},
{
"quote": "\`",
"range": [
213,
216,
],
"value": "bar",
},
]
`;
13 changes: 13 additions & 0 deletions test/assets/extract-quoted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-disable quotes */
export default [
'',
"",
``,
'foo',
"bar",
`baz`,
'foo\'bar\"baz',
`foo${'bar'}${'baz'}`,
`foo${'bar'}baz${`spam`}ham eggs`,
`foo${`spam${"ham"}${'eggs'}`}${`bacon`}bar`,
]
14 changes: 14 additions & 0 deletions test/assets/variant-group.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
const a = 1
const b = 2
// eslint-disable-next-line space-infix-ops
const c = a-(b -a -b)
</script>

<template>
<div class="bg-white font-light sm:hover:(bg-gray-100 font-medium)" />
<div class="lt-sm:hover:(p-1 p-2)" />
<div class="sm:(p-1 p-2)" />
<div class="dark:lg:(p-1 p-2)" />
<div class="hover:(bg-red text-green dark:(bg-cyan text-pink))" />
</template>
62 changes: 62 additions & 0 deletions test/extract-quoted.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { readFile } from 'fs/promises'
import { extractQuoted } from '@unocss/core'
import { describe, expect, test } from 'vitest'

describe('extractString', async() => {
const code = await readFile('./test/assets/extract-quoted.ts', 'utf-8')

test('works', () => {
const strings = extractQuoted(code)
expect(strings).toMatchInlineSnapshot(`
[
"",
"",
"",
"foo",
"bar",
"baz",
"foo\\\\'bar\\\\\\"baz",
"foo\${'bar'}\${'baz'}",
"foo\${'bar'}baz\${\`spam\`}ham eggs",
"foo\${\`spam\${\\"ham\\"}\${'eggs'}\`}\${\`bacon\`}bar",
]
`)
}, 1000)

test('should extract deep template string', () => {
const strings = extractQuoted(code, { deep: true })
expect(strings).toMatchInlineSnapshot(`
[
"",
"",
"",
"foo",
"bar",
"baz",
"foo\\\\'bar\\\\\\"baz",
"bar",
"baz",
"foo\${'bar'}\${'baz'}",
"bar",
"spam",
"foo\${'bar'}baz\${\`spam\`}ham eggs",
"ham",
"eggs",
"spam\${\\"ham\\"}\${'eggs'}",
"bacon",
"foo\${\`spam\${\\"ham\\"}\${'eggs'}\`}\${\`bacon\`}bar",
]
`)
}, 1000)

test('should extract static template only', () => {
const strings = extractQuoted(
code,
{
templateStaticOnly: true,
details: true,
})
.filter(({ value: { length } }) => length)
expect(strings).toMatchSnapshot()
}, 1000)
})

0 comments on commit db97240

Please sign in to comment.