Amazon Dies In Darkness
Amazon Dies In Darkness

Reputation: 5833

Allowed syntax of comments within CSS selectors

In CSS, often it improves code readability to not wrap long lines, but instead ensure all lines fit horizontally within the code editor's viewport.

But whitespace is very significant in CSS, often making this goal challenging.

Thus, sometimes embedding a comment within a long CSS selector (in a CSS ruleset) is a reasonable choice.

I have found that selectors like:

div./*  
 */class

work reliably, but selectors like:

div/*  
 */.class

are less supported. At least, I'm getting an error in Stylish when using CSS Lint with this one.

Are either (or both) of these technically valid, and if so, where in the RFC is this indicated?

Upvotes: 0

Views: 328

Answers (1)

BoltClock
BoltClock

Reputation: 724312

Before I answer your question, I want to assure you that whitespace is not as much of a problem in CSS selectors as you may think, and that it is actually insignificant most of the time. There are very few places where it is and only one of them you encounter in everyday use: descendant combinators. And even then you can still use a line break in place of a space and it'll still be parsed as a descendant combinator. There's only one situation I can think of and that is if the identifiers (class, ID, attribute, etc) in your compound selectors are getting too long, and you want to break up your compound selectors. That's probably a sign of issues out of your control though, so I won't judge. Now let's get to your question.

These specific examples aren't documented in the spec. To answer your question upfront: they are both valid. To understand why, you'll need to understand how tokenization works in CSS, which is covered in a specification called css-syntax. Thankfully, one crucial thing CSS has in common with many other languages whose comments have start and end delimiters, is that if a comment is sitting cleanly between two distinct tokens and neither is being broken up, then those two tokens will parse exactly the same as if the comment wasn't there.

But how CSS is tokenized can be a bit of a surprise. One might assume that a class selector such as .class would be considered a single token, based on the Selectors grammar, and therefore a comment anywhere within it would break it and cause a parse error:

<class-selector> = '.' <ident-token>

However, <class-selector> is a production, that consists of two tokens: the dot which is considered a <delim-token>, followed by an <ident-token>. Since the dot exists as a separate token from the ident that would form the class name, a comment may exist cleanly between both tokens (./**/class) while still allowing this to be parsed as a valid class selector.

This applies to class selectors, pseudo-classes (:nth-child()) and pseudo-elements (::first-letter). However it does not apply to ID selectors because an ID selector is actually a single <hash-token> (think hex color values), a comment cannot appear before a ( because reasons, nor can it appear next to a hyphen within an ident because it's part of the ident.

Having said that, a comment sitting between two characters doesn't cause a parse error right away if the resulting two tokens can still parse. But context matters. Here's an example:

.cla/**/ss

This gets parsed into the following tokens:

  1. <delim-token> '.'
  2. <ident-token> 'cla'
  3. <comment-token> (empty)
  4. <ident-token> 'ss'

This isn't an error in and of itself, because if we forget the dot for a moment then we really just have two idents with a comment between them, and such cases are valid CSS anywhere you may have two or more idents otherwise separated by whitespace, like border: thin/**/dashed being equivalent to border: thin dashed.

But this becomes an error in Selectors because the Selectors grammar doesn't allow two consecutive idents in that context (there's a limited number of places where it's allowed such as unquoted attribute selectors with an i/s flag).

As for div/**/.class, since div and .class are two distinct productions (a <type-selector> followed by a <class-selector>), a comment sitting cleanly between them won't have any effect on parsing, so this'll still be parsed as a compound selector without a descendant combinator.

The only browsers that I know have trouble parsing selectors with comments inside them are IE8 and older. This fact has been exploited over the years to produce reliable selector hacks. If you really have to use comments to hide line breaks that would otherwise break your selectors (because you've run out of places you could substitute regular line breaks), I'd recommend using them to separate entire simple selectors rather than delimiters from names because it's a little bit more readable that way. Nevertheless, the Selectors level 4 spec helpfully provides a list of places where whitespace isn't allowed within a selector and you can therefore substitute a comment in a way CSS Lint has evidently failed to account for:

White space is forbidden:

  • Between any of the top-level components of a <compound-selector> (that is, forbidden between the <type-selector> and <subclass-selector>, or between the <subclass-selector> and <pseudo-element-selector>, etc).
  • Between any of the components of a <type-selector> or a <class-selector>.
  • Between the ':'s, or between the ':' and <ident-token> or <function-token>, of a <pseudo-element-selector> or a <pseudo-class-selector>.
  • Between any of the components of a <wq-name>.
  • Between the components of an <attr-matcher>.
  • Between the components of a <combinator>.

Note that whitespace (and therefore line breaks) is allowed in most parts of an attribute selector, so the use of comments is unnecessary. Note also that the one exception to this list is <attr-matcher>, which appears to be a single token rather than two <delim-token>s. I can't find this documented anywhere.

Again, I really can't imagine having to do this, but hey, at least you learned something about CSS tokenization, right?

Upvotes: 3

Related Questions