Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure @apply of a rule inside an AtRule works #6594

Merged
merged 2 commits into from Dec 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Only generate variants for non-`user` layers ([#6589](https://github.com/tailwindlabs/tailwindcss/pull/6589))
- Properly extract classes with arbitrary values in arrays and classes followed by escaped quotes ([#6590](https://github.com/tailwindlabs/tailwindcss/pull/6590))
- Improve jsx interpolation candidate matching ([#6593](https://github.com/tailwindlabs/tailwindcss/pull/6593))
- Ensure `@apply` of a rule inside an AtRule works ([#6594](https://github.com/tailwindlabs/tailwindcss/pull/6594))

## [3.0.6] - 2021-12-16

Expand Down
36 changes: 36 additions & 0 deletions src/lib/expandApplyAtRules.js
Expand Up @@ -266,6 +266,42 @@ function processApply(root, context) {

if (canRewriteSelector) {
root.walkRules((rule) => {
// Let's imagine you have the following structure:
//
// .foo {
// @apply bar;
// }
//
// @supports (a: b) {
// .bar {
// color: blue
// }
//
// .something-unrelated {}
// }
//
// In this case we want to apply `.bar` but it happens to be in
// an atrule node. We clone that node instead of the nested one
// because we still want that @supports rule to be there once we
// applied everything.
//
// However it happens to be that the `.something-unrelated` is
// also in that same shared @supports atrule. This is not good,
// and this should not be there. The good part is that this is
// a clone already and it can be safely removed. The question is
// how do we know we can remove it. Basically what we can do is
// match it against the applyCandidate that you want to apply. If
// it doesn't match the we can safely delete it.
//
// If we didn't do this, then the `replaceSelector` function
// would have replaced this with something that didn't exist and
// therefore it removed the selector altogether. In this specific
// case it would result in `{}` instead of `.something-unrelated {}`
if (!extractClasses(rule).some((thing) => thing === applyCandidate)) {
rule.remove()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RobinMalfait I'm not sure what exactly the issue is. I'll keep investigating.

My best guess is that something else is missing besides just removing the rule. Maybe the parent still has a reference to the rule which would cause it to be undefined?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I do the following, it no longer errors but I'm just shooting in the dark here...

rule.before(rule.nodes)
rule.remove();
return;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please open an issue with a reproduction.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return
}

rule.selector = replaceSelector(parent.selector, rule.selector, applyCandidate)

rule.walkDecls((d) => {
Expand Down
62 changes: 62 additions & 0 deletions tests/apply.test.js
Expand Up @@ -712,3 +712,65 @@ it('should not be possible to apply user css with variants', () => {
)
})
})

it('should not apply unrelated siblings when applying something from within atrules', () => {
let config = {
content: [{ raw: html`<div class="foo bar something-unrelated"></div>` }],
plugins: [],
}

let input = css`
@tailwind components;
@tailwind utilities;

@layer components {
.foo {
font-weight: bold;
@apply bar;
}

.bar {
color: green;
}

@supports (a: b) {
.bar {
color: blue;
}

.something-unrelated {
color: red;
}
}
}
`

return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.foo {
font-weight: bold;
color: green;
}

@supports (a: b) {
.foo {
color: blue;
}
}

.bar {
color: green;
}

@supports (a: b) {
.bar {
color: blue;
}

.something-unrelated {
color: red;
}
}
`)
})
})