Skip to content
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

Node.before() inserts after if the new node is already a sibling #1778

Closed
nex3 opened this issue Sep 28, 2022 · 11 comments
Closed

Node.before() inserts after if the new node is already a sibling #1778

nex3 opened this issue Sep 28, 2022 · 11 comments

Comments

@nex3
Copy link
Contributor

nex3 commented Sep 28, 2022

Test case:

const postcss = require("postcss");

const root = postcss.parse("@foo; @bar; @baz");
const foo = root.nodes[0];
const baz = root.nodes[2];
baz.before(foo);
root.toString(); // expected: "@bar; @foo; @baz"
                 // actually: "@bar; @baz; @foo"

It looks like the index calculation gets stale once the new node is removed from its original location.

@ai
Copy link
Member

ai commented Sep 28, 2022

Sorry, I need to ask a help with research and PR.

I am spending all my free resources helping people affected by war.

@romainmenke
Copy link
Contributor

👋 willing to take a look in the next few days.

@romainmenke
Copy link
Contributor

romainmenke commented Sep 29, 2022

Have done some digging and as far as I can tell the Container methods aren't made safe for this scenario.

They are intended and written for adding new nodes.

let root = parse('@alpha; @beta; @gamma')
let alpha = root.nodes[0];
root.push(alpha);

// Just adds one more "@alpha"
// @alpha; @beta; @gamma; @alpha

This is also reflected in the documentation : https://postcss.org/api/#container-before

Insert new node before current node to current node’s parent.


That said I think all the methods can be updated with something like this :

function someContainerMethodForInsertion(..., add) {
  if (this.index(add) > -1) {
    this.removeChild(add);
  }

  // existing code

Each method first checks if the node exists and removes it, if needed.

I think this might be a breaking change for some plugins.


Update : this comment was incorrect

@ai
Copy link
Member

ai commented Sep 29, 2022

Each method first checks if the node exists and removes it, if needed.
I think this might be a breaking change for some plugins.

Yes, it could be dangerous.

It is better to call remove() in plugin.

@romainmenke
Copy link
Contributor

Taking a more in depth look because there does seem to be some weirdness.

This works and gives the correct final order :

  insertBefore(exist, add) {
    this.removeChild(add); // call before "this.index()"
    exist = this.index(exist)

This is broken and gives an unexpected order :

  insertBefore(exist, add) {
    exist = this.index(exist)
    this.removeChild(add); // call after "this.index()"

So it seems that calling this.index might have more impact than intended.

@nex3
Copy link
Contributor Author

nex3 commented Sep 29, 2022

If the expectation is that Node.before() is only passed a node without an existing parent, imo it should enforce that. If it doesn't enforce it, imo passing in any node with a parent should work according to the function documentation.

@romainmenke
Copy link
Contributor

What I missed before is that the normalize function always detaches a node that will be inserted in a container from its current parent. This comment was incorrect : #1778 (comment)

So @nex3 was absolutely right about :

It looks like the index calculation gets stale once the new node is removed from its original location.

Only insertAfter and insertBefore had local variables with these index values that could go stale. The behaviour also depended on the node order.

for [a, b, c]:

  • a.after(c) -> index of a remains the same after removing c
  • c.after(a) -> index of c changes after removing a

Changing it would make the behaviour more predictable.

@ai
Copy link
Member

ai commented Sep 30, 2022

@romainmenke fix was released in 8.4.17.

Can we close an issue?

@romainmenke
Copy link
Contributor

romainmenke commented Sep 30, 2022

@ai Did a few checks throughout the ecosystem and found that 8.4.17 affects whitespace.

I don't think this is an issue as the whitespace change doesn't affect the actual CSS to the best of my knowledge.

I've opened pull requests to update test results :

But these also give you an idea of the effect in case you do think this is something that requires further action.


If no action is required we can close this :)
Thank you again for splitting your focus for this.

@romainmenke
Copy link
Contributor

@nex3 Have you had a chance to try out the latest version to verify that this is fixed?

@romainmenke
Copy link
Contributor

I think we can close this.

@ai ai closed this as completed Jan 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants