mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-18 00:56:30 +03:00
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 <style> open tag. That same character is the CSS child combinator, so pruning one unused selector from a list like '.wrap > .orphan, .orphan' cut the prelude mid-list; when every remaining fragment equaled the flagged selector, the whole-rule branch then deleted from the cut point and left a dangling '.wrap >' in source. A '>' now bounds the walk only when it actually closes a <style ...> tag; combinators are walked through. Regression tests cover a mid-list combinator prune and the dangling- fragment shape (verified failing on the previous code). AI-assisted (Claude Code). Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Code
parent
20213a6817
commit
0c18cbc9ef
@@ -137,6 +137,33 @@ describe('compiler-driven pruning', () => {
|
||||
const { source } = pruneUnusedSelectors('<div>{broken', () => { throw new Error('nope'); });
|
||||
assert.equal(source, '<div>{broken');
|
||||
});
|
||||
|
||||
it('prunes past child combinators without truncating the selector list', () => {
|
||||
// The prelude walk must not treat the child combinator as a boundary:
|
||||
// cutting at the `>` of `.wrap > .item` used to rewrite the list from
|
||||
// mid-prelude.
|
||||
const component = `<div class="wrap"><p class="item">x</p></div>\n<style>\n .wrap > .item, .orphan { font-weight: bold; }\n .wrap { padding: 4px; }\n</style>`;
|
||||
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 = `<div class="wrap"><p class="item">x</p></div>\n<style>\n .wrap > .orphan, .orphan { color: blue; }\n .wrap { padding: 4px; }\n</style>`;
|
||||
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', () => {
|
||||
|
||||
Reference in New Issue
Block a user