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:
Robin Malfait 2021-12-10 16:08:45 +01:00 committed by GitHub
parent 838185bd0e
commit 08241c3f75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 140 additions and 0 deletions

View File

@ -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

View File

@ -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')

View File

@ -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`')
})
})