• # A Change in Plans for Sass 3.3

This was originally published as a gist.

Sass 3.3 is coming soon, and along with it several major new features. It supports source maps, SassScript maps, and the use of & in SassScript. In preparation for its release, we've put out a couple of release candidates to be sure that everything was set and ready to go. Unfortunately, it wasn't.

Release candidates often turn up small bugs and inconsistencies in new features, but it's rare that they find anything truly damning. In this case, though, several users noticed an issue with using & in SassScript that rendered a sizable chunk of our plan for that section of 3.3 unworkable. It's not a fatal issue, and we think we have a good plan for dealing with it (I'll get to that in a bit), but it is a problem.

### The Background

To understand what's wrong, first you need to understand the reason we decided to make & accessible to SassScript in the first place. One thing users want to do pretty often is to add suffixes to classes. Sometimes this takes the place of nesting selectors, sometimes it's just to make a new class based on the old ones -- the reason doesn't matter much to this discussion. When people tried to do this, they'd write something like .foo { &-suffix { ... } }, and it wouldn't work. The reason is that & has the same syntactic function as a type selector (e.g. h1) or a universal selector (*), since it could be replaced by any of those. It doesn't make sense to write *-suffix in a selector, so &-suffix wasn't allowed either.

This didn't stop people from wanting to do it, though. So we decided, "all right, we already use interpolation (#{}) to support injecting text into selectors -- let's just use that". We decided to add & as a sort of special variable in SassScript that contained a parsed representation of the current selector. You could then mimic &-suffix by doing @at-root #{&}-suffix instead1. Life was peachy, until our intrepid users discovered the problem.

### The Problem

Here's a small snippet of SCSS that demonstrates the issue. See if you can figure it out:

Did you get it? That's right: & in this example is .foo, .bar, which means the selector compiles to .foo, .bar-suffix. Since #{} injects plain old text, there's no chance for Sass to figure out how it should split up the selector.

Chris and I talked and talked about how to fix this. We considered adding a function to add the suffix, but that was too verbose. We considered making & split the compilation of the CSS rule into several parallel rules which each had a single selector for &, but that was too complicated and fell down in too many edge cases. We eventually concluded that there was no way for SassScript & to cleanly support the use case we designed it for.

### The Solution

We knew we wanted to support the &-suffix use case, and our clever plan for doing so had failed. We put our heads together and discussed, and decided that the best way to support it was the most straightforward: we'd just allow &-suffix. This was, after all, what most people tried first when they wanted this behavior, and with the & embedded directly in the selector, we can handle selector lists easily.

This means that &-suffix will be supported in Sass 3.3, without needing #{} or @at-root. I've created issue 1055 to track it. When compiling these selectors, if the parent selector is one that would result in an invalid selector (e.g. *-suffix or :nth-child(1)-suffix), we'll throw an error there describing why that selector was generated.

We are still worried about cases where people write mixins using &-suffix that will then fail to work with certain parent selectors, but in this case we determined that this would be the least of all available evils.

### The Future of & in SassScript

In addition to supporting &-suffix, we've decided to pull SassScript & from the 3.3 release. Rest assured that it will return -- we recognize that it has other good use cases, and we intend to bring it back for the next big release (likely 3.4). In addition, it will come with a suite of functions for manipulating the selectors it makes available, so it will be more powerful than ever.

There are two reasons that we want to hold off on using & in SassScript for now. The first is that we want some time to create the functions that will go along with it and put them through their paces. This may require changing the way it works in various ways, and we don't want to have to make backwards-incompatible changes to do so.

The second reason is that we've spent a fair amount of energy talking up #{&} as a solution to the &-suffix problem. This is our own fault, clearly, but it's true and it's something we need to deal with. Making &-suffix work is great, but if everyone is using #{&} anyway because that's what we told them about a few months ago, then it's not doing everything it can. Having a release where &-suffix works but #{&} doesn't will help guide users towards the best way to solve their problem, before we make the more advanced functionality available.

@at-root will still be included in Sass 3.3.

### Releasing 3.3

Unfortunately, this change will delay the release of 3.3, but hopefully not by too much. I anticipate this being relatively straightforward to implement; the major hurdle was figuring out what to do about it, and that part's done. I plan to devote a large chunk of time to getting 3.3 out the door after I come back from winter vacation, so hopefully (no promises) it'll be released some time in January.

1. The @at-root is necessary since Sass can't reliably figure out whether & was used in the selector like it can when & is used without #{}

• # How @extend Works

This was originally published as a gist.

Aaron Leung is working on hcatlin/libsass and was wondering how @extend is implemented in the Ruby implementation of Sass. Rather than just tell him, I thought I'd write up a public document about it so anyone who's porting Sass or is just curious about how it works can see.

Note that this explanation is simplified in numerous ways. It's intended to explain the most complex parts of a basic correct @extend transformation, but it leaves out numerous details that will be important if full Sass compatibility is desired. This should be considered an explication of the groundwork for @extend, upon which full support can be built. For a complete understanding of @extend, there's no substitute for consulting the Ruby Sass code and its tests.

This document assumes familiarity with the selector terminology defined in the Selectors Level 4 spec. Throughout the document, selectors will be treated interchangeably with lists or sets of their components. For example, a complex selector may be treated as a list of compound selectors or a list of lists of simple selectors.

### Primitives

Following are a set of primitive objects, definitions, and operations that are necessary for implementing @extend. Implementing these is left as an exercise for the reader.

• A selector object is obviously necessary, since @extend is all about selectors. Selectors will need to be parsed thoroughly and semantically. It's necessary for the implementation to know a fair amount of the meaning behind the various different forms of selectors.

• A custom data structure I call a "subset map" is also necessary. A subset map has two operations: Map.set(Set, Object) and Map.get(Set) => [Object]. The former associates a value with a set of keys in the map. The latter looks up all values that are associated with subsets of a set of keys. For example:

• A selector S1 is a "superselector" of a selector S2 if every element matched by S2 is also matched by S1. For example, .foo is a superselector of .foo.bar, a is a superselector of div a, and * is a superselector of everything. The inverse of a superselector is a "subselector".

• An operation unify(Compound Selector, Compound Selector) => Compound Selector that returns a selector that matches exactly those elements matched by both input selectors. For example, unify(.foo, .bar) returns .foo.bar. This only needs to work for compound or simpler selectors. This operation can fail (e.g. unify(a, h1)), in which case it should return null.

• An operation trim([Selector List]) => Selector List that removes complex selectors that are subselectors of other complex selectors in the input. It takes the input as multiple selector lists and only checks for subselectors across these lists since the prior @extend process won't produce intra-list subselectors. For example, if it's passed [[a], [.foo a]] it would return [a] since .foo a is a subselector of a.

• An operation paths([[Object]]) => [[Object]] that returns a list of all possible paths through a list of choices for each step. For example, paths([[1, 2], [3], [4, 5, 6]]) returns [[1, 3, 4], [1, 3, 5], [1, 3, 6], [2, 3, 4], [2, 3, 5], [2, 3, 6]].

### The Algorithm

The @extend algorithm requires two passes: one to record the @extends that are declared in the stylesheet, and another to transform selectors using those @extends. This is necessary, since @extends can affect selectors earlier in the stylesheet as well.

#### Recording Pass

In pseudocode, this pass can be described as follows:

#### Transformation Pass

The transformation pass is more complicated than the recording pass. It's described in pseudocode below:

A keen reader will have noticed an undefined function used in this pseudocode: weave. weave is much more complicated than the other primitive operations, so I wanted to explain it in detail.

#### Weave

At a high level, the "weave" operation is pretty easy to understand. It's best to think of it as expanding a "parenthesized selector". Imagine you could write .foo (.bar a) and it would match every a element that has both a .foo parent element and a .bar parent element. weave makes this happen.

In order to match this a element, you need to expand .foo (.bar a) into the following selector list: .foo .bar a, .foo.bar a, .bar .foo a. This matches all possible ways that a could have both a .foo parent and a .bar parent. However, weave does not in fact emit .foo.bar a; including merged selectors like it would cause exponential output size and provide very little utility.

This parenthesized selector is passed in to weave as a list of complex selectors. For example, .foo (.bar a) would be passed in as [.foo, .bar a]. Similarly, (.foo div) (.bar a) (.baz h1 span) would be passed in as [.foo div, .bar a, .baz h1 span].

weave works by moving left-to-right through the parenthesized selector, building up a list of all possible prefixes and adding to this list as each parenthesized component is encountered. Here's the pseudocode:

This includes yet another undefined function, subweave, which contains most of the logic of weaving together selectors. It's one of the most complicated pieces of logic in the entire @extend algorithm -- it handles selector combinators, superselectors, subject selectors, and more. However, the semantics are extremely simple, and writing a baseline version of it is very easy.

Where weave weaves together many complex selectors, subweave just weaves two. The complex selectors it weaves together are considered to have an implicit identical trailing compound selector; for example, if it's passed .foo .bar and .x .y .z, it weaves them together as though they were .foo .bar E and .x .y .z E. In addition, it doesn't merge the two selectors in most cases, so it would just return .foo .bar .x .y .z, .x .y .z .foo .bar in this case. An extremely naive implementation could just return the two orderings of the two arguments and be correct a majority of the time.

Delving into the full complexity of subweave is out of scope here, since it falls almost entirely into the category of advanced functionality that this document is intentionally avoiding. The code for it is located in lib/sass/selector/sequence.rb and should be consulted when attempting a serious implementation.