|
1 | 1 | import postcss from 'postcss'
|
2 | 2 | import parser from 'postcss-selector-parser'
|
| 3 | + |
3 | 4 | import { resolveMatches } from './generateRules'
|
4 | 5 | import bigSign from '../util/bigSign'
|
5 | 6 | import escapeClassName from '../util/escapeClassName'
|
6 | 7 |
|
7 |
| -function containsBase(selector, classCandidateBase, separator) { |
8 |
| - return parser((selectors) => { |
9 |
| - let contains = false |
| 8 | +function extractClasses(node) { |
| 9 | + let classes = new Set() |
| 10 | + let container = postcss.root({ nodes: [node.clone()] }) |
10 | 11 |
|
11 |
| - selectors.walkClasses((classSelector) => { |
12 |
| - if (classSelector.value.split(separator).pop() === classCandidateBase) { |
13 |
| - contains = true |
14 |
| - return false |
15 |
| - } |
16 |
| - }) |
| 12 | + container.walkRules((rule) => { |
| 13 | + parser((selectors) => { |
| 14 | + selectors.walkClasses((classSelector) => { |
| 15 | + classes.add(classSelector.value) |
| 16 | + }) |
| 17 | + }).processSync(rule.selector) |
| 18 | + }) |
17 | 19 |
|
18 |
| - return contains |
19 |
| - }).transformSync(selector) |
| 20 | + return Array.from(classes) |
| 21 | +} |
| 22 | + |
| 23 | +function extractBaseCandidates(candidates, separator) { |
| 24 | + let baseClasses = new Set() |
| 25 | + |
| 26 | + for (let candidate of candidates) { |
| 27 | + baseClasses.add(candidate.split(separator).pop()) |
| 28 | + } |
| 29 | + |
| 30 | + return Array.from(baseClasses) |
20 | 31 | }
|
21 | 32 |
|
22 | 33 | function prefix(context, selector) {
|
@@ -212,15 +223,40 @@ function processApply(root, context) {
|
212 | 223 | let siblings = []
|
213 | 224 |
|
214 | 225 | for (let [applyCandidate, important, rules] of candidates) {
|
215 |
| - let base = applyCandidate.split(context.tailwindConfig.separator).pop() |
216 |
| - |
217 | 226 | for (let [meta, node] of rules) {
|
218 |
| - if ( |
219 |
| - containsBase(parent.selector, base, context.tailwindConfig.separator) && |
220 |
| - containsBase(node.selector, base, context.tailwindConfig.separator) |
221 |
| - ) { |
| 227 | + let parentClasses = extractClasses(parent) |
| 228 | + let nodeClasses = extractClasses(node) |
| 229 | + |
| 230 | + // Add base utility classes from the @apply node to the list of |
| 231 | + // classes to check whether it intersects and therefore results in a |
| 232 | + // circular dependency or not. |
| 233 | + // |
| 234 | + // E.g.: |
| 235 | + // .foo { |
| 236 | + // @apply hover:a; // This applies "a" but with a modifier |
| 237 | + // } |
| 238 | + // |
| 239 | + // We only have to do that with base classes of the `node`, not of the `parent` |
| 240 | + // E.g.: |
| 241 | + // .hover\:foo { |
| 242 | + // @apply bar; |
| 243 | + // } |
| 244 | + // .bar { |
| 245 | + // @apply foo; |
| 246 | + // } |
| 247 | + // |
| 248 | + // This should not result in a circular dependency because we are |
| 249 | + // just applying `.foo` and the rule above is `.hover\:foo` which is |
| 250 | + // unrelated. However, if we were to apply `hover:foo` then we _did_ |
| 251 | + // have to include this one. |
| 252 | + nodeClasses = nodeClasses.concat( |
| 253 | + extractBaseCandidates(nodeClasses, context.tailwindConfig.separator) |
| 254 | + ) |
| 255 | + |
| 256 | + let intersects = parentClasses.some((selector) => nodeClasses.includes(selector)) |
| 257 | + if (intersects) { |
222 | 258 | throw node.error(
|
223 |
| - `Circular dependency detected when using: \`@apply ${applyCandidate}\`` |
| 259 | + `You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.` |
224 | 260 | )
|
225 | 261 | }
|
226 | 262 |
|
@@ -250,7 +286,6 @@ function processApply(root, context) {
|
250 | 286 | // Inject the rules, sorted, correctly
|
251 | 287 | let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1])
|
252 | 288 |
|
253 |
| - // console.log(parent) |
254 | 289 | // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
|
255 | 290 | parent.after(nodes)
|
256 | 291 | }
|
|
0 commit comments