-
-
Notifications
You must be signed in to change notification settings - Fork 780
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(variantGroup): nested variantGroup
- Loading branch information
Showing
10 changed files
with
361 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
}, | ||
] | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) |
Oops, something went wrong.