From 195c799d90abecd2cadcfafa8d4f8b37fe9f8f77 Mon Sep 17 00:00:00 2001 From: sibbng Date: Wed, 23 Nov 2022 00:17:45 +0300 Subject: [PATCH 1/6] feat(preset-mini): support arbitrary properties with quotes --- packages/core/src/extractors/index.ts | 2 +- packages/core/src/extractors/split.ts | 18 +++++++++++++++++- packages/preset-mini/src/_rules/variables.ts | 9 ++++++++- packages/shared-common/src/index.ts | 10 +++++++++- test/__snapshots__/preset-mini.test.ts.snap | 4 ++++ test/assets/preset-mini-targets.ts | 4 ++++ 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/core/src/extractors/index.ts b/packages/core/src/extractors/index.ts index ecec67de51..bc4541d0b4 100644 --- a/packages/core/src/extractors/index.ts +++ b/packages/core/src/extractors/index.ts @@ -1,2 +1,2 @@ -export { extractorSplit } from './split' +export { extractorSplit, cssPropertyRE } from './split' export { extractorSvelte } from './svelte' diff --git a/packages/core/src/extractors/split.ts b/packages/core/src/extractors/split.ts index 899e83a345..40ae38fd70 100644 --- a/packages/core/src/extractors/split.ts +++ b/packages/core/src/extractors/split.ts @@ -1,7 +1,23 @@ import type { Extractor } from '../types' import { isValidSelector } from '../utils' -export const splitCode = (code: string) => [...new Set(code.split(/\\?[\s'"`;{}]+/g))].filter(isValidSelector) +const defaultSplitRE = /\\?[\s'"`;{}]+/g +export const cssPropertyRE = /[\s'"`](\[(\\?[^\s])+:['"]?[^\s]*?['"]?\]):?/g + +export const splitCode = (code: string) => { + const result = new Set() + + for (const match of code.matchAll(cssPropertyRE)) { + if (!match[0].endsWith(':')) + result.add(match[1]) + } + + code.split(defaultSplitRE).forEach((match) => { + isValidSelector(match) && result.add(match) + }) + + return [...result] +} export const extractorSplit: Extractor = { name: 'split', diff --git a/packages/preset-mini/src/_rules/variables.ts b/packages/preset-mini/src/_rules/variables.ts index f851bc54af..d0bb92fd91 100644 --- a/packages/preset-mini/src/_rules/variables.ts +++ b/packages/preset-mini/src/_rules/variables.ts @@ -26,5 +26,12 @@ export const cssVariables: Rule[] = [ ] export const cssProperty: Rule[] = [ - [/^\[([\w_-]+):([^'"]+)\]$/, ([, prop, value]) => ({ [prop]: h.bracket(`[${value}]`) })], + [/^\[(.+):(.+)\]$/, ([, prop, value]) => { + // preset-wind.test.ts / non-targets + // allow `~` and `=` only in css variables as they can be escaped + if (/~|=/.test(prop) && !prop.startsWith('--')) + return + + return { [prop]: h.bracket(`[${value}]`) } + }], ] diff --git a/packages/shared-common/src/index.ts b/packages/shared-common/src/index.ts index 59005d0077..25d5229b6d 100644 --- a/packages/shared-common/src/index.ts +++ b/packages/shared-common/src/index.ts @@ -1,5 +1,5 @@ import type { UnoGenerator } from '@unocss/core' -import { escapeRegExp, isAttributifySelector, regexClassGroup } from '@unocss/core' +import { cssPropertyRE, escapeRegExp, isAttributifySelector, regexClassGroup } from '@unocss/core' import MagicString from 'magic-string' // https://github.com/dsblv/string-replace-async/blob/main/index.js @@ -53,6 +53,14 @@ export function getMatchedPositions(code: string, matched: string[], hasVariantG start = end }) + // highlight for arbitrary css properties + for (const match of code.matchAll(cssPropertyRE)) { + const start = match.index! + 1 + const end = start + match[1].length + if (plain.has(match[1])) + result.push([start, end, match[1]]) + } + // highlight for variant group if (hasVariantGroup) { Array.from(code.matchAll(regexClassGroup)) diff --git a/test/__snapshots__/preset-mini.test.ts.snap b/test/__snapshots__/preset-mini.test.ts.snap index 6b22715909..d6c2f6b9f2 100644 --- a/test/__snapshots__/preset-mini.test.ts.snap +++ b/test/__snapshots__/preset-mini.test.ts.snap @@ -45,6 +45,10 @@ exports[`preset-mini > targets 1`] = ` .\\\\[background-image\\\\:url\\\\(star_transparent\\\\.gif\\\\)\\\\,_url\\\\(cat_front\\\\.png\\\\)\\\\]{background-image:url(star_transparent.gif), url(cat_front.png);} .\\\\[content\\\\:attr\\\\(attr_content\\\\)\\\\]{content:attr(attr content);} .\\\\[content\\\\:attr\\\\(attr\\\\\\\\_content\\\\)\\\\]{content:attr(attr_content);} +.\\\\[font-family\\\\:\\\\'Inter\\\\'\\\\,_sans-serif\\\\]{font-family:'Inter', sans-serif;} +.\\\\[font-family\\\\:var\\\\(--font-family\\\\)\\\\]{font-family:var(--font-family);} +.\\\\[font-feature-settings\\\\:\\\\'cv02\\\\'\\\\,\\\\'cv03\\\\'\\\\,\\\\'cv04\\\\'\\\\,\\\\'cv11\\\\'\\\\]{font-feature-settings:'cv02','cv03','cv04','cv11';} +.\\\\[font-variation-settings\\\\:\\\\\\"wght\\\\\\"_400\\\\,_\\\\\\"opsz\\\\\\"_14\\\\]{font-variation-settings:\\"wght\\" 400, \\"opsz\\" 14;} .\\\\[margin\\\\:logical_1rem_2rem_3rem\\\\]{margin:logical 1rem 2rem 3rem;} .all-\\\\[\\\\.target\\\\]-\\\\[combinator\\\\:test-2\\\\] .target, .children-\\\\[\\\\.target\\\\]-\\\\[combinator\\\\:test-2\\\\]>.target, diff --git a/test/assets/preset-mini-targets.ts b/test/assets/preset-mini-targets.ts index 725e3629fc..ea5ba4ea81 100644 --- a/test/assets/preset-mini-targets.ts +++ b/test/assets/preset-mini-targets.ts @@ -850,6 +850,10 @@ export const presetMiniTargets: string[] = [ '[content:attr(attr_content)]', '[content:attr(attr\\_content)]', '[background-image:url(star_transparent.gif),_url(cat_front.png)]', + '[font-family:var(--font-family)]', + '[font-family:\'Inter\',_sans-serif]', + '[font-feature-settings:\'cv02\',\'cv03\',\'cv04\',\'cv11\']', + '[font-variation-settings:"wght"_400,_"opsz"_14]', // variants 'active:scale-4', From 527738ed84b839bd215d2274329f27baf020547c Mon Sep 17 00:00:00 2001 From: sibbng Date: Wed, 23 Nov 2022 01:55:38 +0300 Subject: [PATCH 2/6] fix: inspector build --- packages/preset-mini/src/_rules/variables.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/preset-mini/src/_rules/variables.ts b/packages/preset-mini/src/_rules/variables.ts index d0bb92fd91..208abaa00e 100644 --- a/packages/preset-mini/src/_rules/variables.ts +++ b/packages/preset-mini/src/_rules/variables.ts @@ -26,7 +26,7 @@ export const cssVariables: Rule[] = [ ] export const cssProperty: Rule[] = [ - [/^\[(.+):(.+)\]$/, ([, prop, value]) => { + [/^\[([^'"]?.+):(.+)\]$/, ([, prop, value]) => { // preset-wind.test.ts / non-targets // allow `~` and `=` only in css variables as they can be escaped if (/~|=/.test(prop) && !prop.startsWith('--')) From 110dd0e14399f1cd0886f058f92d639025b0054e Mon Sep 17 00:00:00 2001 From: sibbng Date: Wed, 23 Nov 2022 02:08:01 +0300 Subject: [PATCH 3/6] fix: inspector build --- packages/preset-mini/src/_rules/variables.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/preset-mini/src/_rules/variables.ts b/packages/preset-mini/src/_rules/variables.ts index 208abaa00e..444c240143 100644 --- a/packages/preset-mini/src/_rules/variables.ts +++ b/packages/preset-mini/src/_rules/variables.ts @@ -26,10 +26,10 @@ export const cssVariables: Rule[] = [ ] export const cssProperty: Rule[] = [ - [/^\[([^'"]?.+):(.+)\]$/, ([, prop, value]) => { + [/^\[(.+):(.+)\]$/, ([, prop, value]) => { // preset-wind.test.ts / non-targets // allow `~` and `=` only in css variables as they can be escaped - if (/~|=/.test(prop) && !prop.startsWith('--')) + if (/[~="]/.test(prop) && !prop.startsWith('--')) return return { [prop]: h.bracket(`[${value}]`) } From 2383b0625309492831e9d9823b0a20e423ef8cdd Mon Sep 17 00:00:00 2001 From: sibbng Date: Wed, 23 Nov 2022 02:38:26 +0300 Subject: [PATCH 4/6] fix: edge cases --- packages/preset-mini/src/_rules/variables.ts | 9 +-------- test/assets/preset-mini-targets.ts | 4 ++++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/preset-mini/src/_rules/variables.ts b/packages/preset-mini/src/_rules/variables.ts index 444c240143..c71a5f4b1e 100644 --- a/packages/preset-mini/src/_rules/variables.ts +++ b/packages/preset-mini/src/_rules/variables.ts @@ -26,12 +26,5 @@ export const cssVariables: Rule[] = [ ] export const cssProperty: Rule[] = [ - [/^\[(.+):(.+)\]$/, ([, prop, value]) => { - // preset-wind.test.ts / non-targets - // allow `~` and `=` only in css variables as they can be escaped - if (/[~="]/.test(prop) && !prop.startsWith('--')) - return - - return { [prop]: h.bracket(`[${value}]`) } - }], + [/^\[(--.+|[\w-]+):(.+)\]$/, ([, prop, value]) => ({ [prop]: h.bracket(`[${value}]`) })], ] diff --git a/test/assets/preset-mini-targets.ts b/test/assets/preset-mini-targets.ts index ea5ba4ea81..3198f4dde1 100644 --- a/test/assets/preset-mini-targets.ts +++ b/test/assets/preset-mini-targets.ts @@ -1048,4 +1048,8 @@ export const presetMiniNonTargets = [ // variants - combinator 'all:[svg]:fill-red', + + // arbitrary css properties edge cases that cause invalid output + '[name].[hash:9]', + '["update:modelValue"]', ] From 52d62f43e158646a3a3ec7fb3ad37bbe962186d6 Mon Sep 17 00:00:00 2001 From: sibbng Date: Wed, 23 Nov 2022 04:52:30 +0300 Subject: [PATCH 5/6] refactor: stricter regex --- packages/core/src/extractors/split.ts | 8 +++----- packages/preset-mini/src/_rules/variables.ts | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/core/src/extractors/split.ts b/packages/core/src/extractors/split.ts index 40ae38fd70..760a51db50 100644 --- a/packages/core/src/extractors/split.ts +++ b/packages/core/src/extractors/split.ts @@ -2,15 +2,13 @@ import type { Extractor } from '../types' import { isValidSelector } from '../utils' const defaultSplitRE = /\\?[\s'"`;{}]+/g -export const cssPropertyRE = /[\s'"`](\[(\\?[^\s])+:['"]?[^\s]*?['"]?\]):?/g +export const cssPropertyRE = /[\s'"`](\[(\\\W|[\w-])+:['"]?\S*?['"]?\])/g export const splitCode = (code: string) => { const result = new Set() - for (const match of code.matchAll(cssPropertyRE)) { - if (!match[0].endsWith(':')) - result.add(match[1]) - } + for (const match of code.matchAll(cssPropertyRE)) + result.add(match[1]) code.split(defaultSplitRE).forEach((match) => { isValidSelector(match) && result.add(match) diff --git a/packages/preset-mini/src/_rules/variables.ts b/packages/preset-mini/src/_rules/variables.ts index c71a5f4b1e..2fa2ec0bbe 100644 --- a/packages/preset-mini/src/_rules/variables.ts +++ b/packages/preset-mini/src/_rules/variables.ts @@ -26,5 +26,5 @@ export const cssVariables: Rule[] = [ ] export const cssProperty: Rule[] = [ - [/^\[(--.+|[\w-]+):(.+)\]$/, ([, prop, value]) => ({ [prop]: h.bracket(`[${value}]`) })], + [/^\[(--(\w|\\\W)+|[\w-]+):(.+)\]$/, ([, prop,, value]) => ({ [prop]: h.bracket(`[${value}]`) })], ] From 0288d5aaeb074190748a745fd077b6a3a0464194 Mon Sep 17 00:00:00 2001 From: sibbng Date: Wed, 23 Nov 2022 21:32:55 +0300 Subject: [PATCH 6/6] test: add test for arbitrary css variables --- test/__snapshots__/preset-mini.test.ts.snap | 2 ++ test/assets/preset-mini-targets.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/test/__snapshots__/preset-mini.test.ts.snap b/test/__snapshots__/preset-mini.test.ts.snap index d6c2f6b9f2..ff5793ef57 100644 --- a/test/__snapshots__/preset-mini.test.ts.snap +++ b/test/__snapshots__/preset-mini.test.ts.snap @@ -41,6 +41,8 @@ exports[`preset-mini > targets 1`] = ` .fw-\\\\$variable{font-weight:var(--variable);} .items-\\\\$size{align-items:var(--size);} .ws-\\\\$variable{white-space:var(--variable);} +.\\\\[--css-variable\\\\:\\\\\\"wght\\\\\\"_400\\\\,_\\\\\\"opsz\\\\\\"_14\\\\]{--css-variable:\\"wght\\" 400, \\"opsz\\" 14;} +.\\\\[--escaped\\\\\\\\\\\\~variable\\\\\\\\\\\\:\\\\:100\\\\%\\\\]{--escaped\\\\~variable\\\\::100%;} .\\\\[a\\\\:b\\\\]{a:b;} .\\\\[background-image\\\\:url\\\\(star_transparent\\\\.gif\\\\)\\\\,_url\\\\(cat_front\\\\.png\\\\)\\\\]{background-image:url(star_transparent.gif), url(cat_front.png);} .\\\\[content\\\\:attr\\\\(attr_content\\\\)\\\\]{content:attr(attr content);} diff --git a/test/assets/preset-mini-targets.ts b/test/assets/preset-mini-targets.ts index 3198f4dde1..1b37229f42 100644 --- a/test/assets/preset-mini-targets.ts +++ b/test/assets/preset-mini-targets.ts @@ -854,6 +854,8 @@ export const presetMiniTargets: string[] = [ '[font-family:\'Inter\',_sans-serif]', '[font-feature-settings:\'cv02\',\'cv03\',\'cv04\',\'cv11\']', '[font-variation-settings:"wght"_400,_"opsz"_14]', + '[--css-variable:"wght"_400,_"opsz"_14]', + '[--escaped\\~variable\\::100%]', // variants 'active:scale-4', @@ -1052,4 +1054,6 @@ export const presetMiniNonTargets = [ // arbitrary css properties edge cases that cause invalid output '[name].[hash:9]', '["update:modelValue"]', + // escaped arbitrary css properties only allowed in css variables + '[cant\~escape:me]', ]