From 8ba5c786238af86db74843c992212b7a9983c373 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 25 Mar 2024 13:39:40 +0100 Subject: [PATCH 01/10] implement `@apply ` --- packages/tailwindcss/src/index.test.ts | 64 ++++++++++++++++++++++++++ packages/tailwindcss/src/index.ts | 58 ++++++++++++++++++++++- 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 05e4213c0f6b..e5827f592a0a 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -308,6 +308,70 @@ describe('@apply', () => { }" `) }) + + it('should be possible to apply user-defined CSS', () => { + expect( + compileCss(css` + @theme { + --spacing-2: 0.5rem; + --spacing-3: 0.75rem; + --color-red-600: #e53e3e; + } + + .btn { + @apply py-2 px-3; + } + + .btn-red { + @apply btn bg-red-600; + } + `), + ).toMatchInlineSnapshot(` + ":root { + --spacing-2: .5rem; + --spacing-3: .75rem; + --color-red-600: #e53e3e; + } + + .btn { + padding-top: var(--spacing-2, .5rem); + padding-bottom: var(--spacing-2, .5rem); + padding-left: var(--spacing-3, .75rem); + padding-right: var(--spacing-3, .75rem); + } + + .btn-red { + padding-top: var(--spacing-2, .5rem); + padding-bottom: var(--spacing-2, .5rem); + padding-left: var(--spacing-3, .75rem); + padding-right: var(--spacing-3, .75rem); + background-color: var(--color-red-600, #e53e3e); + }" + `) + }) + + it('should apply user-defined CSS that happens to be a utility class', () => { + expect( + compileCss(css` + .flex { + --display-mode: flex; + } + + .example { + @apply flex; + } + `), + ).toMatchInlineSnapshot(` + ".flex { + --display-mode: flex; + } + + .example { + --display-mode: flex; + display: flex; + }" + `) + }) }) describe('arbitrary variants', () => { diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 34a095584944..25755d6690cb 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -20,6 +20,10 @@ export function compile(css: string): { invalidCandidates.add(candidate) } + // Track `@apply` information + let containsAtApply = css.includes('@apply') + let applyables = new Map() + // Find all `@theme` declarations let theme = new Theme() let firstThemeRule: Rule | null = null @@ -28,6 +32,25 @@ export function compile(css: string): { walk(ast, (node, { replaceWith }) => { if (node.kind !== 'rule') return + // Track all user-defined classes for `@apply` support + if (containsAtApply && node.selector[0] === '.' && !node.selector.includes(' ')) { + // Convert the class `.foo` into a candidate `foo` + let candidate = node.selector.slice(1) + + // It could be that multiple definitions exist for the same class, so we + // need to track all of them. + let nodes = applyables.get(candidate) ?? [] + + // Add all children of the current rule to the list of nodes for the + // current candidate. + for (let child of node.nodes) { + nodes.push(child) + } + + // Store the list of nodes for the current candidate + applyables.set(candidate, nodes) + } + // Drop instances of `@media reference` // // We support `@import "tailwindcss/theme" reference` as a way to import an external theme file @@ -143,7 +166,7 @@ export function compile(css: string): { }) // Replace `@apply` rules with the actual utility classes. - if (css.includes('@apply')) { + if (containsAtApply) { walk(ast, (node, { replaceWith }) => { if (node.kind === 'rule' && node.selector[0] === '@' && node.selector.startsWith('@apply')) { let candidates = node.selector @@ -153,9 +176,41 @@ export function compile(css: string): { // Replace the `@apply` rule with the actual utility classes { + let newNodes: AstNode[] = [] + + // Collect all user-defined classes for the current candidates that we + // need to apply. + for (let candidate of candidates) { + let nodes = applyables.get(candidate) + if (!nodes) continue + + for (let child of nodes) { + newNodes.push(structuredClone(child)) + } + } + // Parse the candidates to an AST that we can replace the `@apply` rule with. let candidateAst = compileCandidates(candidates, designSystem, { onInvalidCandidate: (candidate) => { + // When a candidate is invalid, we want to first verify that the + // candidate is a user-defined class or not. If it is, then we can + // safely ignore this. If it's not, then we throw an error because + // the candidate is unknown. + // + // The reason we even have to check user-defined classes is + // because it could be that the user defined CSS like that is also + // a known utility class. For example, the following CSS would be: + // + // ```css + // .flex { + // --display-mode: flex; + // } + // ``` + // + // When the user then uses `@apply flex`, we want to both apply + // the user-defined class and the utility class. + if (applyables.has(candidate)) return + throw new Error(`Cannot apply unknown utility class: ${candidate}`) }, }).astNodes @@ -163,7 +218,6 @@ export function compile(css: string): { // Collect the nodes to insert in place of the `@apply` rule. When a // rule was used, we want to insert its children instead of the rule // because we don't want the wrapping selector. - let newNodes: AstNode[] = [] for (let candidateNode of candidateAst) { if (candidateNode.kind === 'rule' && candidateNode.selector[0] !== '@') { for (let child of candidateNode.nodes) { From 6488984f00c2b046fcc0b0fcdcbb8bdc9c9338b6 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 25 Mar 2024 16:50:18 +0100 Subject: [PATCH 02/10] improve variable name --- packages/tailwindcss/src/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 25755d6690cb..b7165b3b4213 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -22,7 +22,7 @@ export function compile(css: string): { // Track `@apply` information let containsAtApply = css.includes('@apply') - let applyables = new Map() + let userDefinedApplyables = new Map() // Find all `@theme` declarations let theme = new Theme() @@ -39,7 +39,7 @@ export function compile(css: string): { // It could be that multiple definitions exist for the same class, so we // need to track all of them. - let nodes = applyables.get(candidate) ?? [] + let nodes = userDefinedApplyables.get(candidate) ?? [] // Add all children of the current rule to the list of nodes for the // current candidate. @@ -48,7 +48,7 @@ export function compile(css: string): { } // Store the list of nodes for the current candidate - applyables.set(candidate, nodes) + userDefinedApplyables.set(candidate, nodes) } // Drop instances of `@media reference` @@ -181,7 +181,7 @@ export function compile(css: string): { // Collect all user-defined classes for the current candidates that we // need to apply. for (let candidate of candidates) { - let nodes = applyables.get(candidate) + let nodes = userDefinedApplyables.get(candidate) if (!nodes) continue for (let child of nodes) { @@ -209,7 +209,7 @@ export function compile(css: string): { // // When the user then uses `@apply flex`, we want to both apply // the user-defined class and the utility class. - if (applyables.has(candidate)) return + if (userDefinedApplyables.has(candidate)) return throw new Error(`Cannot apply unknown utility class: ${candidate}`) }, From 0906a55f4b366294e4c14b3f78c0d0dd34256959 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 25 Mar 2024 17:11:06 +0100 Subject: [PATCH 03/10] validate the selector --- packages/tailwindcss/src/index.ts | 11 ++++- .../utils/is-simple-class-selector.test.ts | 42 +++++++++++++++++++ .../src/utils/is-simple-class-selector.ts | 26 ++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 packages/tailwindcss/src/utils/is-simple-class-selector.test.ts create mode 100644 packages/tailwindcss/src/utils/is-simple-class-selector.ts diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index b7165b3b4213..97900aabe690 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -4,6 +4,7 @@ import { compileCandidates } from './compile' import * as CSS from './css-parser' import { buildDesignSystem } from './design-system' import { Theme } from './theme' +import { isSimpleClassSelector } from './utils/is-simple-class-selector' export function compile(css: string): { build(candidates: string[]): string @@ -33,7 +34,15 @@ export function compile(css: string): { if (node.kind !== 'rule') return // Track all user-defined classes for `@apply` support - if (containsAtApply && node.selector[0] === '.' && !node.selector.includes(' ')) { + if ( + containsAtApply && + // Verify that it is a valid applyable-class. An applyable class is a + // class that is a very simple selector, like `.foo` or `.bar`, but doesn't + // contain any spaces, combinators, pseudo-selectors, pseudo-elements, or + // attribute selectors. + node.selector[0] === '.' && + isSimpleClassSelector(node.selector) + ) { // Convert the class `.foo` into a candidate `foo` let candidate = node.selector.slice(1) diff --git a/packages/tailwindcss/src/utils/is-simple-class-selector.test.ts b/packages/tailwindcss/src/utils/is-simple-class-selector.test.ts new file mode 100644 index 000000000000..8dd854c23144 --- /dev/null +++ b/packages/tailwindcss/src/utils/is-simple-class-selector.test.ts @@ -0,0 +1,42 @@ +import { expect, it } from 'vitest' +import { isSimpleClassSelector } from './is-simple-class-selector' + +it.each([ + // Simple class selector + ['.foo', true], + + // Class selectors with escaped characters + ['.w-\\[123px\\]', true], + ['.content-\\[\\+\\>\\~\\*\\]', true], + + // ID selector + ['#foo', false], + ['.foo#foo', false], + + // Element selector + ['h1', false], + ['h1.foo', false], + + // Attribute selector + ['[data-foo]', false], + ['.foo[data-foo]', false], + ['[data-foo].foo', false], + + // Pseudo-class selector + ['.foo:hover', false], + + // Additional class selector + ['.foo.bar', false], + + // Combinator + ['.foo>.bar', false], + ['.foo+.bar', false], + ['.foo~.bar', false], + ['.foo .bar', false], + + // Selector list + ['.foo, .bar', false], + ['.foo,.bar', false], +])('should validate %s', (selector, expected) => { + expect(isSimpleClassSelector(selector)).toBe(expected) +}) diff --git a/packages/tailwindcss/src/utils/is-simple-class-selector.ts b/packages/tailwindcss/src/utils/is-simple-class-selector.ts new file mode 100644 index 000000000000..015562bb75de --- /dev/null +++ b/packages/tailwindcss/src/utils/is-simple-class-selector.ts @@ -0,0 +1,26 @@ +export function isSimpleClassSelector(selector: string): boolean { + // The selector must start with a dot, otherwise it's not a class selector. + if (selector[0] !== '.') return false + + for (let i = 1; i < selector.length; i++) { + switch (selector[i]) { + // The character is escaped, skip the next character + case '\\': + i += 1 + continue + + case ' ': // Descendat combinator + case '.': // Class selector + case '#': // ID selector + case '[': // Attribute selector + case ':': // Pseudo-classes and pseudo-elements + case '>': // Child combinator + case '+': // Next-sibling combinator + case '~': // Subsequent-sibling combinator + case ',': // Selector list + return false + } + } + + return true +} From 3f50c0626709ba4a8c7c530f35e4aded23659da6 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 25 Mar 2024 17:17:47 +0100 Subject: [PATCH 04/10] add docblock --- packages/tailwindcss/src/utils/is-simple-class-selector.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/tailwindcss/src/utils/is-simple-class-selector.ts b/packages/tailwindcss/src/utils/is-simple-class-selector.ts index 015562bb75de..6f43fb61c3c6 100644 --- a/packages/tailwindcss/src/utils/is-simple-class-selector.ts +++ b/packages/tailwindcss/src/utils/is-simple-class-selector.ts @@ -1,3 +1,10 @@ +/** + * Check if a selector is a simple class selector. + * + * A simple class selector is a class selector that doesn't contain any other + * selector types, such as ID selectors, element selectors, attribute selectors, + * pseudo-classes, combinators, or selector lists. + */ export function isSimpleClassSelector(selector: string): boolean { // The selector must start with a dot, otherwise it's not a class selector. if (selector[0] !== '.') return false From dd011057ba8696f9542ad7ecb99781cc1f93d4d5 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 25 Mar 2024 17:24:42 +0100 Subject: [PATCH 05/10] add more apply tests --- packages/tailwindcss/src/index.test.ts | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index e5827f592a0a..7d01dc36d587 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -372,6 +372,55 @@ describe('@apply', () => { }" `) }) + + it('should apply user-defined CSS that is defined after where the `@apply` is used', () => { + expect( + compileCss(css` + .example { + @apply foo; + } + + .foo { + color: red; + } + `), + ).toMatchInlineSnapshot(` + ".example, .foo { + color: red; + }" + `) + }) + + it('should apply user-defined CSS that is defined multiple times', () => { + expect( + compileCss(css` + .foo { + color: red; + } + + .example { + @apply foo; + } + + .foo { + background-color: blue; + } + `), + ).toMatchInlineSnapshot(` + ".foo { + color: red; + } + + .example { + color: red; + background-color: #00f; + } + + .foo { + background-color: #00f; + }" + `) + }) }) describe('arbitrary variants', () => { From 80a32e95bf530a24067f9abdc0be0ada76193df3 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 25 Mar 2024 19:14:41 +0100 Subject: [PATCH 06/10] detect circular `@apply` calls --- packages/tailwindcss/src/index.test.ts | 42 ++++++++ packages/tailwindcss/src/index.ts | 134 ++++++++++++++----------- 2 files changed, 118 insertions(+), 58 deletions(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 7d01dc36d587..c701aba80a3b 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -421,6 +421,48 @@ describe('@apply', () => { }" `) }) + + it('should error when circular @apply is used', () => { + expect(() => + compileCss(css` + .foo { + @apply bar; + } + + .bar { + @apply baz; + } + + .baz { + @apply foo; + } + `), + ).toThrowErrorMatchingInlineSnapshot(`[Error: Detected circular \`@apply\`: foo]`) + }) + + it('should error when circular @apply is used but nested', () => { + expect(() => + compileCss(css` + .foo { + &:hover { + @apply bar; + } + } + + .bar { + &:hover { + @apply baz; + } + } + + .baz { + &:hover { + @apply foo; + } + } + `), + ).toThrowErrorMatchingInlineSnapshot(`[Error: Detected circular \`@apply\`: foo]`) + }) }) describe('arbitrary variants', () => { diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 97900aabe690..02b7cac0ba6e 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -176,70 +176,88 @@ export function compile(css: string): { // Replace `@apply` rules with the actual utility classes. if (containsAtApply) { - walk(ast, (node, { replaceWith }) => { - if (node.kind === 'rule' && node.selector[0] === '@' && node.selector.startsWith('@apply')) { - let candidates = node.selector - .slice(7 /* Ignore `@apply ` when parsing the selector */) - .trim() - .split(/\s+/g) - - // Replace the `@apply` rule with the actual utility classes - { - let newNodes: AstNode[] = [] - - // Collect all user-defined classes for the current candidates that we - // need to apply. - for (let candidate of candidates) { - let nodes = userDefinedApplyables.get(candidate) - if (!nodes) continue - - for (let child of nodes) { - newNodes.push(structuredClone(child)) + walk(ast, (root) => { + if (root.kind !== 'rule') return WalkAction.Continue + + let rootAsCandidate = root.selector.slice(1) + + walk(root.nodes, (node, { replaceWith }) => { + if ( + node.kind === 'rule' && + node.selector[0] === '@' && + node.selector.startsWith('@apply') + ) { + let candidates = node.selector + .slice(7 /* Ignore `@apply ` when parsing the selector */) + .trim() + .split(/\s+/g) + + // Replace the `@apply` rule with the actual utility classes + { + let newNodes: AstNode[] = [] + + // Collect all user-defined classes for the current candidates that + // we need to apply. + for (let candidate of candidates) { + if (candidate === rootAsCandidate) { + throw new Error(`Detected circular \`@apply\`: ${candidate}`) + } + + let nodes = userDefinedApplyables.get(candidate) + if (!nodes) continue + + for (let child of nodes) { + newNodes.push(structuredClone(child)) + } } - } - // Parse the candidates to an AST that we can replace the `@apply` rule with. - let candidateAst = compileCandidates(candidates, designSystem, { - onInvalidCandidate: (candidate) => { - // When a candidate is invalid, we want to first verify that the - // candidate is a user-defined class or not. If it is, then we can - // safely ignore this. If it's not, then we throw an error because - // the candidate is unknown. - // - // The reason we even have to check user-defined classes is - // because it could be that the user defined CSS like that is also - // a known utility class. For example, the following CSS would be: - // - // ```css - // .flex { - // --display-mode: flex; - // } - // ``` - // - // When the user then uses `@apply flex`, we want to both apply - // the user-defined class and the utility class. - if (userDefinedApplyables.has(candidate)) return - - throw new Error(`Cannot apply unknown utility class: ${candidate}`) - }, - }).astNodes - - // Collect the nodes to insert in place of the `@apply` rule. When a - // rule was used, we want to insert its children instead of the rule - // because we don't want the wrapping selector. - for (let candidateNode of candidateAst) { - if (candidateNode.kind === 'rule' && candidateNode.selector[0] !== '@') { - for (let child of candidateNode.nodes) { - newNodes.push(child) + // Parse the candidates to an AST that we can replace the `@apply` + // rule with. + let candidateAst = compileCandidates(candidates, designSystem, { + onInvalidCandidate: (candidate) => { + // When a candidate is invalid, we want to first verify that the + // candidate is a user-defined class or not. If it is, then we + // can safely ignore this. If it's not, then we throw an error + // because the candidate is unknown. + // + // The reason we even have to check user-defined classes is + // because it could be that the user defined CSS like that is + // also a known utility class. For example, the following CSS + // would be: + // + // ```css + // .flex { + // --display-mode: flex; + // } + // ``` + // + // When the user then uses `@apply flex`, we want to both apply + // the user-defined class and the utility class. + if (userDefinedApplyables.has(candidate)) return + + throw new Error(`Cannot apply unknown utility class: ${candidate}`) + }, + }).astNodes + + // Collect the nodes to insert in place of the `@apply` rule. When a + // rule was used, we want to insert its children instead of the rule + // because we don't want the wrapping selector. + for (let candidateNode of candidateAst) { + if (candidateNode.kind === 'rule' && candidateNode.selector[0] !== '@') { + for (let child of candidateNode.nodes) { + newNodes.push(child) + } + } else { + newNodes.push(candidateNode) } - } else { - newNodes.push(candidateNode) } - } - replaceWith(newNodes) + replaceWith(newNodes) + } } - } + }) + + return WalkAction.Skip }) } From c7a13086db8ebe7ebce664bb9f82472b49767771 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 25 Mar 2024 19:15:45 +0100 Subject: [PATCH 07/10] adjust comment --- packages/tailwindcss/src/index.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 02b7cac0ba6e..5007b11e1792 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -215,15 +215,13 @@ export function compile(css: string): { // rule with. let candidateAst = compileCandidates(candidates, designSystem, { onInvalidCandidate: (candidate) => { - // When a candidate is invalid, we want to first verify that the - // candidate is a user-defined class or not. If it is, then we - // can safely ignore this. If it's not, then we throw an error - // because the candidate is unknown. + // We must pass in user-defined classes and then filter them out + // here because, while they are usually not known utilities, the + // user can define a class that happens to *also* be a known + // utility. // - // The reason we even have to check user-defined classes is - // because it could be that the user defined CSS like that is - // also a known utility class. For example, the following CSS - // would be: + // For example, given the following, `flex` counts as both a + // user-defined class and a known utility: // // ```css // .flex { From 52f5ef9468510a1f1dbf7af61f4df0b4f6ab68ee Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 25 Mar 2024 19:21:31 +0100 Subject: [PATCH 08/10] add more comments --- packages/tailwindcss/src/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 5007b11e1792..498d531e7c0d 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -179,6 +179,13 @@ export function compile(css: string): { walk(ast, (root) => { if (root.kind !== 'rule') return WalkAction.Continue + // It's possible to `@apply` user-defined classes. We need to make sure + // that we never run into a situation where we are eventually applying + // the same class that we are currently processing otherwise we will end + // up in an infinite loop (circular dependency). + // + // This means that we need to track the current node as a candidate and + // error when we encounter it again. let rootAsCandidate = root.selector.slice(1) walk(root.nodes, (node, { replaceWith }) => { @@ -199,6 +206,8 @@ export function compile(css: string): { // Collect all user-defined classes for the current candidates that // we need to apply. for (let candidate of candidates) { + // If the candidate is the same as the current node we are + // processing, we have a circular dependency. if (candidate === rootAsCandidate) { throw new Error(`Detected circular \`@apply\`: ${candidate}`) } From a3e3be5c68bef60eaf03f57e8b652eeba51c7fe5 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 25 Mar 2024 19:22:20 +0100 Subject: [PATCH 09/10] update error message to match v3 error message --- packages/tailwindcss/src/index.test.ts | 4 ++-- packages/tailwindcss/src/index.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index c701aba80a3b..c3ab7ed86675 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -437,7 +437,7 @@ describe('@apply', () => { @apply foo; } `), - ).toThrowErrorMatchingInlineSnapshot(`[Error: Detected circular \`@apply\`: foo]`) + ).toThrowErrorMatchingInlineSnapshot(`[Error: You cannot \`@apply\` the \`foo\` utility here because it creates a circular dependency.]`) }) it('should error when circular @apply is used but nested', () => { @@ -461,7 +461,7 @@ describe('@apply', () => { } } `), - ).toThrowErrorMatchingInlineSnapshot(`[Error: Detected circular \`@apply\`: foo]`) + ).toThrowErrorMatchingInlineSnapshot(`[Error: You cannot \`@apply\` the \`foo\` utility here because it creates a circular dependency.]`) }) }) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 498d531e7c0d..1e7288b3b948 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -209,7 +209,9 @@ export function compile(css: string): { // If the candidate is the same as the current node we are // processing, we have a circular dependency. if (candidate === rootAsCandidate) { - throw new Error(`Detected circular \`@apply\`: ${candidate}`) + throw new Error( + `You cannot \`@apply\` the \`${candidate}\` utility here because it creates a circular dependency.`, + ) } let nodes = userDefinedApplyables.get(candidate) From 4ecb61b7a87202a3da939548dbda44b606795576 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 25 Mar 2024 19:41:46 +0100 Subject: [PATCH 10/10] run prettier --- packages/tailwindcss/src/index.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index c3ab7ed86675..7cae2182a884 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -437,7 +437,9 @@ describe('@apply', () => { @apply foo; } `), - ).toThrowErrorMatchingInlineSnapshot(`[Error: You cannot \`@apply\` the \`foo\` utility here because it creates a circular dependency.]`) + ).toThrowErrorMatchingInlineSnapshot( + `[Error: You cannot \`@apply\` the \`foo\` utility here because it creates a circular dependency.]`, + ) }) it('should error when circular @apply is used but nested', () => { @@ -461,7 +463,9 @@ describe('@apply', () => { } } `), - ).toThrowErrorMatchingInlineSnapshot(`[Error: You cannot \`@apply\` the \`foo\` utility here because it creates a circular dependency.]`) + ).toThrowErrorMatchingInlineSnapshot( + `[Error: You cannot \`@apply\` the \`foo\` utility here because it creates a circular dependency.]`, + ) }) })