Skip to content

Commit de2e71b

Browse files
committed
fix: apply inside a nested structure
1 parent 8d244cc commit de2e71b

File tree

2 files changed

+153
-48
lines changed

2 files changed

+153
-48
lines changed

__tests__/applyComplexClasses.test.js

+98
Original file line numberDiff line numberDiff line change
@@ -996,3 +996,101 @@ test('you can apply classes to a rule with multiple selectors', () => {
996996
expect(result.warnings().length).toBe(0)
997997
})
998998
})
999+
1000+
test('you can apply classes in a nested rule', () => {
1001+
const input = `
1002+
.selector {
1003+
&:hover {
1004+
@apply text-white;
1005+
}
1006+
}
1007+
`
1008+
1009+
const expected = `
1010+
.selector {
1011+
&:hover {
1012+
--text-opacity: 1;
1013+
color: #fff;
1014+
color: rgba(255, 255, 255, var(--text-opacity));
1015+
}
1016+
}
1017+
`
1018+
1019+
return run(input).then(result => {
1020+
expect(result.css).toMatchCss(expected)
1021+
expect(result.warnings().length).toBe(0)
1022+
})
1023+
})
1024+
1025+
test('you can apply classes in a nested @atrule', () => {
1026+
const input = `
1027+
.selector {
1028+
@media (min-width: 200px) {
1029+
@apply overflow-hidden;
1030+
}
1031+
}
1032+
`
1033+
1034+
const expected = `
1035+
.selector {
1036+
@media (min-width: 200px) {
1037+
overflow: hidden;
1038+
}
1039+
}
1040+
`
1041+
1042+
return run(input).then(result => {
1043+
expect(result.css).toMatchCss(expected)
1044+
expect(result.warnings().length).toBe(0)
1045+
})
1046+
})
1047+
1048+
test('you can apply classes in a custom nested @atrule', () => {
1049+
const input = `
1050+
.selector {
1051+
@screen md {
1052+
@apply w-2/6;
1053+
}
1054+
}
1055+
`
1056+
1057+
const expected = `
1058+
.selector {
1059+
@media (min-width: 768px) {
1060+
width: 33.333333%;
1061+
}
1062+
}
1063+
`
1064+
1065+
return run(input).then(result => {
1066+
expect(result.css).toMatchCss(expected)
1067+
expect(result.warnings().length).toBe(0)
1068+
})
1069+
})
1070+
1071+
test('you can deeply apply classes in a custom nested @atrule', () => {
1072+
const input = `
1073+
.selector {
1074+
.subselector {
1075+
@screen md {
1076+
@apply w-2/6;
1077+
}
1078+
}
1079+
}
1080+
`
1081+
1082+
const expected = `
1083+
.selector {
1084+
.subselector {
1085+
@media (min-width: 768px) {
1086+
width: 33.333333%
1087+
}
1088+
}
1089+
}
1090+
`
1091+
1092+
return run(input).then(result => {
1093+
expect(result.css).toMatchCss(expected)
1094+
expect(result.warnings().length).toBe(0)
1095+
})
1096+
})

src/flagged/applyComplexClasses.js

+55-48
Original file line numberDiff line numberDiff line change
@@ -186,68 +186,75 @@ function makeExtractUtilityRules(css, lookupTree, config) {
186186
}
187187
}
188188

189+
function findParent(rule, predicate) {
190+
let parent = rule.parent
191+
while (parent) {
192+
if (predicate(parent)) {
193+
return parent
194+
}
195+
196+
parent = parent.parent
197+
}
198+
199+
throw new Error('No parent could be found')
200+
}
201+
189202
function processApplyAtRules(css, lookupTree, config) {
190203
const extractUtilityRules = makeExtractUtilityRules(css, lookupTree, config)
191204

192205
do {
193-
css.walkRules(rule => {
194-
const applyRules = []
206+
css.walkAtRules('apply', applyRule => {
207+
const parent = applyRule.parent // Direct parent
208+
const nearestParentRule = findParent(applyRule, r => r.type === 'rule')
209+
const currentUtilityNames = extractUtilityNames(nearestParentRule.selector)
210+
211+
const [
212+
importantEntries,
213+
applyUtilityNames,
214+
important = importantEntries.length > 0,
215+
] = _.partition(applyRule.params.split(/[\s\t\n]+/g), n => n === '!important')
216+
217+
if (_.intersection(applyUtilityNames, currentUtilityNames).length > 0) {
218+
const currentUtilityName = _.intersection(applyUtilityNames, currentUtilityNames)[0]
219+
throw parent.error(
220+
`You cannot \`@apply\` the \`${currentUtilityName}\` utility here because it creates a circular dependency.`
221+
)
222+
}
195223

196-
// Only walk direct children to avoid issues with nesting plugins
197-
rule.each(child => {
198-
if (child.type === 'atrule' && child.name === 'apply') {
199-
applyRules.unshift(child)
200-
}
201-
})
224+
// Extract any post-apply declarations and re-insert them after apply rules
225+
const afterRule = parent.clone({ raws: {} })
226+
afterRule.nodes = afterRule.nodes.slice(parent.index(applyRule) + 1)
227+
parent.nodes = parent.nodes.slice(0, parent.index(applyRule) + 1)
202228

203-
applyRules.forEach(applyRule => {
204-
const [
205-
importantEntries,
206-
applyUtilityNames,
207-
important = importantEntries.length > 0,
208-
] = _.partition(applyRule.params.split(/[\s\t\n]+/g), n => n === '!important')
229+
// Sort applys to match CSS source order
230+
const applys = extractUtilityRules(applyUtilityNames, applyRule)
209231

210-
const currentUtilityNames = extractUtilityNames(rule.selector)
232+
// Get new rules with the utility portion of the selector replaced with the new selector
233+
const rulesToInsert = []
211234

212-
if (_.intersection(applyUtilityNames, currentUtilityNames).length > 0) {
213-
const currentUtilityName = _.intersection(applyUtilityNames, currentUtilityNames)[0]
214-
throw rule.error(
215-
`You cannot \`@apply\` the \`${currentUtilityName}\` utility here because it creates a circular dependency.`
216-
)
217-
}
235+
applys.forEach(
236+
nearestParentRule === parent
237+
? util => rulesToInsert.push(generateRulesFromApply(util, parent.selectors))
238+
: util => util.rule.nodes.forEach(n => afterRule.append(n.clone()))
239+
)
218240

219-
// Extract any post-apply declarations and re-insert them after apply rules
220-
const afterRule = rule.clone({ raws: {} })
221-
afterRule.nodes = afterRule.nodes.slice(rule.index(applyRule) + 1)
222-
rule.nodes = rule.nodes.slice(0, rule.index(applyRule) + 1)
223-
224-
// Sort applys to match CSS source order
225-
const applys = extractUtilityRules(applyUtilityNames, applyRule)
226-
227-
// Get new rules with the utility portion of the selector replaced with the new selector
228-
const rulesToInsert = [
229-
...applys.map(applyUtility => {
230-
return generateRulesFromApply(applyUtility, rule.selectors)
231-
}),
232-
afterRule,
233-
]
234-
235-
const { nodes } = _.tap(postcss.root({ nodes: rulesToInsert }), root =>
236-
root.walkDecls(d => {
237-
d.important = important
238-
})
239-
)
241+
rulesToInsert.push(afterRule)
240242

241-
const mergedRules = mergeAdjacentRules(rule, nodes)
243+
const { nodes } = _.tap(postcss.root({ nodes: rulesToInsert }), root =>
244+
root.walkDecls(d => {
245+
d.important = important
246+
})
247+
)
242248

243-
applyRule.remove()
244-
rule.after(mergedRules)
245-
})
249+
const mergedRules = mergeAdjacentRules(nearestParentRule, nodes)
250+
251+
applyRule.remove()
252+
parent.after(mergedRules)
246253

247254
// If the base rule has nothing in it (all applys were pseudo or responsive variants),
248255
// remove the rule fuggit.
249-
if (rule.nodes.length === 0) {
250-
rule.remove()
256+
if (parent.nodes.length === 0) {
257+
parent.remove()
251258
}
252259
})
253260

0 commit comments

Comments
 (0)