-
Notifications
You must be signed in to change notification settings - Fork 49
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make insertions during iteration safe #295
Conversation
This change addresses some unexpected behavior when inserting nodes into a container. For example, assume you have a container containing two class nodes `.foo.baz`. While iterating that container with `each`, assume you are processing `.foo` at index 0. Today, the behavior is as follows: - `insertBefore(.baz, .bar)` => the next callback is to `.baz` at idx 3 - `insertAfter(.foo, .bar)` => the next callback is to `.baz` at idx 3 - `prepend(.bar)` => the next callback is to `.foo` again at idx 1 With this change, the behavior is the following, respectively: - the next callback is to `.bar` at idx 2 - the next callback is to `.bar` at idx 2 - the next callback is to `.baz` at idx 3 The newly added tests demonstrate this behavior. I've also removed the old "container#each (safe iteration)" test, as it now creates an infinite loop. I'd argue that this is the expected behavior now, though.
/cc @romainmenke I'm afraid this might be a breaking change, what do you think? |
Yes, this is a breaking change.
Can we somehow avoid rolling out an update that results in infinite loops for code that ran fine before? We received a similar request from Google some time ago: csstools/postcss-plugins#1202 We eventually switched to a more nuanced way of iterating nodes: |
Thank you for the quick follow-up! Which change specifically is breaking? I'd imagine the change to If this breaks nesting, maybe nesting could be made more resilient first so this change wouldn't result in an infinite loop? To me that feels better than rather than relying on (what I consider to be) surprising iterating behavior. Please let me know if my assumptions are wrong, though :) I'm very new to using PostCSS. (Apologies for any typos; sending this from mobile) |
I haven't pinpointed exactly why It isn't stuck in an infinite loop, it produces different outcomes, effectively breaking the plugin because it no longer correctly transpiles nested CSS selectors.
Definitely, but that would still require making this a semver major to ensure that anyone on older versions wouldn't accidentally get this behavior change when updating transitive dependencies. I am open to changes and willing to deal with the downstream impact as far as those plugins are maintained by me :) The infinite loop comment is separate from the issues with any individual plugin. Arguably the removed test should have been: test('container#each (safe iteration)', (t) => {
let out = parse('.x, .y', (selectors) => {
selectors.each((selector) => {
if (selector.type === 'class' && selector.value !== 'x' && selector.value !== 'y') {
return;
}
selector.parent.insertBefore(
selector,
parser.className({value: 'b'})
);
selector.parent.insertAfter(
selector,
parser.className({value: 'a'})
);
});
});
t.deepEqual(out, '.b,.x,.a,.b, .y,.a');
}); It is highly unusual to make AST mutations and inserting new nodes without checking what the current node is. I agree that it is fair that "insert one more node after the current node" will always result in an infinite loop if there are no constraints. But somewhere someone will have made such a mistake. Is it correct that this change also aligns https://github.com/postcss/postcss/blob/main/lib/container.js |
We use We do this to ensure that we don't produce invalid selectors after serializing. e.g. Because |
Wouldn't the old behavior still be buggy in that case where you |
We called I am not advocating for or defending the old behavior :) I've gone ahead and changed our code so that we do simpler array mutations and don't use the AST methods when sorting. I think this change is fine, if published as a semver major. |
@romainmenke is will be fine to make |
I've also just checked cssnano and stylelint. So I think we can move forwards with this :) |
This change addresses some unexpected behavior when inserting nodes into a container. For example, assume you have a container containing two class nodes
.foo.baz
. While iterating that container witheach
, assume you are processing.foo
at index 0. Today, the behavior is as follows:insertBefore(.baz, .bar)
=> the next callback is to.baz
at idx 2insertAfter(.foo, .bar)
=> the next callback is to.baz
at idx 2prepend(.bar)
=> the next callback is to.foo
again at idx 1With this change, the behavior is the following, respectively:
.bar
at idx 1.bar
at idx 1.baz
at idx 2The newly added tests demonstrate this behavior. I've also removed the old "container#each (safe iteration)" test, as it now creates an infinite loop. I'd argue that this is the expected behavior now, though.