In my recent work in SassyStudio to support the SCSS language I have hit a few hurdles. While I don't have any problems with the SCSS specification, I think it's worth delving into some tooling hurdles that I've encountered due to the way SCSS works.

Back To Basics - CSS

First, lets disect a very basic CSS rule set

ul li:hover {
    color:red;
}

Here we have a few components.

  • ul and li:hover are the "selectors" for this rule.
  • :hover is a pseudo-selector for the li element that only applies when the element is being hovered over
  • color:red is setting the color property of the li element's foreground text to red

Pretty simple. The CSS syntax is pretty flat compared to SCSS which adds nesting, so it means that the selectors will always be at the root of the document and then the rule body will have properties. It is also worth noting that CSS does not require there to be any whitespace between the colon and the property value.

Now lets look at an equivalent SCSS rule set

ul {
    li:hover {
        color: red;
    }
}

Since SCSS allows us to have nested selectors, we bump into our first problem: pseudo-selectors. Let's say you have typed out your ul rule set and begin typing li and then hit the colon. Currently, we are in an ambiguous context: do we have an li element that we would like to show code completion for pseudo-selectors or do we have a li property?

There's a few ways to resolve this, but they are all brittle: we could try to see if we have a valid property name, but that requires us to keep an up-to-date schema, including browser specific properties, which is ideal, but tough. Furthermore, we could have non-text property names and selector components like #{$some-variable} which would then require us to evaluate the variable, which could be expensive.

Another way we could address this problem is look at the element and see if there is a pseudo-selector for it. Unfortunately this won't work because most pseudo-elements apply to all elements.

The most logical way to address this problem (although still not useful in the code completion context) would be to look for the opening curly brace to determine if something is a selector or not. Unfortunately for us, SCSS supports nested properties which means that the following is a completely valid property, not a selector.

.nesting {
    font: 2px/3px {
        family: fantasy;
        size: 30em;
        weight: bold;
    }
}

Granted in that example there is a space after the colon, but the example doesn't matter as much as the concept. There's no real definitive way to know definitively tell between a property and selector when there is only one value, and thus, the only safe way to handle this scenario from tooling is by not providing completion for pseudo-selectors, only properties.

This is still not ideal, but it's better than the alternative, which would be that anytime you entered the property value context by typing a colon, you would get a list of pseudo-selectors, pseudo-classes, and pseudo-functions. Since you are likely to be providing more properties than selectors in a document, I chose to err on the side of assuming something is a property.

Reflecting back

When it all comes down to it, the main problem here is that the SCSS language (which is a superset of CSS3) introduces some ambiguities that were not present in CSS3. While they are fairly easy to work around at a document level, they aren't as easy to work around in an interactive environment.

How would I solve this? Unfortunately, I don't have any great ideas yet. Ultimately, if there was an operator for nested selector like the parent reference operator, this wouldn't be a problem. We would have an easy way to tell the context we were in and it would make parsing a lot simipler. On the other hand, it's not really necessary and would likely get annoying. Ultimately the problem arises only when you are in the process of writing your document; everything is simpler when dealing with the document as a whole. Unfortunately, that's almost the entire process from the tooling aspect.