From aa5296222efa9cd44a23c1863ec88cbe646fd5cc Mon Sep 17 00:00:00 2001 From: lzt1008 Date: Sat, 17 Sep 2022 18:50:07 +0800 Subject: [PATCH 1/8] Improve type checking for formal syntax --- src/corePlugins.js | 2 +- src/util/dataTypes.js | 22 +++++++++---------- src/util/pluginUtils.js | 2 ++ src/util/validateFormalSyntax.js | 34 ++++++++++++++++++++++++++++++ tests/arbitrary-values.test.js | 36 ++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 src/util/validateFormalSyntax.js diff --git a/src/corePlugins.js b/src/corePlugins.js index 3fd4ed99c51a..67cd4f0c6155 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -1482,7 +1482,7 @@ export let corePlugins = { }, backgroundSize: createUtilityPlugin('backgroundSize', [['bg', ['background-size']]], { - type: ['lookup', ['length', { preferOnConflict: true }], 'percentage'], + type: ['lookup', ['length', { preferOnConflict: true }], 'percentage', 'size'], }), backgroundAttachment: ({ addUtilities }) => { diff --git a/src/util/dataTypes.js b/src/util/dataTypes.js index d7f04f7044bc..beb4cec89499 100644 --- a/src/util/dataTypes.js +++ b/src/util/dataTypes.js @@ -6,6 +6,10 @@ let cssFunctions = ['min', 'max', 'clamp', 'calc'] // Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types +function isCSSFunction(value) { + return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value)) +} + // This is not a data type, but rather a function that can normalize the // correct values. export function normalize(value, isRoot = true) { @@ -55,13 +59,11 @@ export function url(value) { } export function number(value) { - return !isNaN(Number(value)) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?`).test(value)) + return !isNaN(Number(value)) || isCSSFunction(value) } export function percentage(value) { - return splitAtTopLevelOnly(value, '_').every((part) => { - return /%$/g.test(part) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?%`).test(part)) - }) + return (value.endsWith('%') && number(value.slice(0, -1))) || isCSSFunction(value) } let lengthUnits = [ @@ -84,13 +86,11 @@ let lengthUnits = [ ] let lengthUnitsPattern = `(?:${lengthUnits.join('|')})` export function length(value) { - return splitAtTopLevelOnly(value, '_').every((part) => { - return ( - part === '0' || - new RegExp(`${lengthUnitsPattern}$`).test(part) || - cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?${lengthUnitsPattern}`).test(part)) - ) - }) + return ( + value === '0' || + new RegExp(`^[+-]?[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`).test(value) || + isCSSFunction(value) + ) } let lineWidths = new Set(['thin', 'medium', 'thick']) diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index d056a78cee01..c56f6a1dd412 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -18,6 +18,7 @@ import { shadow, } from './dataTypes' import negateValue from './negateValue' +import { backgroundSize } from './validateFormalSyntax' export function updateAllClasses(selectors, updateClass) { let parser = selectorParser((selectors) => { @@ -162,6 +163,7 @@ export let typeMap = { 'absolute-size': guess(absoluteSize), 'relative-size': guess(relativeSize), shadow: guess(shadow), + size: guess(backgroundSize), } let supportedTypes = Object.keys(typeMap) diff --git a/src/util/validateFormalSyntax.js b/src/util/validateFormalSyntax.js new file mode 100644 index 000000000000..bf56acdcc122 --- /dev/null +++ b/src/util/validateFormalSyntax.js @@ -0,0 +1,34 @@ +import { length, percentage } from './dataTypes' +import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' + +/** + * + * https://developer.mozilla.org/en-US/docs/Web/CSS/background-size#formal_syntax + * + * background-size = + * # + * + * = + * [ | auto ]{1,2} | + * cover | + * contain + * + * = + * | + * + * + * @param {string} value + */ +export function backgroundSize(value) { + const keywordValues = ['cover', 'contain'] + // the type will probably be a css function + // so we have to use `splitAtTopLevelOnly` + return splitAtTopLevelOnly(value, ',').every((part) => { + const sizes = splitAtTopLevelOnly(part, '_').filter(Boolean) + if (sizes.length === 1 && keywordValues.includes(sizes[0])) return true + + if (sizes.length !== 1 && sizes.length !== 2) return false + + return sizes.every((size) => length(size) || percentage(size) || size === 'auto') + }) +} diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index a4dbf6fe990a..e13d829c82ac 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -463,3 +463,39 @@ it('should correctly validate each part when checking for `percentage` data type `) }) }) + +it('should correctly validate background size', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-\[auto_auto\2c cover\2c _contain\2c 10px\2c 10px_10\%\] { + background-size: auto auto, cover, contain, 10px, 10px 10%; + } + `) + }) +}) + +it('should correctly validate combination of percentage and length', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css.trim()).toHaveLength(0) + }) +}) From 15c1ab47e68f2a9c3792e252b02b288cdb80fbc4 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 19 Sep 2022 10:45:00 -0400 Subject: [PATCH 2/8] Add test --- tests/arbitrary-values.test.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index e13d829c82ac..4e1449916e53 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -499,3 +499,28 @@ it('should correctly validate combination of percentage and length', () => { expect(result.css.trim()).toHaveLength(0) }) }) + +it('can explicitly specify type for percentage and length', () => { + let config = { + content: [ + { raw: html`
` }, + ], + corePlugins: { preflight: false }, + plugins: [], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(` + .bg-\\[size\\:50px_10\\%\\] { + background-size: 50px 10%; + } + .bg-\\[position\\:50\\%_10\\%\\] { + background-position: 50% 10%; + } + `) + }) +}) From a6c6d27b3236ebacbfaf885362b4ec6042cff6cb Mon Sep 17 00:00:00 2001 From: liangzhengtai Date: Tue, 20 Sep 2022 14:07:30 +0800 Subject: [PATCH 3/8] Change order of test class name --- tests/arbitrary-values.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index 4e1449916e53..171ebac45ddd 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -503,7 +503,7 @@ it('should correctly validate combination of percentage and length', () => { it('can explicitly specify type for percentage and length', () => { let config = { content: [ - { raw: html`
` }, + { raw: html`
` }, ], corePlugins: { preflight: false }, plugins: [], From 6d8ce9e40559dd036434fd987067b6f02955fb3b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 29 Sep 2022 17:47:15 +0200 Subject: [PATCH 4/8] fix failing tests --- src/corePlugins.js | 2 +- src/util/validateFormalSyntax.js | 4 ++-- tests/arbitrary-values.test.js | 23 ++++++++++++++++++----- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/corePlugins.js b/src/corePlugins.js index 67cd4f0c6155..16198f972b08 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -1482,7 +1482,7 @@ export let corePlugins = { }, backgroundSize: createUtilityPlugin('backgroundSize', [['bg', ['background-size']]], { - type: ['lookup', ['length', { preferOnConflict: true }], 'percentage', 'size'], + type: ['lookup', 'length', 'percentage', ['size', { preferOnConflict: true }]], }), backgroundAttachment: ({ addUtilities }) => { diff --git a/src/util/validateFormalSyntax.js b/src/util/validateFormalSyntax.js index bf56acdcc122..d3dafea1f07f 100644 --- a/src/util/validateFormalSyntax.js +++ b/src/util/validateFormalSyntax.js @@ -20,11 +20,11 @@ import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' * @param {string} value */ export function backgroundSize(value) { - const keywordValues = ['cover', 'contain'] + let keywordValues = ['cover', 'contain'] // the type will probably be a css function // so we have to use `splitAtTopLevelOnly` return splitAtTopLevelOnly(value, ',').every((part) => { - const sizes = splitAtTopLevelOnly(part, '_').filter(Boolean) + let sizes = splitAtTopLevelOnly(part, '_').filter(Boolean) if (sizes.length === 1 && keywordValues.includes(sizes[0])) return true if (sizes.length !== 1 && sizes.length !== 2) return false diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index 171ebac45ddd..4e32a4d99b1a 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -311,7 +311,7 @@ it('should warn and not generate if arbitrary values are ambiguous (without fall plugins: [ function ({ matchUtilities }) { matchUtilities({ foo: (value) => ({ value }) }, { type: ['position'] }) - matchUtilities({ foo: (value) => ({ value }) }, { type: ['length'] }) + matchUtilities({ foo: (value) => ({ value }) }, { type: ['size'] }) }, ], } @@ -496,7 +496,17 @@ it('should correctly validate combination of percentage and length', () => { ` return run(input, config).then((result) => { - expect(result.css.trim()).toHaveLength(0) + expect(result.css).toMatchFormattedCss(css` + .bg-\[50px_10\%\] { + background-size: 50px 10%; + } + .bg-\[50\%_10\%\] { + background-size: 50% 10%; + } + .bg-\[50px_10px\] { + background-size: 50px 10px; + } + `) }) }) @@ -514,11 +524,14 @@ it('can explicitly specify type for percentage and length', () => { ` return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(` - .bg-\\[size\\:50px_10\\%\\] { + expect(result.css).toMatchFormattedCss(css` + .bg-\[50px_10px\] { + background-size: 50px 10px; + } + .bg-\[size\:50px_10\%\] { background-size: 50px 10%; } - .bg-\\[position\\:50\\%_10\\%\\] { + .bg-\[position\:50\%_10\%\] { background-position: 50% 10%; } `) From 3d2b6c6c3900c753e623ccfe38a4c0f30302eb91 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 29 Sep 2022 17:51:21 +0200 Subject: [PATCH 5/8] prefer `position` over `size` for backwards compatibility reasons Previously `bg-[10px_10%]` generated `background-position: 10px 10%` before we introduced the fallback plugins. Therefore we should prefer `position` over `size` as the default for backwards compatibility. --- src/corePlugins.js | 4 ++-- tests/arbitrary-values.test.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/corePlugins.js b/src/corePlugins.js index 16198f972b08..5cf27287128e 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -1482,7 +1482,7 @@ export let corePlugins = { }, backgroundSize: createUtilityPlugin('backgroundSize', [['bg', ['background-size']]], { - type: ['lookup', 'length', 'percentage', ['size', { preferOnConflict: true }]], + type: ['lookup', 'length', 'percentage', 'size'], }), backgroundAttachment: ({ addUtilities }) => { @@ -1503,7 +1503,7 @@ export let corePlugins = { }, backgroundPosition: createUtilityPlugin('backgroundPosition', [['bg', ['background-position']]], { - type: ['lookup', 'position'], + type: ['lookup', ['position', { preferOnConflict: true }]], }), backgroundRepeat: ({ addUtilities }) => { diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index 4e32a4d99b1a..58b6a6716f6d 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -285,7 +285,7 @@ it('should pick the fallback plugin when arbitrary values collide', () => { } .bg-\[200px_100px\] { - background-size: 200px 100px; + background-position: 200px 100px; } `) }) @@ -498,13 +498,13 @@ it('should correctly validate combination of percentage and length', () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .bg-\[50px_10\%\] { - background-size: 50px 10%; + background-position: 50px 10%; } .bg-\[50\%_10\%\] { - background-size: 50% 10%; + background-position: 50% 10%; } .bg-\[50px_10px\] { - background-size: 50px 10px; + background-position: 50px 10px; } `) }) @@ -525,12 +525,12 @@ it('can explicitly specify type for percentage and length', () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - .bg-\[50px_10px\] { - background-size: 50px 10px; - } .bg-\[size\:50px_10\%\] { background-size: 50px 10%; } + .bg-\[50px_10px\] { + background-position: 50px 10px; + } .bg-\[position\:50\%_10\%\] { background-position: 50% 10%; } From 748d3f9d58341fb30a1bc9db652765fc48fb935f Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 29 Sep 2022 17:54:19 +0200 Subject: [PATCH 6/8] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f909b3a25de..2fc1056168a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Revert change that only listened for stdin close on TTYs ([#9331](https://github.com/tailwindlabs/tailwindcss/pull/9331)) - Ignore unset values (like `null` or `undefined`) when resolving the classList for intellisense ([#9385](https://github.com/tailwindlabs/tailwindcss/pull/9385)) - Implement fallback plugins when arbitrary values result in css from multiple plugins ([#9376](https://github.com/tailwindlabs/tailwindcss/pull/9376)) +- Improve type checking for formal syntax ([#9349](https://github.com/tailwindlabs/tailwindcss/pull/9349)) ## [3.1.8] - 2022-08-05 From c93e7b8af50b6277cb0791aba8082b4b3bc53d73 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 29 Sep 2022 18:15:04 +0200 Subject: [PATCH 7/8] ensure correct order Thanks Prettier! --- tests/arbitrary-values.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index 58b6a6716f6d..da17a6cdf1aa 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -513,7 +513,7 @@ it('should correctly validate combination of percentage and length', () => { it('can explicitly specify type for percentage and length', () => { let config = { content: [ - { raw: html`
` }, + { raw: html`
` }, ], corePlugins: { preflight: false }, plugins: [], From e2a0623cea8ee78efd548e31d3f0bd1d91d7dd1e Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 29 Sep 2022 18:23:13 +0200 Subject: [PATCH 8/8] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc1056168a0..32dc8876f5f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Revert change that only listened for stdin close on TTYs ([#9331](https://github.com/tailwindlabs/tailwindcss/pull/9331)) - Ignore unset values (like `null` or `undefined`) when resolving the classList for intellisense ([#9385](https://github.com/tailwindlabs/tailwindcss/pull/9385)) - Implement fallback plugins when arbitrary values result in css from multiple plugins ([#9376](https://github.com/tailwindlabs/tailwindcss/pull/9376)) -- Improve type checking for formal syntax ([#9349](https://github.com/tailwindlabs/tailwindcss/pull/9349)) +- Improve type checking for formal syntax ([#9349](https://github.com/tailwindlabs/tailwindcss/pull/9349), [#9448](https://github.com/tailwindlabs/tailwindcss/pull/9448)) ## [3.1.8] - 2022-08-05