Skip to content

Expose index and siblings on walk context#20108

Closed
RobinMalfait wants to merge 3 commits into
mainfrom
feat/improve-walk
Closed

Expose index and siblings on walk context#20108
RobinMalfait wants to merge 3 commits into
mainfrom
feat/improve-walk

Conversation

@RobinMalfait
Copy link
Copy Markdown
Member

This PR is a small improvement of the current walk implementation where we will expose the index and the siblings on the current context.

During a walk, we walk over objects that contain a nodes: [] field. The ctx.parent that already exists is a reference to the parent node, but ctx.siblings is a reference to the ctx.parent.nodes.

The ctx.index is the index of the current node we are walking in the siblings array. This way we can prevent the awkward ctx.parent?.nodes.indexOf(node) which is a bit silly because we already know the nodes we're walking and its index...

The ctx.parent can be null, but the ctx.siblings will never be null, this can be seen in a situation like this:

let ast: AstNode = [nodeA, nodeB]

walk(ast, (node, ctx) => {
  if (node === nodeA) {
    ctx.parent === null; // Because there is no parent
    ctx.siblings === ast; // Because that's the current list we're looping over

    // Before this PR, we would have to do something like:
    let siblings = ctx.parent?.nodes ?? ast
  }
})

In the above example, the ast is a separately variable, but if this was inlined, we would run into some issues:

walk([nodeA, nodeB], (node, ctx) => {
  if (node === nodeA) {
    ctx.parent === null; // Because there is no parent
    ctx.siblings === ast; // Because that's the current list we're looping over

    // At this point, there is no way to get to the `[nodeA, nodeB]` list
    // without moving it to a variable first.
  }
})

So, this PR doesn't change much, the additional information we track is already known information that is now exposed to the caller of the walk function.

In this PR we did update some usages and got rid of some awkward ctx.parent?.nodes ?? [] and ctx.parent.nodes.indexOf(…) usages.

Test plan

  • All tests still pass as expected

@RobinMalfait RobinMalfait requested a review from a team as a code owner May 24, 2026 19:41
These are just references to other information we already have. But it
does mean that we can avoid things such as:

```
let idx = ctx.parent.nodes.indexOf(node)
```
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 24, 2026

Confidence Score: 5/5

Safe to merge. The new fields are additive and backward-compatible, and all existing call sites that use them produce the same results as the patterns they replace.

The changes are additive — new fields on an internal interface, with the walk engine setting them correctly in both enter and exit phases. Every updated call site is a straightforward mechanical substitution of an existing pattern. The test suite asserts ctx.index === ctx.siblings.indexOf(node) at every visited node, providing direct coverage of the invariant. No behavioral changes, no new edge cases introduced.

No files require special attention.

Reviews (2): Last reviewed commit: "use `ctx.index` and `ctx.siblings`" | Re-trigger Greptile

Comment thread packages/tailwindcss/src/canonicalize-candidates.ts Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: fdd2e3a3-eaca-4802-8812-6688c0c8aeae

📥 Commits

Reviewing files that changed from the base of the PR and between a9a939a and 68fefb0.

📒 Files selected for processing (3)
  • packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts
  • packages/tailwindcss/src/candidate.ts
  • packages/tailwindcss/src/canonicalize-candidates.ts

Walkthrough

This PR adds index and siblings to VisitContext, initializes and maintains them during walk enter/exit, and forwards them through cssContext. Tests were updated to assert ctx.index. Consumers were refactored: candidate printing, canonicalize-candidates, and the migrate-theme-to-var codemod now use ctx.index/ctx.siblings for sibling-aware neighbor detection, whitespace trimming, operator handling, and conditional parenthesization.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: exposing index and siblings on the walk context, which is the primary objective across all modified files.
Description check ✅ Passed The description clearly relates to the changeset, explaining the motivation for exposing index and siblings fields, providing concrete examples of improved patterns, and referencing test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/tailwindcss/src/canonicalize-candidates.ts`:
- Around line 983-1010: The loop that inspects the previous sibling in
canonicalize-candidates incorrectly initializes idx to ctx.index (causing it to
examine the current node) — change the initialization in the function/block that
declares let idx = ctx.index to start from the previous sibling (let idx =
ctx.index - 1) so the while loop checks ctx.siblings[idx] (and the subsequent
checks on previous.kind, previous.value, and potential wrapping of replacement)
operate on the actual previous node as intended.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e84b7102-ea21-4464-8265-13fe1f682ab4

📥 Commits

Reviewing files that changed from the base of the PR and between 982e920 and 07ee498.

📒 Files selected for processing (5)
  • packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts
  • packages/tailwindcss/src/candidate.ts
  • packages/tailwindcss/src/canonicalize-candidates.ts
  • packages/tailwindcss/src/walk.test.ts
  • packages/tailwindcss/src/walk.ts

Comment thread packages/tailwindcss/src/canonicalize-candidates.ts Outdated
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.

♻️ Duplicate comments (1)
packages/tailwindcss/src/canonicalize-candidates.ts (1)

983-1010: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Off-by-one: loop should start at ctx.index - 1, not ctx.index.

Starting at ctx.index means the first iteration checks the current theme(...) node itself rather than the previous sibling. Compare with migrate-theme-to-var.ts which correctly starts at ctx.index - 1.

🐛 Proposed fix
-      let idx = ctx.index
+      let idx = ctx.index - 1
       while (idx !== -1) {
         let previous = ctx.siblings[idx]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tailwindcss/src/canonicalize-candidates.ts` around lines 983 - 1010,
The loop's start index is off-by-one: initialize idx to ctx.index - 1 (not
ctx.index) so the loop examines the previous sibling rather than the current
node; update the initialization of idx in the block that uses ctx.siblings and
ctx.index, leaving the rest of the while loop logic (separator check, operator
regex test, wrapping replacement) unchanged so that parentheses are applied
based on the actual previous sibling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/tailwindcss/src/canonicalize-candidates.ts`:
- Around line 983-1010: The loop's start index is off-by-one: initialize idx to
ctx.index - 1 (not ctx.index) so the loop examines the previous sibling rather
than the current node; update the initialization of idx in the block that uses
ctx.siblings and ctx.index, leaving the rest of the while loop logic (separator
check, operator regex test, wrapping replacement) unchanged so that parentheses
are applied based on the actual previous sibling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 664cb7a5-6728-4c1e-b4e2-5d9a045e65bd

📥 Commits

Reviewing files that changed from the base of the PR and between 07ee498 and 3082806.

📒 Files selected for processing (5)
  • packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts
  • packages/tailwindcss/src/ast.ts
  • packages/tailwindcss/src/candidate.ts
  • packages/tailwindcss/src/canonicalize-candidates.ts
  • packages/tailwindcss/src/walk.ts

@RobinMalfait RobinMalfait force-pushed the feat/improve-walk branch 2 times, most recently from 2e12393 to a9a939a Compare May 24, 2026 19:50
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/tailwindcss/src/candidate.ts (1)

1102-1125: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Off-by-one in sibling lookups after ctx.index migration (Line 1102).

idx is already shifted (ctx.index - 1), but subsequent reads use idx - 1 / idx - 2, so the immediate previous sibling is skipped. This can mis-detect whether --* functions should be parenthesized.

🐛 Proposed fix
-      let idx = ctx.index - 1
+      let idx = ctx.index - 1

       // When it's the first argument, then we don't have to wrap it in `(…)`
@@
       if (idx <= 0) return
@@
-      let previous = ctx.siblings[idx - 1]
+      let previous = ctx.siblings[idx]
       if (previous?.kind === 'separator' && previous.value === ',') return
@@
-      let previousPrevious = ctx.siblings[idx - 2]
+      let previousPrevious = ctx.siblings[idx - 1]
       if (previousPrevious && !symbols.has(previousPrevious.value)) return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tailwindcss/src/candidate.ts` around lines 1102 - 1125, The idx
computation is off-by-one: change "let idx = ctx.index - 1" to "let idx =
ctx.index" so that the subsequent sibling lookups (previous = ctx.siblings[idx -
1] and previousPrevious = ctx.siblings[idx - 2]) refer to the correct neighbors;
keep or adjust the early-return check accordingly so it still guards
first-argument cases (i.e., ensure the boundary check uses the updated idx).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/tailwindcss/src/candidate.ts`:
- Around line 1102-1125: The idx computation is off-by-one: change "let idx =
ctx.index - 1" to "let idx = ctx.index" so that the subsequent sibling lookups
(previous = ctx.siblings[idx - 1] and previousPrevious = ctx.siblings[idx - 2])
refer to the correct neighbors; keep or adjust the early-return check
accordingly so it still guards first-argument cases (i.e., ensure the boundary
check uses the updated idx).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d7df8dd0-3aa4-4a62-be32-aa0e9f29e7c5

📥 Commits

Reviewing files that changed from the base of the PR and between 2e12393 and a9a939a.

📒 Files selected for processing (3)
  • packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts
  • packages/tailwindcss/src/candidate.ts
  • packages/tailwindcss/src/canonicalize-candidates.ts

@RobinMalfait RobinMalfait deleted the feat/improve-walk branch May 24, 2026 20:32
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.

1 participant