diff --git a/packages/core/src/utils/extractQuoted.ts b/packages/core/src/utils/extractQuoted.ts deleted file mode 100644 index 969afb85ea..0000000000 --- a/packages/core/src/utils/extractQuoted.ts +++ /dev/null @@ -1,124 +0,0 @@ -export type Range = [start: number, end: number] - -export interface ExtractStringOptions
{ - deep?: boolean - templateStaticOnly?: boolean - details?: Details - range?: Range -} - -export interface DetailString { - value: string - range: Range - quote: '\'' | '"' | '`' -} - -export function matchingPair( - text: string, - [left, right]: Iterable, - 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): string[] -export function extractQuoted(str: string, options: ExtractStringOptions): DetailString[] -export function extractQuoted( - str: string, - options: ExtractStringOptions = {}, -): 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, - ), - ) - } - } - else { - end += 1 - } - } - i = end + 1 - } - else { - i++ - } - } - - return result -} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 1b23f10974..e15b3b6386 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -7,4 +7,3 @@ export * from './layer' export * from './variantGroup' export * from './warn' export * from './handlers' -export * from './extractQuoted' diff --git a/packages/core/src/utils/variantGroup.ts b/packages/core/src/utils/variantGroup.ts index 49634a074e..1b95b45aae 100644 --- a/packages/core/src/utils/variantGroup.ts +++ b/packages/core/src/utils/variantGroup.ts @@ -2,24 +2,30 @@ import type MagicString from 'magic-string' export const regexClassGroup = /([!\w+:_/-]+?)([:-])\(((?:[~!\w\s:/\\,%#.$-]|\[.*?\])*?)\)/gm -export function expandVariantGroup(str: string): string -export function expandVariantGroup(str: MagicString): MagicString -export function expandVariantGroup(str: string | MagicString) { +export function expandVariantGroup(str: string, seperators?: ('-' | ':')[]): string +export function expandVariantGroup(str: MagicString, seperators?: ('-' | ':')[]): MagicString +export function expandVariantGroup(str: string | MagicString, seperators: ('-' | ':')[] = ['-', ':']) { regexClassGroup.lastIndex = 0 let hasChanged = false + let content = str.toString() do { - const before = str.toString() - str = str.replace( + const before = content + content = content.replace( regexClassGroup, - (_, pre, sep, body: string) => { + (from, pre, sep, body: string) => { + if (!seperators.includes(sep)) + return from return body .split(/\s/g) .map(i => i === '~' ? pre : i.replace(/^(!?)(.*)/, `$1${pre}${sep}$2`)) .join(' ') }, ) - hasChanged = str.toString() !== before + hasChanged = content !== before } while (hasChanged) - return str + if (typeof str === 'string') + return content + else + return str.overwrite(0, str.length(), content) } diff --git a/packages/transformer-variant-group/src/index.ts b/packages/transformer-variant-group/src/index.ts index 959a9fbd43..84c3927e91 100644 --- a/packages/transformer-variant-group/src/index.ts +++ b/packages/transformer-variant-group/src/index.ts @@ -1,21 +1,30 @@ import type { SourceCodeTransformer } from '@unocss/core' -import { expandVariantGroup, extractQuoted } from '@unocss/core' +import { expandVariantGroup } from '@unocss/core' -export default function transformerVariantGroup(): SourceCodeTransformer { +export interface TransformerVariantGroupOptions { + /** + * Separators to expand. + * + * ``` + * foo-(bar baz) -> foo-bar foo-baz + * ^ + * separator + * ``` + * + * You may set it to `[':']` for strictness. + * + * @default [':', '-'] + * @see https://github.com/unocss/unocss/pull/1231 + */ + separators?: (':' | '-')[] +} + +export default function transformerVariantGroup(options: TransformerVariantGroupOptions = {}): SourceCodeTransformer { return { name: 'variant-group', enforce: 'pre', transform(s) { - extractQuoted( - s.toString(), - { - details: true, - templateStaticOnly: true, - deep: true, - }, - ) - .filter(({ value: { length } }) => length) - .forEach(({ value, range }) => s.overwrite(...range, expandVariantGroup(value))) + expandVariantGroup(s, options.separators) }, } } diff --git a/test/extract-quoted.test.ts b/test/extract-quoted.test.ts deleted file mode 100644 index c618bed431..0000000000 --- a/test/extract-quoted.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -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')) - .replace(/\r\n/g, '\n') - .trim() - - 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) -}) diff --git a/test/transformer-variant-group.test.ts b/test/transformer-variant-group.test.ts index 0fe64d7c93..7d33dc5a97 100644 --- a/test/transformer-variant-group.test.ts +++ b/test/transformer-variant-group.test.ts @@ -1,5 +1,4 @@ import { readFile } from 'fs/promises' -import transformerVariantGroup from '@unocss/transformer-variant-group' import { describe, expect, test } from 'vitest' import { expandVariantGroup } from '@unocss/core' import MagicString from 'magic-string' @@ -32,13 +31,13 @@ describe('transformer-variant-group', () => { } }) - test('vue file', async () => { - const transformer = transformerVariantGroup() - const transform = async (code: string) => { + test('vue file with strict sep', async () => { + async function transform(code: string) { const s = new MagicString(code) - await transformer.transform(s, '', {} as any) + expandVariantGroup(s, [':']) return s.toString() } + const file = await readFile('./test/assets/variant-group.vue', 'utf-8') const result = await transform(file) expect(result).toMatchInlineSnapshot(` @@ -48,7 +47,7 @@ describe('transformer-variant-group', () => { // eslint-disable-next-line @typescript-eslint/space-infix-ops const c = a-(b -a -b) - +