From 8b4471778b211d92bf8f81e4fc82304377730279 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 17 Nov 2022 17:53:12 +0100 Subject: [PATCH] Fix `foo-[abc]/[def]` not being handled correctl (#9866) * fix `foo-[abc]/[def]` not being handled correctly This commit does a bit of cleanup, it also ensures that we lookup `[abc]/[def]` in the `values` first, and if it doesn't exist, then we start parsing all the values out. We also ensure that `abc` and `def` are parsed out correctly for the correct type instead of dropping the rule altogether because we happen to end up with an `any` rule. TODO: we should further clean the whole type system because this should only be used to figure out what type an arbitrary value is and to find the corresponding plugin and that's it. One of the fixes is doing a crazy lookup and running a generator, even though we know it is a lookup value so we should be done with all the work anyways. * update changelog --- CHANGELOG.md | 1 + src/util/pluginUtils.js | 43 +++-- tests/match-utilities.test.js | 339 ++++++++++++++++++++++++++++++++++ 3 files changed, 364 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b1981c32..ab1b307ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Cleanup unused `variantOrder` ([#9829](https://github.com/tailwindlabs/tailwindcss/pull/9829)) +- Fix `foo-[abc]/[def]` not being handled correctl ([#9866](https://github.com/tailwindlabs/tailwindcss/pull/9866)) ### Added diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index 5049b088e..7f8c4ab74 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -133,18 +133,14 @@ export function parseColorFormat(value) { return value } -export function asColor( - _, - options = {}, - { tailwindConfig = {}, utilityModifier, rawModifier } = {} -) { - if (options.values?.[rawModifier] !== undefined) { - return parseColorFormat(options.values?.[rawModifier]) +export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) { + if (options.values?.[modifier] !== undefined) { + return parseColorFormat(options.values?.[modifier]) } // TODO: Hoist this up to getMatchingTypes or something // We do this here because we need the alpha value (if any) - let [color, alpha] = splitUtilityModifier(rawModifier) + let [color, alpha] = splitUtilityModifier(modifier) if (alpha !== undefined) { let normalizedColor = @@ -167,7 +163,7 @@ export function asColor( return withAlphaValue(normalizedColor, tailwindConfig.theme.opacity[alpha]) } - return asValue(rawModifier, options, { rawModifier, utilityModifier, validate: validateColor }) + return asValue(modifier, options, { validate: validateColor }) } export function asLookupValue(modifier, options = {}) { @@ -175,8 +171,8 @@ export function asLookupValue(modifier, options = {}) { } function guess(validate) { - return (modifier, options, extras) => { - return asValue(modifier, options, { ...extras, validate }) + return (modifier, options) => { + return asValue(modifier, options, { validate }) } } @@ -208,6 +204,22 @@ function splitAtFirst(input, delim) { } export function coerceValue(types, modifier, options, tailwindConfig) { + if (options.values && modifier in options.values) { + for (let { type } of types ?? []) { + let result = typeMap[type](modifier, options, { + tailwindConfig, + }) + + if (result === undefined) { + continue + } + + return [result, type, null] + } + + return [options.values[modifier], 'lookup', null] + } + if (isArbitraryValue(modifier)) { let arbitraryValue = modifier.slice(1, -1) let [explicitType, value] = splitAtFirst(arbitraryValue, ':') @@ -280,17 +292,10 @@ export function* getMatchingTypes(types, rawModifier, options, tailwindConfig) { utilityModifier = utilityModifier.slice(1, -1) } } - - let result = asValue(rawModifier, options, { rawModifier, utilityModifier, tailwindConfig }) - if (result !== undefined) { - yield [result, 'any', null] - } } - for (const { type } of types ?? []) { + for (let { type } of types ?? []) { let result = typeMap[type](modifier, options, { - rawModifier, - utilityModifier, tailwindConfig, }) diff --git a/tests/match-utilities.test.js b/tests/match-utilities.test.js index 587a46866..0e76373ef 100644 --- a/tests/match-utilities.test.js +++ b/tests/match-utilities.test.js @@ -165,3 +165,342 @@ test('match utilities can omit utilities by returning null', async () => { } `) }) + +test('matching utilities with an arbitrary value and configured modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + modifiers: { + bar: 'configured_bar', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/bar { + value: foo; + modifier: configured_bar; + } + `) + }) +}) + +test('matching utilities with an configured value and an arbitrary modifier (raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + foo: 'configured_foo', + }, + modifiers: 'any', // Raw `[value]` + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/\[bar\] { + value: configured_foo; + modifier: [bar]; + } + `) + }) +}) + +test('matching utilities with an configured value and an arbitrary modifier (non-raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + foo: 'configured_foo', + }, + modifiers: {}, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/\[bar\] { + value: configured_foo; + modifier: bar; + } + `) + }) +}) + +test('matching utilities with an configured value and a configured modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + foo: 'configured_foo', + }, + modifiers: { + bar: 'configured_bar', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/bar { + value: configured_foo; + modifier: configured_bar; + } + `) + }) +}) + +test('matching utilities with an arbitrary value and an arbitrary modifier (raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + modifiers: 'any', + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: foo; + modifier: [bar]; + } + `) + }) +}) + +test('matching utilities with an arbitrary value and an arbitrary modifier (non-raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + modifiers: {}, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: foo; + modifier: bar; + } + `) + }) +}) + +test('matching utilities with a lookup value that looks like an arbitrary value and modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/[bar]': 'hello', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: hello; + } + `) + }) +}) + +test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = any)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/[bar]': 'hello', + }, + modifiers: 'any', + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: hello; + } + `) + }) +}) + +test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = {})', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/[bar]': 'hello', + }, + modifiers: {}, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: hello; + } + `) + }) +}) + +test('matching utilities with a lookup value that looks like an arbitrary value and a configured modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/bar': 'hello', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/bar { + value: hello; + } + `) + }) +}) + +test('matching utilities with a lookup value that looks like a configured value and an arbitrary modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + 'foo/[bar]': 'hello', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/\[bar\] { + value: hello; + } + `) + }) +})