From 0c18cbc9ef36f39f7c906ed02cfd37f2bf77d49d Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Tue, 28 Jul 2026 18:05:41 -0700 Subject: [PATCH] fix: stop treating the child combinator as a prelude boundary when pruning removeSelectorAt walked backward to find the rule prelude and stopped at any '>', added so the walk would not escape past the `; + const { source, removed } = pruneUnusedSelectors(component, compile); + assert.deepEqual(removed, ['.orphan']); + assert.match(source, /\.wrap > \.item \{ font-weight: bold; \}/); + assert.match(source, /\.wrap \{ padding: 4px; \}/); + const { warnings } = compile(source, { generate: false }); + assert.deepEqual(warnings.filter((w) => w.code === 'css_unused_selector'), []); + }); + + it('removes a fully unused combinator rule without leaving a dangling fragment', () => { + // The corruption shape: after a mid-prelude cut, every remaining fragment + // equals the flagged selector, so the whole-rule branch deleted from the + // cut point and left `.wrap >` dangling in source. + const component = `

x

\n`; + const { source } = pruneUnusedSelectors(component, compile); + assert.doesNotMatch(source, /\.orphan/); + assert.doesNotMatch(source, /\.wrap >\s*\{/, 'no dangling combinator fragment'); + assert.doesNotMatch(source, /\.wrap >\s*$/m, 'no dangling combinator line'); + assert.match(source, /\.wrap \{ padding: 4px; \}/); + const { warnings } = compile(source, { generate: false }); + assert.deepEqual(warnings.filter((w) => w.code === 'css_unused_selector'), []); + }); }); describe('postcondition scanner', () => {