Skip to content

Fix DimensionToken serialization for scientific-notation-like units#74

Open
gaoflow wants to merge 1 commit into
Kozea:mainfrom
gaoflow:fix-dimension-scientific-serialize
Open

Fix DimensionToken serialization for scientific-notation-like units#74
gaoflow wants to merge 1 commit into
Kozea:mainfrom
gaoflow:fix-dimension-scientific-serialize

Conversation

@gaoflow

@gaoflow gaoflow commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Problem

DimensionToken._serialize_to escapes a unit's leading e/E so the unit does
not merge with the number into a scientific-notation number token when the
output is re-parsed. The guard is both incomplete and case-corrupting, so
serialize(parse(css)) == css is violated for some valid dimension tokens:

>>> from tinycss2 import parse_component_value_list as p, serialize
>>> serialize(p(r'1\45 '))     # value 1, unit 'E'
'1\\65 '                       # re-parses to unit 'e' -> case flipped
>>> serialize(p(r'1\65 3'))    # value 1, unit 'e3'
'1e3'                          # re-parses as the NUMBER 1000.0

Two failure modes:

  • The escape is hard-coded as \65 (lowercase e), so a unit E round-trips
    to e (case flip).
  • The trigger only covers e/E and e-/E-, missing e/E followed by a
    digit (e3, E3, e05, ...). Those units merge into the exponent and the
    dimension silently becomes a number with a different value.

Common units (em, ex, EX, Q, ...) are unaffected.

Fix

Escape the leading letter using its own code point (\\%X ) so its case is
preserved, and widen the trigger to any unit whose first character is e/E
followed by end-of-unit, a sign, or a digit.

Tests

test_serialize_dimension_scientific_notation round-trips the problem units
(e, E, e3, E3, e05, e-3, E-3), asserting they stay dimensions with
the same value/int_value/unit, and checks that common units are not over-escaped.

Full suite: 16976 passed. ruff check clean.

DimensionToken._serialize_to escaped a unit's leading 'e'/'E' to stop it
merging with the number into a scientific-notation number token on re-parse,
but the guard was both incomplete and case-corrupting:

- It hard-coded the escape '\65 ' (lowercase 'e'), so a unit 'E' round-tripped
  to 'e' (case flipped).
- It only triggered for 'e'/'E' and 'e-'/'E-', missing 'e'/'E' followed by a
  digit (e3, E3, e05, ...). Those units merged into the exponent, so the
  dimension silently re-parsed as a number (1 + unit 'e3' -> '1e3' -> 1000.0).

Escape the leading letter using its own code point (preserving case) and widen
the trigger to any unit whose first char is 'e'/'E' followed by end-of-unit, a
sign, or a digit. Common units (em, ex, EX, Q, ...) are unaffected.
@liZe

liZe commented Jul 6, 2026

Copy link
Copy Markdown
Member

Hi!

Thanks for the pull request.

Out of curiosity: did this problem happen in one of your projects?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants