mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Detect circular dependencies when using @apply (#6365)
* detect circular dependencies when using `@apply` * update changelog * ensure we split by the separator
This commit is contained in:
parent
838185bd0e
commit
08241c3f75
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Ensure complex variants with multiple classes work ([#6311](https://github.com/tailwindlabs/tailwindcss/pull/6311))
|
||||
- Re-add `default` interop to public available functions ([#6348](https://github.com/tailwindlabs/tailwindcss/pull/6348))
|
||||
- Detect circular dependencies when using `@apply` ([#6365](https://github.com/tailwindlabs/tailwindcss/pull/6365))
|
||||
|
||||
## [3.0.0] - 2021-12-09
|
||||
|
||||
|
||||
@ -1,8 +1,24 @@
|
||||
import postcss from 'postcss'
|
||||
import parser from 'postcss-selector-parser'
|
||||
import { resolveMatches } from './generateRules'
|
||||
import bigSign from '../util/bigSign'
|
||||
import escapeClassName from '../util/escapeClassName'
|
||||
|
||||
function containsBase(selector, classCandidateBase, separator) {
|
||||
return parser((selectors) => {
|
||||
let contains = false
|
||||
|
||||
selectors.walkClasses((classSelector) => {
|
||||
if (classSelector.value.split(separator).pop() === classCandidateBase) {
|
||||
contains = true
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
return contains
|
||||
}).transformSync(selector)
|
||||
}
|
||||
|
||||
function prefix(context, selector) {
|
||||
let prefix = context.tailwindConfig.prefix
|
||||
return typeof prefix === 'function' ? prefix(selector) : prefix + selector
|
||||
@ -196,7 +212,18 @@ function processApply(root, context) {
|
||||
let siblings = []
|
||||
|
||||
for (let [applyCandidate, important, rules] of candidates) {
|
||||
let base = applyCandidate.split(context.tailwindConfig.separator).pop()
|
||||
|
||||
for (let [meta, node] of rules) {
|
||||
if (
|
||||
containsBase(parent.selector, base, context.tailwindConfig.separator) &&
|
||||
containsBase(node.selector, base, context.tailwindConfig.separator)
|
||||
) {
|
||||
throw node.error(
|
||||
`Circular dependency detected when using: \`@apply ${applyCandidate}\``
|
||||
)
|
||||
}
|
||||
|
||||
let root = postcss.root({ nodes: [node.clone()] })
|
||||
let canRewriteSelector =
|
||||
node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
|
||||
|
||||
@ -465,3 +465,115 @@ it('should apply all the definitions of a class', () => {
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw when trying to apply a direct circular dependency', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="foo"></div>` }],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.foo:not(.text-red-500) {
|
||||
@apply text-red-500;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).catch((err) => {
|
||||
expect(err.reason).toBe('Circular dependency detected when using: `@apply text-red-500`')
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw when trying to apply an indirect circular dependency', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="a"></div>` }],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.a {
|
||||
@apply b;
|
||||
}
|
||||
|
||||
.b {
|
||||
@apply c;
|
||||
}
|
||||
|
||||
.c {
|
||||
@apply a;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).catch((err) => {
|
||||
expect(err.reason).toBe('Circular dependency detected when using: `@apply a`')
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw when trying to apply an indirect circular dependency with a modifier (1)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="a"></div>` }],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.a {
|
||||
@apply b;
|
||||
}
|
||||
|
||||
.b {
|
||||
@apply c;
|
||||
}
|
||||
|
||||
.c {
|
||||
@apply hover:a;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).catch((err) => {
|
||||
expect(err.reason).toBe('Circular dependency detected when using: `@apply hover:a`')
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw when trying to apply an indirect circular dependency with a modifier (2)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="a"></div>` }],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.a {
|
||||
@apply b;
|
||||
}
|
||||
|
||||
.b {
|
||||
@apply hover:c;
|
||||
}
|
||||
|
||||
.c {
|
||||
@apply a;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).catch((err) => {
|
||||
expect(err.reason).toBe('Circular dependency detected when using: `@apply a`')
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user