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

Unexpected output from @extend with the child combinator (>) #1807

Closed
andrew-skybound opened this issue Aug 24, 2015 · 3 comments
Closed

Unexpected output from @extend with the child combinator (>) #1807

andrew-skybound opened this issue Aug 24, 2015 · 3 comments
Labels
bug Something isn't working

Comments

@andrew-skybound
Copy link

I'm surprised by the way that extender selectors containing the child combinator > merge with @extend. I'm not sure if this is a bug or by design.

First, consider the output when > is not used.

SCSS:

.a .b .c .d .foo {a: b}
.c .d bar .a .b {@extend .foo}

Output CSS:

.a .b .c .d .foo,
.a .b .c .d bar .a .b {
  a: b;
}

Next, we'll add a > combinator:

SCSS:

.a .b .c .d .foo {a: b}
.c > .d bar .a .b {@extend .foo}

Output CSS:

.a .b .c .d .foo,
.a .b .c > .d .d bar .a .b,
.a .b .c > .d bar .a .d .b {
  a: b;
}

Now, neither of the two generated selectors are what I would expect, considering the fact the .c .d is a superselector of .c > .d. Selector 2 has an extra .d after the .c > .d, and selector 3 strangely places an extra .d between the inserted .a .b at the end.

This would be ideal:

.a .b .c .d .foo,
.a .b .c > .d bar .a .b {
  a: b;
}

Or, if .c > .d can't merge into .c .d for some reason, I would expect it to fall back to this instead:

.a .b .c .d .foo,
.c > .d bar .a .b .c .d .b {
  a: b;
}
@chriseppstein
Copy link

@nex3 can you weigh in here? This is a superselector according to sass. I thought we basically choose the longest superselector in the extended selector to be shared with the extending selector, so I suspect this is a bug.

>> is-superselector(".a .b .c .d", ".a .b .c > .d")
true
@nex3
Copy link
Contributor

nex3 commented Aug 28, 2015

This is really complicated. Chris is right that the issue is the algorithm for finding the longest common subsequences (LCS). The way that algorithm works is to compare compound selectors against one another for superselectordom. However, in order to preserve combinators like >, we treat selectors separated by them as a single compound selector for the purposes of this algorithm. This means that we end up comparing .c > .d ... against just .d ...; the latter is a superselector of the former, so the LCS algorithm uses that and proceeds on its way. I believe that's the root of the issue you're seeing here.

Fixing this will be tricky. Even if we updated the LCS algorithm to handle this, the code consuming it strongly assumes that each compound selector in the LCS corresponds to a single compound selector in both of the source selectors, which won't be the case if (.c > .d) corresponds to (.c) (.d). To make this work properly, we may need to overhaul the representation of combinators so that visible combinators are handled less differently than the default descendant combinator in general. One possible way to do this would be to stop representing combinators as strings in the complex selectors and instead make them a property of each selector (Sass::Selector::SimpleSequence in the source). That will likely make the extend logic cleaner in general, but it'll also be a lot of work.

@nex3 nex3 added the bug Something isn't working label Aug 28, 2015
@nex3
Copy link
Contributor

nex3 commented Apr 11, 2023

I've spent some time looking into this again in light of the refactorings we did in #3340, and it's still very difficult. I've managed to get it working, but not without sacrificing some other desirable behavior. The trickiest example is the following test case:

a > b c .c1 {a: b}
a c .c2 {@extend .c1}

This currently produces

a > b c .c1, a > b c .c2 {
  a: b;
}

but this is only possible because we look at a > b as a single unit when comparing it to a, and thereby determine that a > b is a subselector of a. If we want to handle @andrew-skybound's case, we'd need to instead treat each compound selector separately even if it has a child selector... or else find an entirely new algorithm for determining the longest common subsequence that's able to expand itself in more complex ways.

Ultimately, I think this is too complex for too little payoff to be worth solving. In the distant future, once :is() is widely supported everywhere, we'll be able to use that as a target and avoid this problem entirely. Until then, I think I have to close this out as infeasible.

@nex3 nex3 closed this as not planned Won't fix, can't repro, duplicate, stale Apr 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
3 participants