Skip to content

Commit d352092

Browse files
authored
Fix nested tail ampersands (#116)
* Add a bunch of (partially failing) tests * Fix incorrect replacement of deeply nested tail ampersands * Adjust whitespace in test assertion I’d prefer the original version, but I understand why it doesn’t work atm and see no non-hackish way to change it
1 parent db5bce7 commit d352092

File tree

2 files changed

+49
-4
lines changed

2 files changed

+49
-4
lines changed

index.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ module.exports = (opts = {}) => {
132132

133133
return {
134134
postcssPlugin: 'postcss-nested',
135-
RuleExit (rule, { Rule }) {
135+
Rule (rule, { Rule }) {
136136
let unwrapped = false
137137
let after = rule
138138
let copyDeclarations = false
@@ -152,8 +152,6 @@ module.exports = (opts = {}) => {
152152
after.after(child)
153153
after = child
154154
} else if (child.type === 'atrule') {
155-
copyDeclarations = false
156-
157155
if (declarations.length) {
158156
after = pickDeclarations(rule.selector, declarations, after, Rule)
159157
declarations = []
@@ -172,17 +170,21 @@ module.exports = (opts = {}) => {
172170
after = nodes
173171
child.remove()
174172
} else if (bubble[child.name]) {
173+
copyDeclarations = true
175174
unwrapped = true
176175
atruleChilds(rule, child, true)
177176
after = pickComment(child.prev(), after)
178177
after.after(child)
179178
after = child
180179
} else if (unwrap[child.name]) {
180+
copyDeclarations = true
181181
unwrapped = true
182182
atruleChilds(rule, child, false)
183183
after = pickComment(child.prev(), after)
184184
after.after(child)
185185
after = child
186+
} else if (copyDeclarations) {
187+
declarations.push(child)
186188
}
187189
} else if (child.type === 'decl' && copyDeclarations) {
188190
declarations.push(child)

index.test.js

+44-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ it('unwrap rules inside at-rules', () => {
5555
it('unwraps at-rule', () => {
5656
run(
5757
'a { b { @media screen { width: auto } } }',
58-
'@media screen { a b { width: auto } }'
58+
'@media screen {a b { width: auto } }'
5959
)
6060
})
6161

@@ -87,6 +87,13 @@ it('unwraps at-rules', () => {
8787
)
8888
})
8989

90+
it('unwraps at-rules with interleaved properties', () => {
91+
run(
92+
'a { a: 1 } a { color: red; @media screen { @supports (a: 1) { a: 1 } } background: green }',
93+
'a { a: 1 } a { color: red; } @media screen { @supports (a: 1) { a { a: 1 } } } a { background: green }'
94+
)
95+
})
96+
9097
it('do not move custom at-rules', () => {
9198
run(
9299
'.one { @mixin test; } .two { @phone { color: black } }',
@@ -171,6 +178,25 @@ it('replaces ampersands in not selector', () => {
171178
run('.a { &:not(&.no) {} }', '.a:not(.a.no) {}')
172179
})
173180

181+
it('correctly replaces tail ampersands', () => {
182+
run('.a { .b & {} }', '.b .a {}')
183+
})
184+
185+
it('correctly replaces tail ampersands that are nested further down', () => {
186+
run('.a { .b { .c & {} } }', '.c .a .b {}')
187+
})
188+
189+
it('correctly replaces tail ampersands that are nested inside ampersand rules', () => {
190+
run('.a { &:hover { .b { .c & {} } } }', '.c .a:hover .b {}')
191+
})
192+
193+
it('preserves child order when replacing tail ampersands', () => {
194+
run(
195+
'.a { color: red; .first {} @mixinFirst; .b & {} @mixinLast; .last {} }',
196+
'.a { color: red; } .a .first {} .a { @mixinFirst; } .b .a {} .a { @mixinLast; } .a .last {}'
197+
)
198+
})
199+
174200
it('handles :host selector case', () => {
175201
run(':host { &(:focus) {} }', ':host(:focus) {}')
176202
})
@@ -192,6 +218,23 @@ it('works with other visitors', () => {
192218
expect(out).toEqual('a b{color:red}a .in .deep{color:blue}')
193219
})
194220

221+
it('works with other visitors #2', () => {
222+
let css = 'a { @mixin; b {color:red} }'
223+
let mixinPlugin = () => {
224+
return {
225+
postcssPlugin: 'mixin',
226+
AtRule: {
227+
mixin (node) {
228+
node.replaceWith('.in { .deep {color:blue} }')
229+
}
230+
}
231+
}
232+
}
233+
mixinPlugin.postcss = true
234+
let out = postcss([plugin, mixinPlugin]).process(css, { from: undefined }).css
235+
expect(out).toEqual('a .in .deep {color:blue} a b {color:red}')
236+
})
237+
195238
it('shows clear errors on missed semicolon', () => {
196239
let css = 'a{\n color: black\n @mixin b { }\n}\n'
197240
expect(() => {

0 commit comments

Comments
 (0)