Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(variant-group): transformer variant group changes regular express… #3211

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion packages/autocomplete/src/create.ts
Expand Up @@ -62,7 +62,7 @@ export function createAutocomplete(uno: UnoGenerator, options: AutocompleteOptio
}

async function suggest(input: string, allowsEmptyInput = false) {
if (!allowsEmptyInput && input.length < 2)
if (!allowsEmptyInput && input.length < 1)
return []
if (cache.has(input))
return cache.get(input)!
Expand Down
67 changes: 37 additions & 30 deletions packages/autocomplete/src/parse.ts
@@ -1,6 +1,6 @@
import { uniq } from '@unocss/core'
import { Fzf } from 'fzf'
import type { AutoCompleteMatchType, AutocompleteTemplatePart, ParsedAutocompleteTemplate } from './types'
import type { AutoCompleteMatchType, AutocompleteTemplateGroup, AutocompleteTemplatePart, AutocompleteTemplateStatic, ParsedAutocompleteTemplate } from './types'
import { cartesian } from './utils'

export const shorthands: Record<string, string> = {
Expand Down Expand Up @@ -72,10 +72,12 @@ export function parseAutocomplete(template: string, theme: any = {}, extraShorth
})
},
(str) => {
parts.push({
type: 'static',
value: str,
})
if (str !== '-') {
parts.push({
type: 'static',
value: str,
})
}
},
)
}
Expand All @@ -99,62 +101,67 @@ export function parseAutocomplete(template: string, theme: any = {}, extraShorth
function suggest(input: string, matchType: AutoCompleteMatchType = 'prefix') {
if (input.length > 1 && matchType === 'fuzzy')
return fzf.find(input).map(i => i.item)
let rest = input
let matched = ''
let rest = input.replace(/-/g, '')
let matched: string[] = ['']
let combinations: string[] = []
const tempParts = [...parts]

while (tempParts.length) {
const part = tempParts.shift()!
if (part.type === 'static') {
if (combinations.length)
combinations = combinations.map(i => i + part.value)
if (part.value.startsWith(rest) && part.value !== rest && !combinations.length) {
combinations = [part.value]
break
}
else if (!rest.startsWith(part.value)) {
break
}
matched += part.value
rest = rest.slice(part.value.length)
matched = matched.map(m => m += part.value)
rest = rest.slice(part.value.endsWith('-') ? part.value.length - 1 : part.value.length)
}
else if (part.type === 'group') {
const fullMatched = part.values.find(i => i && rest.startsWith(i))
if (fullMatched != null) {
matched += fullMatched
if (fullMatched != null && (tempParts[0] && tempParts[0].type !== 'static')) {
matched = matched.map(m => m += `${fullMatched}-`)
rest = rest.slice(fullMatched.length)
}
else {
combinations = part.values.filter(i => i.startsWith(rest))
if (tempParts[0]?.type !== 'static')
if (tempParts[0] && tempParts[0].type !== 'static') {
const values = (tempParts[0] as AutocompleteTemplateGroup).values
if (values)
matched = matched.map(m => values.map(n => m + n + tempParts[1] ? '-' : '')).flat()
break
}
else if (tempParts[0]) {
return part.values.map(p => (tempParts[0] as AutocompleteTemplateStatic).value.slice(1).split('|').map(t => `${p}-${t}`)).flat()
}
combinations = part.values.filter(p => p.startsWith(rest))
break
}
}
else if (part.type === 'theme') {
const keys = part.objects.flatMap(i => Object.keys(i))
.filter(i => i && !ignoredThemeKeys.includes(i) && i[0] !== '_')
const fullMatched = keys.find(i => i && rest.startsWith(i))
if (fullMatched != null) {
matched += fullMatched
rest = rest.slice(fullMatched.length)
const subObjects = part.objects.map(i => i[fullMatched])
.filter((i): i is Record<string, unknown> => !!i && typeof i === 'object')

if (subObjects.length) {
matched = matched.map(m => m += `${fullMatched}-`)
tempParts.unshift({
type: 'static',
value: '-',
}, {
type: 'theme',
objects: subObjects,
})
}
else {
combinations = keys.filter(i => i.startsWith(rest))
}
}
else {
combinations = keys.filter(i => i.startsWith(rest))
if (tempParts[0]?.type !== 'static')
break
if (tempParts[0] && tempParts[0].type !== 'static') {
const values = (tempParts[0] as AutocompleteTemplateGroup).values
if (values)
matched = matched.map(m => values.map(n => m + n + tempParts[1] ? '-' : '')).flat()
}
else {
combinations = keys.filter(i => i.startsWith(rest))
}
break
}
}
}
Expand Down Expand Up @@ -188,7 +195,7 @@ export function parseAutocomplete(template: string, theme: any = {}, extraShorth
// }
// }

return combinations.map(i => matched + i)
return combinations.map(i => matched.map(m => m + i)).flat()
.filter(i => i.length >= input.length)
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/generator/index.ts
Expand Up @@ -626,7 +626,7 @@ export class UnoGenerator<Theme extends object = object> {

// expand nested shortcuts
if (isString(result))
result = expandVariantGroup(result.trim()).split(/\s+/g)
result = expandVariantGroup(result.trim(), undefined, undefined, true).split(/\s+/g)

// expand nested shortcuts with variants
if (!result) {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/types.ts
Expand Up @@ -654,7 +654,8 @@ export interface SourceCodeTransformer {
transform: (
code: MagicString,
id: string,
ctx: UnocssPluginContext
ctx: UnocssPluginContext,
onlyAttribute?: boolean
) => Awaitable<{ highlightAnnotations?: HighlightAnnotation[] } | void>
}

Expand Down
22 changes: 16 additions & 6 deletions packages/core/src/utils/variant-group.ts
Expand Up @@ -17,18 +17,28 @@ interface VariantGroup {
items: HighlightAnnotation[]
}

export function parseVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5) {
const regexAttribute = /(<[\w\-_]+)\s+(([^<>"']|"[^"]*"|'[^']*'|<)*)\/?>/gm
export function parseVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5, onlyAttribute?: boolean) {
const regexClassGroup = makeRegexClassGroup(separators)
let hasChanged
let content = str.toString()
const prefixes = new Set<string>()
const groupsByOffset = new Map<number, VariantGroup>()

const isAttributor = (offset: number) => {
for (const match of content.matchAll(regexAttribute)) {
const index = match.index!
if (index + match[1].length < offset && (index + match[0].length > offset))
return true
}
return false
}
do {
hasChanged = false
content = content.replace(
regexClassGroup,
(from, pre: string, sep: string, body: string, groupOffset: number) => {
if (!onlyAttribute && !isAttributor(groupOffset))
return from
if (!separators.includes(sep))
return from

Expand Down Expand Up @@ -135,10 +145,10 @@ export function collapseVariantGroup(str: string, prefixes: string[]): string {
.join(' ')
}

export function expandVariantGroup(str: string, separators?: string[], depth?: number): string
export function expandVariantGroup(str: MagicString, separators?: string[], depth?: number): MagicString
export function expandVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5) {
const res = parseVariantGroup(str, separators, depth)
export function expandVariantGroup(str: string, separators?: string[], depth?: number, onlyAttribute?: boolean): string
export function expandVariantGroup(str: MagicString, separators?: string[], depth?: number, onlyAttribute?: boolean): MagicString
export function expandVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5, onlyAttribute?: boolean) {
const res = parseVariantGroup(str, separators, depth, onlyAttribute)
return typeof str === 'string'
? res.expanded
: str
Expand Down
3 changes: 2 additions & 1 deletion packages/shared-common/src/index.ts
Expand Up @@ -238,9 +238,10 @@ export async function getMatchedPositionsFromCode(

const transformers = uno.config.transformers?.filter(i => !ignoreTransformers.includes(i.name))
const annotations = []

for (const enforce of ['pre', 'default', 'post']) {
for (const i of transformers?.filter(i => (i.enforce ?? 'default') === enforce) || []) {
const result = await i.transform(s, id, ctx)
const result = await i.transform(s, id, ctx, true)
const _annotations = result?.highlightAnnotations
if (_annotations)
annotations.push(..._annotations)
Expand Down
2 changes: 1 addition & 1 deletion packages/shared-integration/src/sort-rules.ts
Expand Up @@ -11,7 +11,7 @@ export async function sortRules(rules: string, uno: UnoGenerator) {
// const hasAttributify = !!uno.config.presets.find(i => i.name === '@unocss/preset-attributify')
// const hasVariantGroup = !!uno.config.transformers?.find(i => i.name === '@unocss/transformer-variant-group')

const expandedResult = parseVariantGroup(rules) // todo read seperators from config
const expandedResult = parseVariantGroup(rules, undefined, undefined, true) // todo read seperators from config
rules = expandedResult.expanded

const result = await Promise.all(rules.split(/\s+/g)
Expand Down
Expand Up @@ -4,7 +4,7 @@ import { expandVariantGroup, warnOnce } from '@unocss/core'
type Writeable<T> = { -readonly [P in keyof T]: T[P] }

export async function getUtils(body: string, uno: UnoGenerator): Promise<StringifiedUtil[]> {
const classNames = expandVariantGroup(body)
const classNames = expandVariantGroup(body, undefined, undefined, true)
.split(/\s+/g)
.map(className => className.trim().replace(/\\/, ''))

Expand Down
Expand Up @@ -13,7 +13,7 @@ export async function processClassBody(
uno: UnoGenerator,
filename: string,
): Promise<Partial<ProcessResult>> {
const expandedBody = expandVariantGroup(body)
const expandedBody = expandVariantGroup(body, undefined, undefined, true)

const { rulesToGenerate: rulesFromExpressions, restOfBody, updatedExpressions } = await processExpressions(expandedBody, options, uno, filename)
const { rulesToGenerate: rulesFromRegularClasses, ignore } = await sortClassesIntoCategories(restOfBody, options, uno, filename)
Expand Down
5 changes: 2 additions & 3 deletions packages/transformer-directives/src/apply.ts
Expand Up @@ -37,7 +37,7 @@ export async function parseApply({ code, uno, offset, applyVariable }: Transform
if (!body)
return

const classNames = expandVariantGroup(body)
const classNames = expandVariantGroup(body, undefined, undefined, true)
.split(/\s+/g)
.map(className => className.trim().replace(/\\/, ''))

Expand All @@ -53,7 +53,7 @@ export async function parseApply({ code, uno, offset, applyVariable }: Transform
if (target)
target[2] += item[2]
else
// use spread operator to prevent reassign to uno internal cache
// use spread operator to prevent reassign to uno internal cache
acc.push([...item] as Writeable<StringifiedUtil>)
return acc
}, [] as Writeable<StringifiedUtil>[])
Expand Down Expand Up @@ -88,7 +88,6 @@ export async function parseApply({ code, uno, offset, applyVariable }: Transform
})
newSelector = generate(prelude)
}

let css = `${newSelector}{${body}}`
if (parent)
css = `${parent}{${css}}`
Expand Down
4 changes: 2 additions & 2 deletions packages/transformer-variant-group/src/index.ts
Expand Up @@ -25,8 +25,8 @@ export default function transformerVariantGroup(
return {
name: '@unocss/transformer-variant-group',
enforce: 'pre',
transform(s) {
const result = parseVariantGroup(s, options.separators)
transform(s, id, ctx, onlyAttribute?: boolean) {
const result = parseVariantGroup(s, options.separators, undefined, onlyAttribute)
return {
get highlightAnnotations() {
return [...result.groupsByOffset.values()].flatMap(group => group.items)
Expand Down