-
-
Notifications
You must be signed in to change notification settings - Fork 535
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
feat(css_parser,css_formatter): start parsing exact properties and values #1419
Conversation
✅ Deploy Preview for biomejs canceled.
|
Parser conformance results onjs/262
jsx/babel
symbols/microsoft
ts/babel
ts/microsoft
|
p.expect(T![:]); | ||
|
||
parse_property_value_with_fallbacks(p, |p| { | ||
parse_css_auto(p).or_else(|| parse_regular_number(p)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This technically accepts any number right now, but i want to try to keep this PR "small", and we can add in specific integer/range-bound handling later on.
CodSpeed Performance ReportMerging #1419 will improve performances by 30.53%Comparing Summary
Benchmarks breakdown
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the solution of handling properties with fallback!
@@ -463,6 +594,11 @@ CssGenericProperty = | |||
// background: transparent center/1em auto no-repeat; | |||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |||
// } | |||
// | |||
// This node type is implicitly added to any Node definition with the name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean that this node type is added to any value Node definition?
What do you think about having a fallback for the entire property node to CssGenericProperty
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think having the fallbacks available on each specific property is nice because it ensures you can always query for a property purely by the struct in the AST. Like every declaration of width
, whether it's valid, invalid, a default, or even has a bogus, syntactically incorrect value, will always be a CssWidthProperty
node, and you can be confident that you won't miss any when traversing the tree. So it'd be like:
width: 10px; /* CssWidthProperty, with CssRegularDimension as the value */
width: 10s; /* CssWidthProperty, with CssGenericComponentValueList as a value */
width: 10(\; /* CssWidthProperty, with BogusPropertyValue as a value */
unknown: 10s; /* CssGenericProperty, with CssGenericComponentValueList as a value */
--custom: 10s; /* CssGenericProperty, with CssGenericComponentValueList as a value */
I know we had CssCustomProperty
for a little while that handle the --custom
ones, and maybe that's worth bringing back to indicate that it's semantically valid and understood, separate from CssGenericProperty
that would generally mean "this looks valid, but we don't know what the property is, so we can't enforce any semantics".
Unfortunately, either way if we do just fall back to generic for invalid values, we probably still want to do the automatic code generation stuff with the union to add CssWideKeyword
as a value, since that applies to all properties, too. I think that's why I'm really more okay with having this be another variant on every property type, since there will already be at least 2 or 3 variants no matter what.
0d3a8b0
to
0773ac5
Compare
wip parsing properties properly parse and format `all` and `z-index` fix comment in ungram feedback clippy more clippy doctest
8c21e46
to
fc63830
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm so excited to see the new nodes in action.
Summary
Closes #1445. Related to #268.
This PR implements the infrastructure needed to parse properties and their values exactly. With this, we can represent every property defined in CSS using an exact structure that perfectly matches the grammar definition for that property, exposing all of the known information directly in the syntax tree, rather than having to compute rules about value structures elsewhere, like in an analyzer.
The primary complication is that the CSS spec defines explicit grammars for each property, saying what the "normal" valid values are, but then also includes all of the CSS-wide keywords as valid values for a property, so long as they are the only thing in the value. On top of that, we also want to be able to represent "incorrect" values as syntactically correct, rather than just immediately falling back to Bogus. This will allow other tools to trust the syntax of the value and attempt to interpret it more easily with structured values rather than just a stream of bare tokens.
To handle these fallback cases, this implementation uses the new rewind support from #1417 to attempt parsing a value and then re-parse it entirely if the value is incorrect. It also checks to see if the value definition is fully consumed before "approving" the value to ensure that extraneous content in the value is also handled, and it checks for CSS-wide keyword values as well, finally falling back to Bogus if no other pattern matches, meaning there truly must be a syntax error in the value definition.
All of this behavior is abstracted away into a
parse_property_value_with_fallbacks
function, which lets you provide a parsing function to try to parse the correct value from the input, then handles rolling back and re-parsing if it fails. The result is a really clean implementation for most properties that can just focus on the actual value grammar without needing to handle error cases directly:Additionally, the
parse_any_property
function, which is the entry point to parsing all property declarations, now uses a match branch to dispatch to the appropriate property parser based on the name of the property:Using a branch here provides a guarantee that the parser is at the expected identifier name before entering the property parser. This is more efficient than trying to parse each type of property in order, especially since there will be hundreds of possible properties once the parser is complete and checking each one will take a non-negligible amount of time on average for every declaration in the source content. The branch also ensures that we don't accidentally try to parse a value as some other kind of value, since the name is checked before dispatching at all.
I also added a bunch of comments in the
css.ungram
explaining how properties should be defined, to ensure that the appropriate code is auto-generated, and to hopefully ensure we can do other things that the CSS grammar defines, like embedding other property grammars within another (e.g., thefont
shorthand explicitly includes<'font-size'>
, where the quotes mean "any valid value for thefont-size
property except for the global keywords", rather than just afont-size
token. There may be some additional work needed to ensure we don't accidentally accept global keywords or fallbacks, but that will happen when we come to those properties.I'm hopeful that all of this infrastructure will make it really simple and efficient to implement all of the hundreds of unique properties that CSS defines.
Test Plan
Added a bunch of spec tests to cover all of the variations here: valid properties (both
all
andz-index
), known properties with unknown values, properties with bogus values, custom properties, and unknown properties, all of which should be covered in the snapshots.I think for parser tests we should follow a very similar format for all of the property values: cover the css-wide keywords, a consistent set of "generally incorrect" values that fall back to the
CssUnknownPropertyValue
, and then all of the actually valid types, ordered by how they're defined in the grammar. The result is pretty similar to what MDN shows for their "Examples" section, and for the most part could just be copy-pasted into tests as a starting place, which is nice.