From dc18f9d14da7e841e54fa855ea58e79181bbeb52 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 7 Feb 2022 23:28:36 -0500 Subject: [PATCH] Fix wildcard duplication issue This would be better as a symbol but the stringy-ness of class candidates is fairly well baked into assumptions across the codebase. new String + a well placed check seems to solve the problem --- CHANGELOG.md | 1 + src/lib/expandTailwindAtRules.js | 2 +- src/lib/generateRules.js | 5 +++ src/lib/setupContextUtils.js | 6 +-- src/lib/sharedState.js | 1 + tests/basic-usage.test.js | 63 +++++++++++++++++++++++++++++++- 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 974e95fc4e68..c2b40ae4f703 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove opacity variables from `:visited` pseudo class ([#7458](https://github.com/tailwindlabs/tailwindcss/pull/7458)) - Support arbitrary values + calc + theme with quotes ([#7462](https://github.com/tailwindlabs/tailwindcss/pull/7462)) +- Don't duplicate layer output when scanning content with variants + wildcards ([#7478](https://github.com/tailwindlabs/tailwindcss/pull/7478)) ## [3.0.22] - 2022-02-11 diff --git a/src/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js index eb515f6fc81d..0c48e97202a0 100644 --- a/src/lib/expandTailwindAtRules.js +++ b/src/lib/expandTailwindAtRules.js @@ -158,7 +158,7 @@ export default function expandTailwindAtRules(context) { // --- // Find potential rules in changed files - let candidates = new Set(['*']) + let candidates = new Set([sharedState.NOT_ON_DEMAND]) let seen = new Set() env.DEBUG && console.time('Reading changed files') diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index c3385403ff30..40322cd15d53 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -5,6 +5,7 @@ import isPlainObject from '../util/isPlainObject' import prefixSelector from '../util/prefixSelector' import { updateAllClasses } from '../util/pluginUtils' import log from '../util/log' +import * as sharedState from './sharedState' import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector' import { asClass } from '../util/nameClass' import { normalize } from '../util/dataTypes' @@ -382,6 +383,10 @@ function* resolveMatchedPlugins(classCandidate, context) { } function splitWithSeparator(input, separator) { + if (input === sharedState.NOT_ON_DEMAND) { + return [sharedState.NOT_ON_DEMAND] + } + return input.split(new RegExp(`\\${separator}(?![^[]*\\])`, 'g')) } diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index beea5b47d34b..94128c2d26da 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -138,7 +138,7 @@ function withIdentifiers(styles) { // If this isn't "on-demandable", assign it a universal candidate to always include it. if (containsNonOnDemandableSelectors) { - candidates.unshift('*') + candidates.unshift(sharedState.NOT_ON_DEMAND) } // However, it could be that it also contains "on-demandable" candidates. @@ -163,8 +163,8 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs } function prefixIdentifier(identifier, options) { - if (identifier === '*') { - return '*' + if (identifier === sharedState.NOT_ON_DEMAND) { + return sharedState.NOT_ON_DEMAND } if (!options.respectPrefix) { diff --git a/src/lib/sharedState.js b/src/lib/sharedState.js index f5d640238eb0..e0cfe9f3f653 100644 --- a/src/lib/sharedState.js +++ b/src/lib/sharedState.js @@ -5,6 +5,7 @@ export const env = { export const contextMap = new Map() export const configContextMap = new Map() export const contextSourcesMap = new Map() +export const NOT_ON_DEMAND = new String('*') export function resolveDebug(debug) { if (debug === undefined) { diff --git a/tests/basic-usage.test.js b/tests/basic-usage.test.js index ca6be7af6580..a26796ccc15a 100644 --- a/tests/basic-usage.test.js +++ b/tests/basic-usage.test.js @@ -1,7 +1,7 @@ import fs from 'fs' import path from 'path' -import { html, run, css } from './util/run' +import { html, run, css, defaults } from './util/run' test('basic usage', () => { let config = { @@ -188,3 +188,64 @@ it('can scan extremely long classes without crashing', () => { expect(result.css).toMatchFormattedCss(css``) }) }) + +it('does not produce duplicate output when seeing variants preceding a wildcard (*)', () => { + let config = { + content: [{ raw: html`underline focus:*` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + + * { + color: red; + } + + .combined, + * { + text-align: center; + } + + @layer base { + * { + color: blue; + } + + .combined, + * { + color: red; + } + } + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + * { + color: blue; + } + + .combined, + * { + color: red; + } + + ${defaults} + + .underline { + text-decoration-line: underline; + } + + * { + color: red; + } + + .combined, + * { + text-align: center; + } + `) + }) +})