Re: [csswg-drafts] [css-nesting-1] Can we relax the syntax further? (#7961)

This is very promising! I also like the simplicity of just "parse it as a decl, fall back to rule if it's invalid" - it means that future extensions to property syntax don't require very careful rewriting of the lookahead rules.

(Note: this maintains the existing parser tweak that causes a `;` encountered while parsing the prelude of a rule to cause it to fail parsing; that's important.)

This behavior ends up being very similar to what has already been discussed, so let's re-explore the corners of the behavior. There are four questions to ask:

1. What happens if I write a valid declaration, that happens to also look like a valid rule?
2. What happens if I write a valid rule, that happens to also look like a valid declaration?
3. If I write an invalid declaration, can something unexpected happen?
4. If I write an invalid rule, can something unexpected happen?

tldr: Because we have the full context of the decl/rule to decide, this is remarkably safe, and requires very minimal restrictions on future CSS syntax. I have one very minor nit to address a weird corner case, but otherwise am very happy with this.

-----

For question 1, there is no problem. We also parse as a decl first, so this'll work as expected.

-----

For question 2, this is potentially a problem. I will argue that it is an *extremely rare* problem. (It was a lot easier to have a rule that looked like a declaration *in the first few tokens*, but this look at the whole thing.) For it to be an issue, the "valid declaration" has to (a) have a {} block in its value, and (b) have the stuff before the `{}` look like a valid selector.

Case (a) is already non-existent for CSS-defined properties; none of them take a {} in their value. If we ever do allow properties to do so, we can work around this issue by leaning on case (b): if we restrict ourselves to only having {} blocks as the whole value of a property (which is reasonable, I think), then it solves itself, as the part preceding the block would end with a `:` and thus be guaranteed to be an invalid selector. We don't necessarily need to bind ourselves this tightly if we're careful about grammar design, but this is at least a sufficient condition. So it should be impossible to write a valid rule that's also a valid CSS-defined property.

For custom properties the situation is different, but in multiple ways. A custom property can contain a {} in its value, anywhere. You can absolutely write `--foo:hover {color: red;};` as a valid custom prop. But that was something [we'd already accepted in the lookahead discussions](https://github.com/w3c/csswg-drafts/issues/7961#issuecomment-1396112293) - custom props just consume that entire syntax space, and rules aren't allowed to resemble them. In practice this is totally fine - dashed-idents are invalid custom element names in HTML, so this is only an issue *in theory* for non-HTML host languages in the first place, which to a first approximation don't exist. And in case you *are* using such a language, and have such an element, and want to write a selector like the above? The `:is(--foo):hover` respelling works just fine. (This is the *only* place where such a respelling will ever be required, and as I said, it's never necessary in HTML.)

----

For question 3, this is mostly not a concern (but I have one nit). The theoretical issue is that "parse as rule" might consume less tokens than "parse as decl" - specifically, if it hits a top-level {} block, it'll stop, while "parse as decl" will keep going until it sees a `;`.

This isn't an issue for CSS-defined properties currently, as again, {} blocks are invalid in them. And if we ever do allow this, so long as the {} block is at the end of the value (as it would be if we stuck to the restriction that it's the *whole* value), then the parsing difference is inconsequential: "parse as rule" will stop one token earlier, before the `;`, then the next invocation will grab the `;` and throw it away as an invalid rule. If we allowed {} blocks *not* at the end of the value, again we're still okay so long as what's allowed after it never looks like a rule, which is extremely easy to do. (But probably we should avoid that, just to be safe.)

For custom properties this is potentially more problematic. Again there's nothing preventing you from writing `--foo:hover {color: var(1 borked);} color: red;`. This initially parses as a decl with a very dumb value, but the `var()` is invalid, so we'd fall back to parsing as a rule, consume to the `}`, and generate a valid rule. Then on the next invocation we'd continue parsing and see a valid 'color' property. This is two unexpected things! Notably, tho, this can *only* happen in the rare case that you've messed up a var() function; nothing else can cause a custom property to fail to parse.

So, while this is a super rare case, I'd be more comfortable if we ruled it out entirely - if, during "parse a declaration", what you see is a custom property, then skip parsing as a rule even if the custom property fails to grammar-validate. Equivalently, during "parse a rule", early-exit if you see you're parsing an (assumed invalid) custom property (first two non-ws tokens are a dashed ident followed by a colon) and just skip forward to the next top-level `;`.

-----

For question 4, this is fine. The only way for this to cause a problem is if you write a invalid rule such that it looks like a valid property, but this is essentially the same case as question 2, then (and isn't a problem, for the argument given in question 2). But once it fails to parse as a property, then also failing to parse as a rule preserves today's behavior exactly - we end parsing at the `}` token and throw it away.

-- 
GitHub Notification of comment by tabatkins
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/7961#issuecomment-1490495722 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Thursday, 30 March 2023 15:21:59 UTC