diff --git a/src/lib/defaultExtractor.js b/src/lib/defaultExtractor.js
index eccf8efc4..49004e18d 100644
--- a/src/lib/defaultExtractor.js
+++ b/src/lib/defaultExtractor.js
@@ -14,6 +14,11 @@ const PATTERNS = [
/([^<>"'`\s]*\[[^<>"'`\s]*:"[^"'`\s]*"\])/.source, // `[content:"hello"]` but not `[content:'hello']`
/([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
/([^<>"'`\s]*[^"'`\s:\\])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
+
+ // Arbitrary properties
+ // /([^"\s]*\[[^\s]+?\][^"\s]*)/.source,
+ // /([^'\s]*\[[^\s]+?\][^'\s]*)/.source,
+ // /([^`\s]*\[[^\s]+?\][^`\s]*)/.source,
].join('|')
const BROAD_MATCH_GLOBAL_REGEXP = new RegExp(PATTERNS, 'g')
@@ -29,3 +34,13 @@ export function defaultExtractor(content) {
return results
}
+
+// Regular utilities
+// {{modifier}:}*{namespace}{-{suffix}}*{/{opacityModifier}}?
+
+// Arbitrary values
+// {{modifier}:}*{namespace}-[{arbitraryValue}]{/{opacityModifier}}?
+// arbitraryValue: no whitespace, balanced quotes unless within quotes, balanced brackets unless within quotes
+
+// Arbitrary properties
+// {{modifier}:}*[{validCssPropertyName}:{arbitraryValue}]
diff --git a/tests/default-extractor.test.js b/tests/default-extractor.test.js
index 5c128f642..e507f8de4 100644
--- a/tests/default-extractor.test.js
+++ b/tests/default-extractor.test.js
@@ -25,6 +25,7 @@ const input =
` + jsxExample
@@ -65,6 +66,7 @@ const includes = [
`fill-[#bada55]/50`,
`px-1.5`,
`uppercase`,
+ `lowercase`,
`hover:font-bold`,
`text-sm`,
`text-[10px]`,
@@ -87,6 +89,7 @@ const includes = [
`content-['>']`,
`hover:test`,
`overflow-scroll`,
+ `[--y:theme(colors.blue.500)]`,
]
const excludes = [
@@ -109,3 +112,214 @@ test('The default extractor works as expected', async () => {
expect(extractions).not.toContain(str)
}
})
+
+// Scenarios:
+// - In double quoted class attribute
+// - In single quoted class attribute
+// - Single-quoted as a variable
+// - Double-quoted as a variable
+// - Single-quoted as first array item
+// - Double-quoted as first array item
+// - Single-quoted as middle array item
+// - Double-quoted as middle array item
+// - Single-quoted as last array item
+// - Double-quoted as last array item
+// - Bare as an object key (with trailing `:`)
+// - Quoted as an object key (with trailing `:`)
+// - Within a template literal
+// - Within a template literal directly before interpolation
+// - Within a template literal directly after interpolation
+// - JS: ${...}
+// - PHP: {$...}
+// - Ruby: #{...}
+// - Within a string of HTML wrapped in escaped quotes
+
+test('basic utility classes', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('text-center')
+ expect(extractions).toContain('font-bold')
+ expect(extractions).toContain('px-4')
+ expect(extractions).toContain('pointer-events-none')
+})
+
+test('modifiers with basic utilites', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('hover:text-center')
+ expect(extractions).toContain('hover:focus:font-bold')
+})
+
+test('utilities with dot characters', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('px-1.5')
+ expect(extractions).toContain('active:px-2.5')
+ expect(extractions).toContain('hover:focus:px-3.5')
+})
+
+test('basic utilities with color opacity modifier', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('text-red-500/25')
+ expect(extractions).toContain('hover:text-red-500/50')
+ expect(extractions).toContain('hover:active:text-red-500/75')
+})
+
+test('basic arbitrary values', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('px-[25px]')
+ expect(extractions).toContain('hover:px-[40rem]')
+ expect(extractions).toContain('hover:focus:px-[23vh]')
+})
+
+test('arbitrary values with color opacity modifier', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('text-[#bada55]/25')
+ expect(extractions).toContain('hover:text-[#bada55]/50')
+ expect(extractions).toContain('hover:active:text-[#bada55]/75')
+})
+
+test('arbitrary values with spaces', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('grid-cols-[1fr_200px_3fr]')
+ expect(extractions).toContain('md:grid-cols-[2fr_100px_1fr]')
+ expect(extractions).toContain('open:lg:grid-cols-[3fr_300px_1fr]')
+})
+
+test('arbitrary values with css variables', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('fill-[var(--my-color)]')
+ expect(extractions).toContain('hover:fill-[var(--my-color-2)]')
+ expect(extractions).toContain('hover:focus:fill-[var(--my-color-3)]')
+})
+
+test('arbitrary values with type hints', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('text-[color:var(--my-color)]')
+ expect(extractions).toContain('hover:text-[color:var(--my-color-2)]')
+ expect(extractions).toContain('hover:focus:text-[color:var(--my-color-3)]')
+})
+
+test('arbitrary values with single quotes', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain(`content-['hello_world']`)
+ expect(extractions).toContain(`hover:content-['hello_world_2']`)
+ expect(extractions).toContain(`hover:focus:content-['hello_world_3']`)
+})
+
+test('arbitrary values with double quotes', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain(`content-["hello_world"]`)
+ expect(extractions).toContain(`hover:content-["hello_world_2"]`)
+ expect(extractions).toContain(`hover:focus:content-["hello_world_3"]`)
+})
+
+test('arbitrary values with some single quoted values', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain(`font-['Open_Sans',_system-ui,_sans-serif]`)
+ expect(extractions).toContain(`hover:font-['Proxima_Nova',_system-ui,_sans-serif]`)
+ expect(extractions).toContain(`hover:focus:font-['Inter_var',_system-ui,_sans-serif]`)
+})
+
+test('arbitrary values with some double quoted values', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain(`font-["Open_Sans",_system-ui,_sans-serif]`)
+ expect(extractions).toContain(`hover:font-["Proxima_Nova",_system-ui,_sans-serif]`)
+ expect(extractions).toContain(`hover:focus:font-["Inter_var",_system-ui,_sans-serif]`)
+})
+
+test('arbitrary values with escaped underscores', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain(`content-['hello\\_world']`)
+ expect(extractions).toContain(`hover:content-['hello\\_world\\_2']`)
+ expect(extractions).toContain(`hover:focus:content-['hello\\_world\\_3']`)
+})
+
+test('basic utilities with arbitrary color opacity modifier', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('text-red-500/[.25]')
+ expect(extractions).toContain('hover:text-red-500/[.5]')
+ expect(extractions).toContain('hover:active:text-red-500/[.75]')
+})
+
+test('arbitrary values with arbitrary color opacity modifier', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain('text-[#bada55]/[.25]')
+ expect(extractions).toContain('hover:text-[#bada55]/[.5]')
+ expect(extractions).toContain('hover:active:text-[#bada55]/[.75]')
+})
+
+test('arbitrary values with angle brackets', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain(`content-[>]`)
+ expect(extractions).toContain(`hover:content-[<]`)
+ expect(extractions).toContain(`hover:focus:content-[>]`)
+})
+
+test('arbitrary values with angle brackets in single quotes', async () => {
+ const extractions = defaultExtractor(`
+
+ `)
+
+ expect(extractions).toContain(`content-['>']`)
+ expect(extractions).toContain(`hover:content-['<']`)
+ expect(extractions).toContain(`hover:focus:content-['>']`)
+})
+
+test('arbitrary values with angle brackets in double quotes', async () => {
+ const extractions = defaultExtractor(`
+
"] hover:content-["<"] hover:focus:content-[">"]">
+ `)
+
+ expect(extractions).toContain(`content-[">"]`)
+ expect(extractions).toContain(`hover:content-["<"]`)
+ expect(extractions).toContain(`hover:focus:content-[">"]`)
+})