Increate the CSS spec coverage#5
Open
samoht wants to merge 690 commits into
Open
Conversation
CSS Masking 1 §3.6 [clip-path = none | <clip-source> | [<basic-shape>
|| <geometry-box>] | <geometry-box>] adds gaps cascade did not cover:
- [<geometry-box>]-only values (e.g. [clip-path: margin-box]) and the
shape+box juxtaposition in either order ([padding-box circle(...)]
/ [circle(...) padding-box]) - add [Clip_path_box] /
[Clip_path_with_box].
- CSS Shapes 1 §3 [<shape-radius>] for [circle()] / [ellipse()] is a
[<length-percentage>] or one of [closest-side] / [farthest-side],
and an optional [at <position>] tail. Replace [Clip_path_circle of
length] / [Clip_path_ellipse of length * length] with records
carrying [clip_path_extent] options plus a [position] field.
- CSS Shapes 1 §3.6 [polygon()] takes an optional leading
[<fill-rule>] before the points. Add [clip_path_fill_rule] and
collapse the two [Clip_path_polygon{,_spaced}] arms into a single
record with a [spaced] flag.
Walk the new shapes in [vars_of_clip_path] and update the printer to
emit the new arms.
CSS Shapes 1 §3.6 [polygon()] tweaks under minify: - [nonzero] is the [<fill-rule>] default, so [polygon(nonzero, ...)] minifies to [polygon(...)] without changing semantics. - [<percentage>]-suffixed coordinates ([50% 0%]) self-delimit, so the space between [50%] and the next length can drop, matching csso's [50%0] form. Also accept the bare [<geometry-box>] value at the end of the declaration ([clip-path: margin-box;]) by treating [;] as a value-end delimiter alongside true [is_done].
Five interop fixtures use math or basic-shape forms whose typed reader refuses the input but lightningcss / esbuild / csso preserve verbatim: - [rotate: asin(45deg)] - [asin()] takes [<number>], not [<angle>] - [rotate: acos(45deg)] / [rotate: atan(45deg)] - same shape - [width: asin(sin(45deg))] - [width] takes [<length>], not [<angle>] - [clip-path: ellipse(50px 60px at 0 10% 20%)] - 3-value [<position>] Cascade's typed grammar is intentionally stricter than what these tools accept, but authoring round-trip wins here: extend [allows_opaque_fallback] to include the inverse-trig and basic-shape function names so the declaration is preserved as raw text when the typed reader fails. A future spec-based optimizer step is what eliminates these.
The lightning interop bench and runner mixed [Printf.sprintf / Fmt.epr / Printf.eprintf] etc.; consolidate on [Fmt] (already a transitive dependency) so the test target uses one formatting library and the dune file declares it explicitly.
[Css.Parser.according_to_grammar] was renamed to [matches_grammar] in the public parser API; update the two test call sites that still used the old name.
…_feature] The public [Properties] API renamed the [font_variant_east_asian_feature] reader and printer to drop the redundant [font_variant] prefix; track the rename in [check_font_variant_east_asian_feature].
Add a [Test_inline] module exercising the [Css.inline_vars] entry point: substitution of theme [var(--name)] references, keeping the [var()] when the [--name] is undefined, and preserving the value when the user explicitly opts out via [Css.inline_vars] options. Wire the new suite into the [test] runner.
Cascade had drifted between [foo_from_bar] (English-style) and [foo_of_bar] (the standard OCaml convention) for "build a [foo] out of a [bar]" helpers. Standardise on [_of_] for the four affected entry points: - [Css.rules_of_statements] (was [rules_from_statements]) - [Css.custom_props_of_rules] (was [custom_props_from_rules]) - [Parser.declaration_of_buffer] (was [declaration_from_buffer]) - [Supports.declaration_of_components] (was [declaration_from_components])
Drop the redundant [_from_] suffix in [exclude_selector_changes_from_modified] - the function filters [other_modified] entries, not "from" the [sel_changes] argument.
Fold long descriptive names into shorter, equivalent forms across the fuzz and test runners: - [test_eval_full_context_computes_claimed_observables] -> [test_full_context_observables] - [test_invalid_css_wide_keyword_mixes] -> [test_invalid_css_wide] - [test_shared_invalid_css_wide_inventory] -> [test_css_wide_inventory] - [test_custom_property_token_stream_vectors] -> [test_custom_tokens] - and similar across the other [fuzz_*] / [test_*] modules. Also break the [generated_stylesheet] generator in [fuzz_optimize] into named statement helpers ([generated_import], [generated_namespace], ...) so individual at-rules are easier to share.
[assert_document_fields] / [assert_query_fields] / [assert_branch_inventory] all destructure records whose constructor name collides with another type's field. Make the parameter type explicit so OCaml's disambiguation picks the right field set.
The stylesheet fuzz suite had a 60+ line inline test-case list. Split it into [parser_cases] / [roundtrip_cases] / [invariant_cases] / etc. named bindings so individual clusters are easy to skip when iterating on a single area.
[fuzz_values] had several inline lists of CSS literals nested inside test bodies. Lift them out as named bindings ([valid_length_vectors], [valid_color_vectors], [valid_number_vectors], ...) so the corpus is easier to scan and reuse.
Track the [@namespace] / [@import] minify-spacing change committed in [5ec2e8b] and the surrounding pretty-printer cleanups: when the URI is a quoted string the leading [\"] already delimits the at-keyword from the URI, so the inter-token whitespace can be elided. Update the four [@import] / [@namespace] oracles in [inline_imports_*] to match the new shorter output.
The optimizer now collapses two consecutive [.x { color: ... }] rules
into the last-wins survivor when both target the same selector with no
intervening at-rule frame: [.x{color:red}] is shadowed by
[.x{color:#00f}] in source order, so under [--minify --inline-imports]
the dead rule is dropped. Update the oracle to match.
CSS Custom Properties L1 §2 allows commas inside a [var()] fallback,
but if the resulting token stream is invalid for the destination
property the declaration is invalid at computed-value time and the
minifier drops it. Two oracles caught up to the new behaviour:
- [.b { color: var(--undef, red, blue) }] - the [red, blue] sequence
isn't a valid [<color>], so [.b] disappears entirely.
- [.a { color: var(--undef,) }] - empty fallback - the wrapper-removed
value is invalid CSS for [<color>] so the rule produces no output;
the surrounding pipeline now wraps [grep -v "warning"] in [|| true]
so the empty result doesn't break the pipeline exit code.
[print_output] always emits a trailing newline when the body is non-empty, so an output that is just a single [\n] (the result of inlining everything away from a stylesheet that originally held only custom-property scaffolding) printed as a blank line. Treat the single-newline payload as empty so the CLI prints nothing in that case.
…handling [Inline.vars] previously walked declarations through [Context.eval] which substitutes typed values one var at a time. That path was OK for simple [color: var(--brand)] forms but missed three classes of input the cli oracle exercises: - Multi-comma fallback lists ([font-family: var(--font, "A", B)] substitute to a token list). - Cycle reachability: when [--a -> var(--b) -> var(--a)] both customs are dead - reachable only from a cycle - so they should drop with the consuming declaration falling back. - Empty / invalid fallbacks ([color: var(--undef,)] and [color: var(--undef, red, blue)] are invalid for [<color>] and the declaration drops under minify). Add a component-level [substitute_components] / [substitute_var] pass that walks the [Component.t list] tree, tracks visited names for cycle detection, and returns either resolved [Components] or [Cycle] when both the visited path and the fallback are exhausted. [declaration_with_components] reprints the substituted value through the typed reader, falling back to opaque preservation for shapes ([font-family] with strings, comma-bearing values) that don't survive typed reparse but are still valid CSS. Detect dead cycles with [cyclic_live_customs] so the dead-code pass actually drops them after substitution. Cover the new behaviour with a [Test_inline] suite that exercises nested fallbacks, missing-var preservation, calc fallback canonical- isation, multi-comma fallback substitution, empty fallback drops, and cycle fallback paths of length 1, 2, and 3.
[simplify_var_record] previously discarded the active visited-name list when recursing into a [var()]'s [default] / [fallback], so a cycle that passes through a var-typed default could re-enter the same name and either loop or silently substitute the wrong value. Pass [visited] through and reuse it when simplifying the fallback / default sub-values; both callers ([Var_residual] and [Calc_residual]) now hand it down explicitly.
CSS Fonts 4 §11.2.1 says [@font-face] is invalid without both [font-family] and [src], but the descriptors that ARE present are still well-formed CSS. Upstream tooling (lightningcss / esbuild / csso) preserves the partial rule verbatim, and cascade's "preserve for authoring fidelity, eliminate when doing spec-based optimization" philosophy applies here too. Drop the hard-reject in [read_font_face]; a later optimizer pass is free to reduce / drop incomplete [@font-face] rules.
…[local("")]
Two follow-up gaps in the [@font-face] / [<font-family>] readers:
- [.foo { font-family: inherit test; }] mixes a CSS-wide keyword with
a custom-ident; cascade rejected the whole declaration, but
lightningcss / esbuild / csso preserve the source verbatim so
minifier output round-trips. Drop the [css-wide-mix] check for
[font-family] / [font] (both are [<custom-ident>#] lists where the
CSS-wide keyword was already invalid as a multi-token value, so
preserving the invalid declaration is the most authoring-friendly
choice).
- [@font-face { src: local("") url(...) }] is technically invalid
(empty [local()]), but again upstream tools preserve. Drop the
empty-arg rejection in [Font_face.read_function_arg].
Replace the declaration-level opaque-fallback path for [asin] / [acos] / [atan] / [atan2] with a typed [Opaque of Component.t list] arm in the [angle] AST. The trig readers now snapshot the function call before attempting the typed reduction; on failure they restore the cursor and emit [Angle.Opaque [<original-call>]] instead of raising. This keeps the parser typed end-to-end (no raw-text fallback at the declaration boundary) and gives a future spec-based [Optimize.drop_invalid] pass a single arm to look for. The pretty-printer just emits the captured tokens verbatim, which round-trips byte-for-byte. The trig contains-check in [allows_opaque_fallback] is no longer needed and removed; basic-shape preservation still uses that path until the equivalent typed arms land for [<basic-shape>] / [<position>].
…apes Replace the substring-matching opaque-fallback path ([raw_value_has_preservable_function] testing for [\"circle(\"] etc.) with a typed [Invalid of Component.t list] arm in the [clip_path] AST. The basic-shape readers ([circle] / [ellipse] / [polygon]) snapshot the function call before attempting their typed reduction; on failure they restore the cursor and return [Clip_path.Invalid [<original-call>]] instead of raising. This drops the substring search entirely - cascade now matches on [Component.Func _] structurally - and gives the future [Optimize.drop_invalid] pass a single arm to look for. The pretty-printer emits the captured tokens verbatim.
The trig readers in [Values] and the basic-shape readers in [Properties]
duplicated the same five-line pattern: snapshot the cursor, peek the
[Func] component, run the typed reader inside [try], and on [Parse_error]
restore + skip + return the original call so the caller can wrap it in
their type's [Invalid] arm. Lift it to
[Cursor.try_typed_call : (t -> 'a) -> t -> ('a, Component.t) result]
and have all four call sites ([read_angle_trig], [read_angle_atan2],
[with_basic_shape_fallback], and the new [read_length_percentage] path)
go through it.
Add the [Invalid of Component.t list] arm to [<length-percentage>] so
[width: asin(sin(45deg))] - inverse trig produces an angle which is
type-incompatible with [<length-percentage>] - parses as
[Length_percentage.Invalid] instead of routing through the
declaration-level opaque fallback. The [length_invalid_function_name]
helper enumerates the trig functions that produce non-length results.
CSS Color 5 §13 [<hue-interpolation-method>] grammar admits a [specified] keyword alongside [shorter] / [longer] / [increasing] / [decreasing]. Cascade was missing the variant, and the [color-mix] fallback path in [Declaration] used a substring scan for [\"specified hue\"] to route the call through opaque preservation. Add [Specified] to the [hue_interpolation] enum, register it in both hue-interpolation readers, and drop the substring scan: with a typed arm, [color-mix] no longer needs the special-case fallback so its entry in [color_fallback_function]'s allow-list is sufficient. Also drop the now-orphan [contains_substring] helper.
Implement the minify-time validation step from the "preserve-then-eliminate" architecture: declarations whose typed value contains an [Invalid] arm (set by the typed readers when CSS Color 5, Shapes 1, or Values 5 detect a spec-violation cascade still parsed for authoring fidelity) are dropped under [Optimize.stylesheet] - and so under [Css.to_string ~minify:true ~optimize:true]. - [Properties.is_invalid_value : 'a property -> 'a -> bool] dispatches on the typed property and walks the value-type-specific [Invalid] arms ([angle], [length_percentage], [clip_path], [text_indent_value]). - [Declaration.is_invalid : declaration -> bool] hands [(property, value)] off to [is_invalid_value], threading through [Theme_guarded]. - [Optimize.drop_invalid] walks every declaration container in the stylesheet and filters declarations failing the predicate. [Optimize.stylesheet] now ends with [drop_invalid] so the existing [--optimize] / minify-with-optimize CLI flag picks up the new behaviour.
…rd mix + verbatim rendering CSS Cascade 5 §7.3: a CSS-wide keyword ([inherit] / [initial] / [unset] / [revert] / [revert-layer]) must stand alone; mixed inside a [<custom-ident>#] list it makes the whole declaration invalid ([font-family: Arial, inherit]). Cascade was happily accepting the mixed list and emitting it verbatim under [--minify]. Add an [Invalid of Component.t list] arm to the [font_family] type; [read_font_family] detects a list with a CSS-wide member and rebuilds the original token slice as the [Invalid] payload. [is_invalid_value] covers the new arm so [Optimize.drop_invalid] removes the declaration under minify. Also fix the printer for the existing [Invalid] arms in [<angle>], [<length-percentage>], and [<clip-path>]: they were rendering the captured components through [Component.pp], which emits the debug-format [<ident name>@[start-end]] suffix. Use [Parser.to_string] (or [Parser.to_string_minified] under [--minify]) so [Invalid] tokens round-trip as their original source text.
[Optimize.drop_invalid] removes declarations whose typed value carries an [Invalid] arm. Until now it was wired only through [Optimize.stylesheet] ([--optimize]); plain [--minify] kept the spec-violating declarations. Run the same pass plus [drop_empty_rules] in the [--minify] path of [Css.to_string] so spec-based eliminations apply at the lighter pass too. Reorder [Optimize.stylesheet] so [drop_invalid] runs before [statements_top_level], which lets [drop_empty_rules] (already part of that pipeline) remove the empty rules left behind. Expose [drop_empty_rules] in [Optimize]'s mli for the [--minify] caller.
[Color_percentage] in [pp_gradient_stop] used [Pp.space_if_pretty] unconditionally, which produced [red0%] / [#00f50%] under minify. That tokenizes wrong: CSS Syntax 3 §4 absorbs the trailing digits into the ident ([red0]) or hash ([#00f50]) token, so the position never starts. Render the colour to a sub-buffer, peek the last byte, and elide the separator only when the colour ends with [)] (function-shaped: [var(...)] / [rgb(...)] / etc.). Ident- and hash-shaped colours keep the mandatory space; function-shaped colours stay compact. Drive-by rename in test/interop/lightning of [conic_gradient_has_bare_from_zero] (5 underscores, blocked merlint) to [conic_gradient_bare_zero_angle] (4).
Add [conic_gradient_bare_zero_angle] regex check that recognises the bare-zero angle in [conic-gradient(from 0, ...)] / [from 0 at] and marks it as an upstream minifier optimization the lightning harness should treat as a cached oracle bug. CSS Values 4 §6.5 restricts the unitless-zero shortcut to [<length>] - angle zeros keep [deg] (or another angle unit) outside [calc()].
… minify
Counter symbols are quoted strings; the [""] delimiters terminate
each token unambiguously, so the inter-symbol whitespace is purely
cosmetic. Switch [Pp.list ~sep:Pp.space] to
[Pp.list ~sep:Pp.space_if_pretty] so [symbols: "(" ")"] minifies
to [symbols:"("")"].
When a rule's declarations carry all four sides of [margin-*] (or all four of [padding-*]) with matching importance and no [var()] / [env()] / [attr()] leaves, fold them into the [margin] / [padding] shorthand. The pretty-printer's existing 1-to-4-value collapse then picks the shortest spelling. Existing [merge_box_shorthand_longhands] runs after this pass and handles absorption when an explicit shorthand was already present. Also drop empty [@page { }] in [drop_empty_rules]: the [Page_with_margins] arm matches [(_, [], [])] alongside the existing empty-block at-rules. Keithamus interop: 185 -> 181 failures (margin / padding composition plus the empty-page drop). Lightning interop: 16 -> 5 failures (the same composition is what most lightning tests expected). All other suites green.
CSS Syntax 3 sec. 8.3 treats [@charset] as a byte-pattern encoding-declaration that the parser consumes before tokenization, not a stylesheet at-rule whose count is meant to round-trip. The cascade emits UTF-8 unconditionally, so [@charset "UTF-8"] is purely redundant; per the spec only the first non-UTF-8 declaration is honored, so subsequent ones can also be dropped. [Optimize.normalize_charset] runs in [statements_top_level]: drop every [@charset "UTF-8"] (case-insensitive), keep the first non-UTF-8 declaration, drop later ones. The fuzz [boundary_shapes] and [atrule_counts_stable] invariants previously treated [Charset _] as a structural at-rule whose presence must round-trip; that's the right invariant for layers / media / keyframes but wrong for charset. Drop [Charset _] from the boundary shape and the at-rule-count check list so the invariants reflect the spec. Keithamus interop: 181 -> 180 failures (charset/0001 now passes). All other suites unchanged. Fuzz green.
[Min_width_rem] / [Min_width_px] always emitted the legacy [(min-width: NNN)] spelling. Per Cascade's README minify policy (media queries: legacy -> range), the shorter [(width>=NNN)] form applies to [@container] queries too. Branch on [minify] in [to_string_with]: minified output emits [(width>=...)]; pretty mode keeps [min-width:] for source-shape preservation.
CSS Grid Layout 2 §7.3 (https://drafts.csswg.org/css-grid/#named-areas) defines a null cell token as one or more sequential periods, all denoting the same single empty cell. Padding spellings like [....] (used to align row strings in source) collapse to the canonical single [.] under minify.
…aren [Animation.ends_with_paren] tracks whether the timing-function token ends with [)] so the shorthand printer can elide the inter-token space ([cubic-bezier(...)X] -> [cubic-bezier(...)X], no space). It already handled [steps(1, jump-start|jump-end)] which fold to [step-start]/[step-end] keywords; the [start] / [end] aliases of the same shorthand do too but were misclassified as paren-shaped. Result: [animation: fade steps(1, start)] minified to [animation:step-startfade] (one ident token, broken). Now emits [animation:step-start fade] as expected.
Two more 2-/4-longhand composition passes alongside margin / padding:
- [row-gap] + [column-gap] -> [gap], emitting the typed
[Gap (Lengths { row_gap; column_gap })] value so the printer's
collapse-to-single-value rule fires when both lengths match.
- 4 [border-*-radius] corners -> [border-radius], reusing the [box_side]
4-tuple convention and the typed [Radius { horizontal; vertical = None }]
value so [length] corners lift to [length_percentage].
Both passes are conservative: contiguous occurrences in the next 2 / 4
declarations, matching importance, no [var()] / [env()] / [attr()]
leaves on any side.
Keithamus interop: 180 -> 176 failures.
CSS Backgrounds 3 §3.6.1 makes the two-value [<repeat-style>{2}] forms
equivalent to shorter spellings under minify:
- [X X] -> [X] for [repeat] / [space] / [round] / [no-repeat]
- [repeat no-repeat] -> [repeat-x]
- [no-repeat repeat] -> [repeat-y]
Drive-by: drop the spurious [Cursor.expect_eof] from the value-side
[read_repeats] helper. The outer [validate_no_extra_tokens] already
gates the trailing-tokens check at the declaration boundary, so
calling [expect_eof] inside the value reader rejected
[background-repeat: no-repeat no-repeat;] just because of the [;]
that was about to be consumed by the declaration parser.
Two related changes: - [cmd_fmt] no longer passes [~flatten_nesting:true] on [--minify]. Modern browsers parse the CSS Nesting Module natively, the nested form is usually shorter than the flattened expansion, and the keithamus interop oracle treats the nested form as the canonical output. A new [--flatten-nesting] CLI flag opt-ins to the compatibility transform for old browsers; the library API [Css.optimize ~flatten_nesting] still controls the underlying pass. - [Optimize.compose_outline_shorthand] folds three contiguous [outline-width / -style / -color] longhands into the [outline] shorthand. Matching importance, no [var()] / [env()] / [attr()] on the width side; the pretty-printer's existing shortest-form pick drops [medium] / [solid] / [currentcolor] default components. Keithamus interop: 176 -> 172 failures.
CSS Images 4 §5.1 ties the cardinal [to <side>] keywords to fixed angles ([to top]=0deg, [to right]=90deg, [to bottom]=180deg, [to left]=270deg). The angle spelling is shorter for every cardinal, so [pp_gradient_direction] picks the angle form under minify. The diagonal [to <corner>] keywords are box-aspect-dependent and have no fixed angle, so they stay as keywords. [pp_linear_gradient_named] also recognises [Angle (Deg 180.)] (not just [To_bottom]) as the default linear-gradient direction now and drops it entirely, including under [With_interpolation].
CSS Overflow 3 §3.1: when both axes of the [overflow] shorthand match, the single-value spelling is canonical. The longhand-merger already emits the single-value form when [overflow-x] = [overflow-y]; this extends the printer so an explicit [overflow: hidden hidden] in source also collapses.
CSS Backgrounds 3 §4.4: [<border-width>] defaults to [medium]. When the user wrote it explicitly and another slot ([border-style] or [border-color]) is non-default, the keyword is redundant. Drop it under minify so [border: medium solid red] -> [border: solid red]. Standalone [border: medium] keeps the keyword - dropping every slot would leave [border:] which is a parse error.
CSS Align 3 §6.1: the [place-*] shorthands accept either a single value applied to both axes or two values for align/justify respectively; when both axes render to the same token the single-value spelling is canonical. Render align and justify into sub-buffers, compare, and emit one value when they match. Also folds [Stretch_stretch] -> [stretch] under minify (the AST constructor preserved the explicit two-value form for source-shape fidelity).
Same shape as the Color 4 cross-tier fallback: when a same-property declaration is followed by one whose typed value contains a [var()] / [env()] / [attr()] / anchor query on a length (possibly through a [calc()]), the earlier declaration is a cascade fallback for browsers that can't resolve the substitution and must survive deduplication. [Declaration.value_uses_runtime_subst] dispatches on the property GADT for the length / length-list properties most commonly used in this pattern: margin / padding / inset, the four corner radii, the margin and padding sides, and [outline-width]. Length-percentage properties ([width] et al.) and compound shorthands are not yet covered. Used by [Optimize.legacy_runtime_subst_fallback] next to [legacy_vendor_fallback] and [legacy_color_fallback]; only fires when the later value introduces the substitution. Keithamus interop: 172 -> 167 failures (duplicates / 0005 and the related length-fallback patterns).
Match the [place-items] collapse for [place-content] (sub-buffer render + compare) and drop the [Stretch, Stretch -> true] branch in the [place-self] case so the existing equal-pair shortcut applies uniformly.
CSS Position 3 §3.1: [inset] is the [top right bottom left] shorthand. Add [extract_inset_side] (1-element [length list] payload) and a [build_inset] case to [compose_box_shorthands]. Reuses the existing [try_compose_box] framework so the runtime-substitution guard, importance match, and four-distinct-side checks all apply.
…longhands Extend [compose_pair_shorthands] to fold the matching [-start] / [-end] longhands into the logical-property shorthand: - [margin-inline-start] + [margin-inline-end] -> [margin-inline] - [margin-block-start] + [margin-block-end] -> [margin-block] - [padding-inline-start] + [padding-inline-end] -> [padding-inline] - [padding-block-start] + [padding-block-end] -> [padding-block] - [inset-inline-start] + [inset-inline-end] -> [inset-inline] - [inset-block-start] + [inset-block-end] -> [inset-block] Add [try_compose_axis_pair] to keep the per-property duplication minimal; it shares the importance / runtime-subst / distinct-side guards with the existing gap composer. Equal sides emit the single-value form; divergent sides emit [v_start v_end]. Drive-by: - replace the nested [match ... | Some _ -> r | None -> ...] ladders in [compose_box_shorthands] and [compose_pair_shorthands] with a list of composer closures + [List.find_map]; same control flow, fewer braces. - rename [FLineHeight] -> [FLine_height] (snake_case per the constructor convention).
Public-API simplification:
- [~optimize] is gone. [Css.to_string ~minify:true] now runs the
full optimizer pipeline (declaration dedup, longhand -> shorthand
merge, rule combine, ...). [~minify:false] does only the spec-
recovery drops (invalid declarations, unknown at-rules, empty
rules) the same as before. The orthogonal "optimise without
minify" combo had no real use case and surprised callers who set
[~minify:true] but forgot [~optimize:true] (e.g. the keithamus
interop harness).
- [~newline] is gone. Output never ends with a trailing newline now;
callers who want one append it themselves. The CLI's
[print_output] already does this, so [bin/cmd_fmt] is unaffected.
- [?indent : int] joins [Css.to_string] / [Stylesheet.to_string] /
[Pp.to_string]. Defaults: [None] under [minify] (no per-level
indent), [Some 2] otherwise. Pass [Some n] to set a per-level
width or [None] to suppress indentation in pretty mode.
- [Pp.ctx]'s [indent : int] (which actually meant "current nesting
level") is renamed [level : int] and a new [indent : int option]
field carries the per-level width.
- [Stylesheet.config] / [pp_config] / [read_config] are gone. CSS
rendering config is not a CSS construct; the round-trip serialiser
only existed to satisfy a generic test harness, and the four-field
record shrunk to just [{minify; mode}] after [optimize] / [newline]
left. Inline the two args directly through [pp_decl_inline] /
[pp_important], and drop the round-trip test (test_stylesheet
[config] case + fuzz [read_config crash safety] case).
- The arbitrary "prepend [header] only when [@layer] statements exist"
branch in [Stylesheet.to_string] is gone with [?header]; callers
wanting a header concatenate it themselves.
- Bump [(lang dune)] to [3.21] and add [(env (dev (flags :standard
%{dune-warnings})))] so standalone opam builds fail on warnings.
… longhands Add three composers that fold contiguous [align-*] + [justify-*] longhand pairs (in either order, matching importance) into the corresponding [place-*] shorthand: - [align-items] + [justify-items] -> [place-items] - [align-content] + [justify-content] -> [place-content] - [align-self] + [justify-self] -> [place-self] The per-property printers already collapse equal-axis [Align_justify] pairs to a single value (CSS Align 3 §6.1), so the composed shorthand prints in canonical form.
The [Css.to_string] refactor stopped appending a trailing newline to the output, so the [c62_origin_wrapper_api] pin needs the same update applied across the test suite by the previous commit.
CSS Lists 3 sec. 1.2: [list-style] shorthand combines [list-style-position], [list-style-image], and [list-style-type] in any order. Cascade stores [List_style] as a string; the composition renders each longhand via its pretty-printer and drops the default-valued components ([outside] / [none] / [disc]). If all three drop, leave the [outside] keyword so the shorthand is not emitted with an empty value. Conservative: requires all three longhands contiguous with matching importance. Keithamus shorthands/0028.
CSS Media Queries 4 §3: a [<media-type>] of [all] without an explicit [not] / [only] prefix is the default; [@media all and X] is equivalent to [@media X]. Match [Type_query { prefix = None; type_ = All; trailing = Some cond }] in [Media.pp] and emit just the trailing condition under minify. Drive-by: rename [LSType] / [LSPosition] / [LSImage] -> snake_case to match the constructor convention.
[pp_var_without_fallback] / [pp_stylesheet_var]'s [Fallback] branch both gated theme-default resolution on [is_theme_var v] (true only for vars declared with [layer = Some "theme"]). That treated the theme set as an allowlist for theme-layer vars instead of a denylist for protection. The cleaner rule, matching the user- facing contract: - no resolver / no default: never inline (keep [var(--name)]) - resolver returns [Some value]: inline only if the variable is not protected by the theme set - resolver returns [None]: keep [var(--name)] (or the supplied fallback) and preserve syntax - theme set: protection / denylist, not an allowlist for non-theme vars Drop the [is_theme_var] gate (and the unused helper). The print-time flow is now: in theme set -> keep var; otherwise resolver, then typed default, then bare [var(--name)] / [var(--name, fallback)]. Drive-by: rename [td_kind] constructors [TDLine] / [TDStyle] / [TDColor] -> [Line] / [Style] / [Color] (the prefix was dead weight; type-directed disambiguation handles the collision with the [Color] / [Style] constructors elsewhere).
The fallback branch of [pp_stylesheet_var] inlined to the supplied
fallback value when the resolver said [None]. Per the user-facing
rule (resolver [None] keeps [var(...)] and preserves fallback
syntax) the fallback is the runtime branch, not a print-time
substitute. Switch the [None] case to re-emit
[var(--name, fallback)] so [.x{color:var(--undef, red)}] minifies
back to [.x{color:var(--undef,red)}] instead of [.x{color:red}].
Also drop the unused [pp_value] parameter warning in
[pp_var_without_fallback] (now always uses [pp_var_ref]).
…ult test [Css.Length] is a constructor in several types ([kind], [property_value_kind], [grid_template]); type-directed disambiguation can't pick the right one in the [Css.var ... Css.Length (Css.Px _)] form. Annotate the [Css.Zero] default and tag the [Length] constructor with the [Css.length Css.kind] type so the declaration builds.
The helpers moved out of [Optimize] into [Stylesheet] are AST -> AST rewrites applied just before serialization, not pretty-printer code. The [minified_output_*] prefix advertised "output strings"; rename to plain [normalise_*]: - [minified_output_charset] -> [normalise_charset] - [minified_output_shadow_color_var_slots] -> [normalise_shadows] - [minified_output_shadow_color_var_declaration] -> [rewrite_shadow_decl] - [minified_output_shadow_color_vars] -> [rewrite_shadow_value] - [minified_output_canonical_statements] -> [normalise] The [pp_] prefix is reserved for actual pretty-printer functions (those returning [unit] into a [Pp.ctx]). The drive-by also adds a comment on [color_var_of_length_var] explaining why [default] is dropped (the var is consumed in colour-slot position; the inline length default doesn't apply there).
Move out of [Optimize]: - [color_custom_property_names] (walks for [--*: <color>] declarations) - [color_fallback_of_length_fallback] / [color_var_of_length_var] (typed fallback/var coercion used by the shadow rewrite) - [normalize_shadow_color_var_*] (rewrites [box-shadow: var(--ring)] into [0 0 0 var(--ring)] so the var sits in the colour slot) - [normalize_charset] (drops redundant [@charset "UTF-8"] and trailing duplicates) These are AST -> AST transforms applied just before serialization, not cascade-correctness optimizations. They live in [Stylesheet] now under [normalise_*]; the [Optimize] entry point still calls them via the [Stylesheet.normalise] umbrella. Drive-by: extend [shorthand_longhands] with [inset] and the logical- property pairs ([margin-/padding-/inset-inline/block]) plus the border family ([border], [border-{width,style,color}], [border- {top,right,bottom,left}]) so the cascade-merge bookkeeping covers the new composers. Drop the explicit [Some Solid -> None] and [Some Current -> None] defaulting from [try_compose_text_decoration]; the typed [pp_text_decoration_shorthand] already drops those defaults at print time.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.