Skip to content

Improve whitespace handling during canonicalization#19986

Merged
RobinMalfait merged 6 commits intomainfrom
fix/improve-whitespace-handling
Apr 26, 2026
Merged

Improve whitespace handling during canonicalization#19986
RobinMalfait merged 6 commits intomainfrom
fix/improve-whitespace-handling

Conversation

@RobinMalfait
Copy link
Copy Markdown
Member

This PR fixes a printing bug during canonicalization where it converts:

[&:has(~_*_*:checked)]:text-green-500

into:

[&:has(~**:checked)]:text-green-500

This is because the _ was marked as insignificant and therefore removed. This PR fixes that and maintains the whitespace (_) characters when needed.

Additionally, in the comments of the linked issue somebody mentioned that:

w-[calc(100%_-_--spacing(60))]

was turned into:

w-[calc(100%---spacing(60))]

...and while that's still correct and parseable, it's not the prettiest.

This PR will still get rid of the whitespace, but introduce wrapping parens (…) instead, in case readability is not ideal.

In this case, we will turn it into:

- w-[calc(100%_-_--spacing(60))]
- w-[calc(100%---spacing(60))]
+ w-[calc(100%-(--spacing(60)))]

Of course there are some cases where we don't need to introduce (…) unnecessarily:

  • shadow-[inset_0px_1px_--theme(--color-white/15%)] would not be turned into shadow-[inset_0px_1px_(--theme(--color-white/15%))] because no readability is gained when it's part of a normal space separated list
  • m-[--spacing(12.34)] would not be turned into m-[(--spacing(12.34))] because there is nothing else it can conflict with
  • m-[calc(--spacing(12.34)*2)] would not be turned into m-[calc((--spacing(12.34))*2)] because it's the first argument and doesn't conflict with the *
  • m-[min(100%,--spacing(12.34))] would not be turned into m-[min(100%,(--spacing(12.34)))] because a , doesn't cause readability issues

Fixes: tailwindlabs/tailwindcss-intellisense#1544

Test plan

  1. Added new tests to ensure the bug is fixed
  2. Added new tests to ensure readability is improved (and not degraged) when whitespace was used to improve readability

@RobinMalfait RobinMalfait requested a review from a team as a code owner April 26, 2026 21:58
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9c8c35d8-e6a1-4ab0-a9a0-bba1a9f6acbc

📥 Commits

Reviewing files that changed from the base of the PR and between 51b92c8 and dabac37.

📒 Files selected for processing (1)
  • CHANGELOG.md
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

Walkthrough

The arbitrary-value printer now uses a shared symbols set (math operators and selector combinators) to detect operator tokens for whitespace removal and checks adjacent symbol tokens (two-token lookahead/behind) to avoid inappropriate space dropping. function nodes whose names start with -- are rewritten into a parenthesized form when occurring after a symbol, with exceptions for first-argument positions, when immediately preceded by a comma, or when the preceding non-comma token is not a recognized symbol. Tests were added for whitespace preservation, shadow --theme(...) cases, w-[calc(...)] normalization, and unnecessary wrapping removal across strategy variants.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary change: improving whitespace handling during canonicalization, which is the core objective of the PR.
Description check ✅ Passed The description clearly explains the bug being fixed with concrete examples and details the readability improvements, including appropriate edge cases where parentheses are not introduced.
Linked Issues check ✅ Passed The PR implementation addresses the core requirement from issue #1544: preserving significant whitespace in selectors to generate valid canonical class suggestions, as demonstrated by test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing whitespace handling during canonicalization and improving readability with parentheses wrapping, with no unrelated modifications detected.

✏️ 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/candidate.ts (1)

7-7: Consider using a type-only import for ValueFunctionNode.

At Line 7, ValueFunctionNode is used only as a type (satisfies ValueFunctionNode). While the current configuration does not require it, using import type clarifies intent and is a TypeScript best practice.

Suggested change
-import { ValueFunctionNode } from './value-parser'
+import type { ValueFunctionNode } from './value-parser'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tailwindcss/src/candidate.ts` at line 7, Change the import of
ValueFunctionNode to a type-only import since it is only used as a type (you can
see it used with "satisfies ValueFunctionNode"); update the import statement in
packages/tailwindcss/src/candidate.ts to use "import type { ValueFunctionNode }"
so the compiler and bundlers treat it as a type-only import and clarify intent.
🤖 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/candidate.ts`:
- Line 7: Change the import of ValueFunctionNode to a type-only import since it
is only used as a type (you can see it used with "satisfies ValueFunctionNode");
update the import statement in packages/tailwindcss/src/candidate.ts to use
"import type { ValueFunctionNode }" so the compiler and bundlers treat it as a
type-only import and clarify intent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 67390443-ce28-4561-804f-0251b2bd0ae5

📥 Commits

Reviewing files that changed from the base of the PR and between 107e451 and 717fa24.

📒 Files selected for processing (2)
  • packages/tailwindcss/src/candidate.ts
  • packages/tailwindcss/src/canonicalize-candidates.test.ts

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/candidate.ts (1)

1101-1134: Broaden predecessor detection to cover compact operator/function forms.

The current check only inspects idx - 2, so cases where the symbol is immediately adjacent (no separator) can skip wrapping unexpectedly.

♻️ Suggested patch
-      let previousPrevious = parentArray[idx - 2]
-      if (previousPrevious && !symbols.has(previousPrevious.value)) return
+      let tokenBeforeFunction =
+        previous?.kind === 'separator' ? parentArray[idx - 2] : previous
+      if (tokenBeforeFunction && !symbols.has(tokenBeforeFunction.value)) return
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tailwindcss/src/candidate.ts` around lines 1101 - 1134, The
predecessor detection currently only checks parentArray[idx - 2]
(previousPrevious) which misses compact forms where a symbol is adjacent without
an intervening separator; replace that single-index check with a backwards scan
from idx - 1 to find the nearest non-separator token (skip separators like ','
or other separator nodes) and then test symbols.has(found.value) to decide
wrapping; update the logic around node.kind/node.value.startsWith('--') and the
use of idx/parentArray so that if no symbol is found you return (no wrap) and if
a symbol is found you proceed to the existing WalkAction.ReplaceSkip with the
ValueFunctionNode.
🤖 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/candidate.ts`:
- Around line 1101-1134: The predecessor detection currently only checks
parentArray[idx - 2] (previousPrevious) which misses compact forms where a
symbol is adjacent without an intervening separator; replace that single-index
check with a backwards scan from idx - 1 to find the nearest non-separator token
(skip separators like ',' or other separator nodes) and then test
symbols.has(found.value) to decide wrapping; update the logic around
node.kind/node.value.startsWith('--') and the use of idx/parentArray so that if
no symbol is found you return (no wrap) and if a symbol is found you proceed to
the existing WalkAction.ReplaceSkip with the ValueFunctionNode.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1b75fe17-1fd6-4195-a3f8-f65bf750a5df

📥 Commits

Reviewing files that changed from the base of the PR and between 717fa24 and 51b92c8.

📒 Files selected for processing (1)
  • packages/tailwindcss/src/candidate.ts

@RobinMalfait RobinMalfait merged commit 3aac5da into main Apr 26, 2026
9 checks passed
@RobinMalfait RobinMalfait deleted the fix/improve-whitespace-handling branch April 26, 2026 22:20
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.

Invalid fix for suggestCanonicalClasses with white spaces in selector

1 participant