Skip to content

Commit

Permalink
[css-nesting-1] Apply #8662, so &div is invalid and div& is valid.
Browse files Browse the repository at this point in the history
  • Loading branch information
tabatkins committed Jul 13, 2023
1 parent 2e5cbb5 commit 02db7b2
Showing 1 changed file with 31 additions and 128 deletions.
159 changes: 31 additions & 128 deletions css-nesting-1/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,9 @@ Nesting Style Rules {#nesting}
possibly multiple times.

A [=nested style rule=] is exactly like a normal style rule,
with the exception that its [=selector=]
cannot start with an [=identifier=] or [=functional notation=].
Additionally, [=nested style rules=]
can use [=relative selectors=].
except that it can use can use [=relative selectors=],
which are implicitly relative
to the elements matched by the parent rule.

<div class=example>
That is,
Expand All @@ -117,7 +116,7 @@ Nesting Style Rules {#nesting}
.foo {
color: red;

.bar {
a {
color: blue;
}
}
Expand All @@ -130,7 +129,7 @@ Nesting Style Rules {#nesting}
.foo {
color: red;
}
.foo .bar {
.foo a {
color: blue;
}
</pre>
Expand Down Expand Up @@ -171,72 +170,6 @@ Nesting Style Rules {#nesting}
</pre>
</div>

<div class=example>
However, starting the nested selector with an identifier
(a [=type selector=], in other words)
is invalid:

<pre class="lang-css invalid">
div {
color: red;

input {
margin: 1em;
}
}
/* Invalid, because "input" is an identifier. */
</pre>

Such selectors can still be written,
they just need to be slightly rephrased:

<pre class=lang-css>
div {
color: red;

& input { margin: 1em; }
/* valid, no longer starts with an identifier */

:is(input) { margin: 1em; }
/* valid, starts with a colon,
and equivalent to the previous rule. */
}
</pre>
</div>

<details class=note>
<summary>Why are there restrictions on nested rule selectors?</summary>

Nesting style rules naively inside of other style rules is, unfortunately, ambiguous--
the syntax of a selector overlaps with the syntax of a declaration,
so an implementation requires unbounded lookahead
to tell whether a given bit of text is a declaration or the start of a style rule.

For example, if a parser starts by seeing ''color:hover ...'',
it can't tell whether that's the 'color' property
(being set to an invalid value...)
or a selector for a <code>&lt;color></code> element.
It can't even rely on looking for valid properties to tell the difference;
this would cause parsing to depend on which properties the implementation supported,
and could change over time.

Forbidding nested style rules from starting with an [=identifier=]
works around this problem--
all [=declarations=] start with an identifier
giving the property name,
so the parser can immediately tell whether it's parsing a [=declaration=] or [=style rule=].

Some non-browser implementations of nested rules do not impose this requirement.
It <em>is</em>, in most cases, <em>eventually</em> possible
to tell properties and selectors apart,
but doing so requires unbounded lookahead in the parser;
that is, the parser might have to hold onto an unknown amount of content
before it can tell which way it's supposed to be interpreting it.
CSS to date requires only a small, known amount of lookahead in its parsing,
which allows for more efficient parsing algorithms,
so unbounded lookahead is generally considered unacceptable among browser implementations of CSS.
</details>


<!--
██████ ██ ██ ██ ██ ████████ ███ ██ ██
Expand All @@ -259,9 +192,6 @@ Syntax {#syntax}
[=Nested style rules=] differ from non-nested rules
in the following ways:

* The selector of [=nested style rules=]
must not start with an [=identifier=]
or a [=functional notation=].
* A [=nested style rule=] accepts a <<relative-selector-list>>
as its prelude
(rather than just a <<selector-list>>).
Expand All @@ -275,9 +205,6 @@ Syntax {#syntax}
The precise details of how nested style rules are parsed
are defined in [[!CSS-SYNTAX-3]].

ISSUE(7961): The CSSWG is currently exploring the consequences of parsing lookahead,
and may adjust the allowed syntax as a result.

An invalid [=nested style rule=] is ignored,
along with its contents,
but does not invalidate its parent rule.
Expand Down Expand Up @@ -438,10 +365,10 @@ Syntax {#syntax}

<b>/* Example usage with Cascade Layers */</b>
@layer base {
html {
html {
block-size: 100%;

& body {
body {
min-block-size: 100%;
}
}
Expand All @@ -459,7 +386,7 @@ Syntax {#syntax}
block-size: 100%;

@layer support {
& body {
body {
min-block-size: 100%;
}
}
Expand Down Expand Up @@ -511,18 +438,6 @@ Syntax {#syntax}
}
*/
</pre>

But these are not valid:

<pre class=lang-css>
<b>/* Selector starts with an identifier */</b>
.foo {
color: blue;
div {
color: red;
}
}
</pre>
</div>

<div class=note>
Expand All @@ -541,34 +456,25 @@ Syntax {#syntax}

<pre class="lang-css">
.foo {
color: blue;
&Bar { color: red; }
color: blue;
&Bar { color: red; }
}
/* In Sass, this is equivalent to
.foo { color: blue; }
.fooBar { color: red; }
.foo { color: blue; }
.fooBar { color: red; }
*/
</pre>

Unfortunately, this string-based interpretation is ambiguous with
the author attempting to add a type selector in the nested rule.
''Bar'', for example,
<em>is</em> a valid <a href="https://html.spec.whatwg.org/multipage/custom-elements.html">custom element name</a> in HTML.
This is not allowed in CSS,
as nesting is not a syntax transformation,
but rather matches on the actual elements the parent selector matches.

CSS doesn't do this:
the nested selector components are interpreted atomically,
and not string-concatenated:

<pre class="lang-css">
.foo {
color: blue;
&Bar { color: red; }
}
/* In CSS, this is instead equivalent to
.foo { color: blue; }
Bar.foo { color: red; }
*/
</pre>
It is also true that the selector ''&Bar'' is invalid in CSS in the first place,
as the ''Bar'' part is a type selector,
which must come first in the compound selector.
(That is, it must be written as ''Bar&''.)
So, luckily, there is no overlap between CSS Nesting
and the preprocessor syntax.
</div>

<div algorithm>
Expand Down Expand Up @@ -912,6 +818,10 @@ Mixing Nesting Rules and Declarations {#mixing}
before "Order Of Appearance" comes into consideration.
</div>

Note: This behavior both matches many existing preprocessor-based nesting behaviors,
and ensures that a rule can be faithfully expressed in the CSSOM
without requiring drastic changes to the existing {{CSSStyleRule}} object.

Note: While one <em>can</em> freely intermix declarations and nested rules,
it's harder to read and somewhat confusing to do so,
since all the properties <em>act as if</em> they came before all the rules.
Expand Down Expand Up @@ -1133,19 +1043,12 @@ Nesting Selector: the ''&'' selector {#nest-selector}
The [=nesting selector=] is capable of matching [=featureless=] elements,
if they were matched by the parent rule.

The <a>nesting selector</a> is allowed anywhere in a <a>compound selector</a>,
even before a <a>type selector</a>,
violating the normal restrictions on ordering within a <a>compound selector</a>.

<div class='example'>
For example, ''&div'' is a valid nesting selector,
meaning "whatever the parent rules matches,
but only if it's also a <{div}> element".

It could also be written as ''div&'' with the same meaning,
but that wouldn't be valid to start a [=nested style rule=]
(but it could be used somewhere other than the very start of the selector).
</div>
While the position of a [=nesting selector=] in a [=compound selector=]
does not make a difference in its behavior
(that is, ''&.foo'' and ''.foo&'' match the same elements),
the existing rule that a [=type selector=], if present, must be first in the [=compound selector=]
continues to apply
(that is, ''&div'' is illegal, and must be written ''div&'' instead).


<!--
Expand Down

0 comments on commit 02db7b2

Please sign in to comment.