Skip to content

Canonicalization: do not canonicalize units in arbitrary values#19988

Merged
RobinMalfait merged 3 commits intomainfrom
fix/canonicalization-inches
Apr 28, 2026
Merged

Canonicalization: do not canonicalize units in arbitrary values#19988
RobinMalfait merged 3 commits intomainfrom
fix/canonicalization-inches

Conversation

@RobinMalfait
Copy link
Copy Markdown
Member

@RobinMalfait RobinMalfait commented Apr 28, 2026

This PR improves the canonicalization when dealing with arbitrary values.

As part of the canonicalization process we compute a signature for a given utility. This way we can ensure that when we canonicalize a candidate into a simpler candidate that it's still equivalent if the signatures match.

One thing we do during signature computation is normalizing dimensions (value + unit) into the same unit to make comparisons easier.

For example:

.foo { margin-top: 20in; }
.bar { margin-top: 1920px; }

Will both get converted to 1920px and therefore foo and bar will have the same signature.

Up until this part, everything is fine. However, this normalization also leaks when we try to canonicalize arbitrary values. One of the things we do is try to move the - into the arbitrary value:

- -mt-[20in]
+ mt-[calc(20in_*_-1)]

This is obviously not cleaner, but we can perform some canonicalization of the arbitrary value. As part of that we do constant folding and the normalization of base units. That means that we would see this:

- -mt-[20in]
- mt-[calc(20in_*_-1)]
+ mt-[-1920px]

But this might be very confusing because it might not make sense where the 1920px even came from... the only thing we should have done here is constant fold that calc expression.

That's what this PR does, it only does the constant folding but without the unit normalization:

- -mt-[20in]
- mt-[calc(20in_*_-1)]
+ mt-[-20in]

Which is exactly what we want!

Fixes: tailwindlabs/tailwindcss-intellisense#1573

Test plan

  1. Added a regression test based on the linked issue
  2. All other tests still pass

When creating signatures, we do want to normalize units into a stable
unit such that `20in` becomes `1920px`.

However, when we are optimizing arbitrary values, we don't want to do
that because this is used as an escape hatch, and the unit is very
likely to be chosen for a particular reason.

That said, arbitrary values should still get canonicalized (e.g. no need
to have multiple calc expressions)
@RobinMalfait RobinMalfait requested a review from a team as a code owner April 28, 2026 14:30
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 49291984-74d8-4487-b11f-0a5aed087699

📥 Commits

Reviewing files that changed from the base of the PR and between 7539281 and 6242396.

📒 Files selected for processing (1)
  • CHANGELOG.md

Walkthrough

Adds regression tests covering arbitrary inset spacing values with inch units. Introduces a normalizeUnit parameter (default true) to constantFoldDeclaration and constantFoldDeclarationAst so unit canonicalization can be disabled; when false, only the existing zero-length (0<length>) handling runs and original unit strings are preserved. Updates canonicalize-candidates to call constant-folding with explicit parameters to control normalization.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: preventing unit canonicalization when processing arbitrary values in Tailwind CSS.
Description check ✅ Passed The description clearly explains the problem, the solution, and includes a test plan demonstrating the changes made.
Linked Issues check ✅ Passed The PR directly addresses issue #1573 by preventing unintuitive unit conversions (e.g., 0.04in to 3.84px) in arbitrary values while preserving constant folding functionality.
Out of Scope Changes check ✅ Passed All changes are focused on fixing unit canonicalization in arbitrary values: tests, function signatures, and constant-folding logic with explicit normalizeUnit parameter.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/tailwindcss/src/canonicalize-candidates.test.ts (1)

1214-1225: Broaden the regression a bit.

This locks down the in case, but the new behavior is generic across all convertible length units. Adding one more case for a different unit, such as cm or rem, would better protect the normalizeUnit=false path from regressions.

Suggested addition
       ['-mt-[0.04in]', 'mt-[-0.04in]'],
       ['mt-[-0.04in]', 'mt-[-0.04in]'],
       ['-mt-[-0.04in]', 'mt-[0.04in]'],
+      ['-mt-[0.1cm]', 'mt-[-0.1cm]'],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tailwindcss/src/canonicalize-candidates.test.ts` around lines 1214 -
1225, Add a new test case to the existing test.each block (the table driving the
test with test.each and the call to expectCanonicalization) to cover a different
length unit (e.g., use 'cm' or 'rem') so the normalization behavior with
normalizeUnit=false is protected more generally; mirror the three-case pattern
used for 'in' (e.g., ['-mt-[0.04cm]', 'mt-[-0.04cm]'], ['mt-[-0.04cm]',
'mt-[-0.04cm]'], ['-mt-[-0.04cm]', 'mt-[0.04cm]']) and place it alongside the
existing 'in' cases in the same test.each array.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/tailwindcss/src/canonicalize-candidates.test.ts`:
- Around line 1214-1225: Add a new test case to the existing test.each block
(the table driving the test with test.each and the call to
expectCanonicalization) to cover a different length unit (e.g., use 'cm' or
'rem') so the normalization behavior with normalizeUnit=false is protected more
generally; mirror the three-case pattern used for 'in' (e.g., ['-mt-[0.04cm]',
'mt-[-0.04cm]'], ['mt-[-0.04cm]', 'mt-[-0.04cm]'], ['-mt-[-0.04cm]',
'mt-[0.04cm]']) and place it alongside the existing 'in' cases in the same
test.each array.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d38c7926-754f-4620-a37c-b1fe69add920

📥 Commits

Reviewing files that changed from the base of the PR and between 3aac5da and 7539281.

📒 Files selected for processing (3)
  • packages/tailwindcss/src/canonicalize-candidates.test.ts
  • packages/tailwindcss/src/canonicalize-candidates.ts
  • packages/tailwindcss/src/constant-fold-declaration.ts

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.

False negative for suggestCanonicalClasses with inches to pixels via arbitrary values

1 participant