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

[css-nesting-1] consider dividing future selector syntax and future declaration syntax differently #8251

Closed
dbaron opened this issue Dec 21, 2022 · 8 comments

Comments

@dbaron
Copy link
Member

dbaron commented Dec 21, 2022

One of the things that came up in today's discussion of issue #8248 of the "option 3" syntax proposal is Peter's point that it reserves nearly all of the future expansion space for selector syntax and very little of it for declaration syntax (where declarations are currently basically property:value pairs, possibly with !important at the end). I wanted to open this issue to explore dividing the future expansion space differently.

The underlying problem is that Option 3's nesting syntax introduces places in CSS syntax that accept either declaration syntax or accept selector syntax (excluding selectors that begin with an identifier or a function). These syntaxes (thanks to the exclusion) currently do not overlap. However, barring adding further exclusions in the future, this means that future expansions of either selectors or the declaration syntax would need to fit within the rules we define for how we determine what is a selector versus what is a declaration. This means that we are constraining one or both of future selector syntax expansion or future declaration syntax expansion. Peter noted (as part but not all of his concern) that we were reserving the bulk of the space for selectors and very little for declarations.

A few initial notes:

  • Some places where we've considered expanding the declaration syntax are for additive cascade and maybe for expression of constraints on property values.
  • Following my earlier comment we did agree to reserve functions for the property:value space rather than the selector space, for the reasons discussed in that comment.
  • I think the future expansion space for selector combinators is very small. I think plausible future combinators are !, $, %, /, <, =, ?, ^, and `. There has been some discussion of the possibility that future combinator expansion might use /keyword/ syntax and thus end up using only one of those symbols. (Most of the others may be somewhat undesirable as combinators for various other reasons as well, although < is certainly plausible from a syntax perspective.)

The things that the Option 3 proposal currently reserves for declaration syntax are things beginning with:

  • identifiers
  • functions (which, as noted above, should not be distinguished from identifiers unless absolutely necessary)

The things that the Option 3 proposal currently reserves for selector syntax and needs to do so in order to support selectors that are valid today (including the & added by the nesting proposal) are things beginning with:

  • The symbols :, &, >, +, ~, |, ., and #
  • Blocks beginning with [

The things that the Option 3 proposal currently reserves for selector syntax but which are not required by selectors as they exist today and which could be used by future CSS syntax are things beginning with:

  • numbers and dimensions
  • The symbol / (see above about combinator expansion; probably desirable for combinators)
  • The symbol < (which is probably desirable to keep with >, which is in the previous list)
  • The symbols !, $, %, =, ?, ^, and ` (see above about combinator expansion, may be less desirable for combinators)
  • The symbol ,
  • Blocks beginning with (
  • Strings beginning with ' or "

I'm opening this issue to consider whether we should refine option 3 by moving some of the things in the final list into the space of things that are reserved for future declaration syntax rather than the space of things that are reserved for future selector syntax.

(Note that I've probably made some mistakes in the above lists of characters, and I expect to need to edit this comment later and document those edits below.)

@tabatkins
Copy link
Member

This post makes the assumption that the rules we set down today are unchangeable in the future. This isn't the case! We can definitely change things in the future, we just face possible compat pain.

However, introducing a new syntax produces compat pain anyway (older browsers won't understand it and will throw it out as invalid), so the important question is just if there's a meaningful behavior difference between "syntax is reserved for properties but unused and currently invalid" and "syntax is reserved for selectors but unused and currently invalid".

I argue that there is not. The behavior defined for properties and blocks by Option 3 right now is:

  • If you look like you're a property, we parse until the next ; (or end of parent block). You can have {} inside of you; it doesn't do anything special parsing-wise.
  • If you look like you're a style rule, we parse until the next ; OR {} (or end of parent block). If we see a ; we guessed wrong and you were actually an invalid property, so we throw it out and start parsing fresh from there. If we see a {} we know you were a rule, and start parsing fresh after that.

So, say we introduce a new $foo: bar; property syntax. Before Nesting, that would just be noticed as an invalid property, and we would consume tokens until we hit the ; and throw it all out. After nesting, it's noticed as a style rule and we consume until we hit the ;, at which point we throw it all out as invalid. Identical behavior in both cases.

The only case where the behavior is different is if there was a {} block inside the property value. Before nesting, $foo: {} bar; would be recognized as an invalid property, and we'd consume tokens until we hit the ; and throw it all out. After Nesting, it's a style rule, and we consume until we hit the {}. Then we start parsing fresh and try to consume the bar; part - unless the content is shaped exactly right, it'll definitely look invalid anyway, and everything up to the ; will get thrown out. So virtually all outcomes still produce identical behavior in both cases, they just reach that via slightly different mechanisms.

Only something like $foo: {} color: red; will actually produce a meaningfully different parse in the two scenarios; Nesting will recognized the color: red; as a property while pre-Nesting would throw it away as part of the invalid-property junk.


All this is to say: we're actually pretty fine to leave the distinction as it currently is, and adjust things in the future if and when we need to. The compat impact is extremely minimal either way, so we don't need to try and plan for the future by tying our hands further today.

@tabatkins
Copy link
Member

Also as a secondary concern, people today use all sorts of ASCII garbage as a "commenting" mechanism for properties. Writing *color: red; as a way to temporarily comment out the 'color' property happens all the time, and often ships publicly too!

So, any future extensions to property syntax that try to add a glyph before the property name need to deal with the potential compat impact of this invalid CSS in the wild. (Nesting gets away with having these invalid declarations trigger style rules because the rest of the declaration won't look like a valid style rule, and we have the early exit if we see the ; while parsing the "selector" of the rule.)

In other words, the syntax space of "declarations, but we put a glyph at the beginning" is already pretty decently poisoned and probably a space we want to avoid. So, having Nesting consume that syntax space more explicitly doesn't actually reduce our usable options as much as it might look.

@fantasai
Copy link
Collaborator

+1 to everything @tabatkins said. I don't think we should change the split, “symbolic vs letter start” is a really clear and easy way to define the division, and as he explains here (and I explain in #8249 (comment)) we can change the split later if there's a particular symbol we want to switch into a declaration start character.

We should probably figure out what we want to do with numbers and dimensions, though. :) They're neither idents nor symbols, and neither has precedent for using them, so I could go either way.

@plinss
Copy link
Member

plinss commented Dec 21, 2022

If you look like you're a property, we parse until the next ; (or end of parent block). You can have {} inside of you; it doesn't do anything special parsing-wise.

This is a change to long-established error recovery. Currently we always parse until the next ; OR {}. Changing error recovery behavior is dangerous.

@plinss
Copy link
Member

plinss commented Dec 21, 2022

A fundamental problem with kicking the can down the road as to which symbols are reserved for properties vs selectors (or refining the rules today), is that it precludes us from ever using the same symbol in both places with different meanings. e.g. using a + before a property for additive behavior and as a combinator.

@tabatkins
Copy link
Member

tabatkins commented Dec 21, 2022

This is a change to long-established error recovery. Currently we always parse until the next ; OR {}. Changing error recovery behavior is dangerous.

This isn't correct. Properties have always been able to have {} blocks inside of themselves. At least, it's certainly been valid per Syntax since I first wrote the spec years ago.

Edit: Finally went and re-verified CSS2 - {}-blocks were indeed allowed in properties per the Core Syntax (property values had the grammar [ any | block | ATKEYWORD S* ]+). They're not valid per the Appendix G grammar, but as noted in the Core Syntax, things allowed by Core but not Appendix G are there to aid with error-handling. So the intention in CSS2 was clearly that {}-blocks were allowed-but-invalid inside of properties, and did not end the property for parsing purposes - only a semicolon (or closing the parent block) did. And the CSS Syntax spec does the same thing.

@fantasai fantasai changed the title consider dividing future selector syntax and future declaration syntax differently [css-nesting-1] consider dividing future selector syntax and future declaration syntax differently Dec 23, 2022
@tabatkins
Copy link
Member

I commented over in #8249, but a relevant point from that comment is:

The recovery rules are:

  • if something invalid is being parsed as a declaration, we throw away tokens until the next top-level semicolon.
  • if something invalid is being parsed as a nested rule, we throw away tokens until the next top-level semicolon or top-level {}-block.

This suggests that (a) we want to bias toward parsing unknown things as nested rules, since the recovery is stricter/safer, and (b) if an older browser parses some pattern as an (invalid) nested rule, and we decide to make that pattern a declaration instead, this is fine so long as it doesn't contain a {}-block followed by something that looks like a complete (prop:val;) declaration.

So I believe we should bias towards triggering nested-rule parsing, and thus keep the current spec (which treats all of those currently-invalid-either-way tokens as being selectors).

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-nesting-1] consider dividing future selector syntax and future declaration syntax differently, and agreed to the following:

  • RESOLVED: Close as no longer relevant.
The full IRC log of that discussion <fantasai> TabAtkins: I think this one is moot given previous resolutions, but if dbaron has anything to discuss
<fantasai> dbaron: I'm fine with moot
<fantasai> astearns: Anyone else with concerns?
<dbaron> astearns: no change?
<fantasai> RESOLVED: Close as no longer relevant.
<dbaron> TabAtkins: changes already made in other issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants