8000 Expose `index` and `siblings` on walk context (#20109) · browner12/tailwindcss@749c45e · GitHub
Skip to content

Commit 749c45e

Browse files
authored
Expose index and siblings on walk context (tailwindlabs#20109)
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: ```ts 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: ```ts 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
1 parent 982e920 commit 749c45e

6 files changed

Lines changed: 48 additions & 38 deletions

File tree

packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,10 @@ function substituteFunctionsInValue(
211211
fallbackValues.length > 0 ? handle(path, ValueParser.toCss(fallbackValues)) : handle(path)
212212
if (replacement === null) return
213213

214-
if (ctx.parent) {
215-
let idx = ctx.parent.nodes.indexOf(node) - 1
214+
{
215+
let idx = ctx.index - 1
216216
while (idx !== -1) {
217-
let previous = ctx.parent.nodes[idx]
217+
let previous = ctx.siblings[idx]
218218
// Skip the space separator
219219
if (previous.kind === 'separator' && previous.value.trim() === '') {
220220
idx -= 1

packages/tailwindcss/src/ast.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ export function cssContext(
190190
): VisitContext<AstNode> & { context: Record<string, string | boolean> } {
191191
return {
192192
depth: ctx.depth,
193+
index: ctx.index,
194+
siblings: ctx.siblings,
193195
get context() {
194196
let context: Record<string, string | boolean> = {}
195197
for (let child of ctx.path()) {

packages/tailwindcss/src/candidate.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,37 +1048,35 @@ const printArbitraryValueCache = new DefaultMap<string, string>((input) => {
10481048
'/',
10491049
])
10501050
walk(ast, (node, ctx) => {
1051-
let parentArray = ctx.parent === null ? ast : (ctx.parent.nodes ?? [])
1052-
10531051
// Handle operators (e.g.: inside of `calc(…)`)
10541052
if (node.kind === 'word' && symbols.has(node.value)) {
1055-
let idx = parentArray.indexOf(node) ?? -1
1053+
let idx = ctx.index
10561054

10571055
// This should not be possible
10581056
if (idx === -1) return
10591057

10601058
// a + b
10611059
// ^ node
10621060
// ^ previous (whitespace)
1063-
let previous = parentArray[idx - 1]
1061+
let previous = ctx.siblings[idx - 1]
10641062
if (previous?.kind !== 'separator' || previous.value !== ' ') return
10651063

10661064
// a + b
10671065
// ^ node
10681066
// ^ next (whitespace)
1069-
let next = parentArray[idx + 1]
1067+
let next = ctx.siblings[idx + 1]
10701068
if (next?.kind !== 'separator' || next.value !== ' ') return
10711069

10721070
// a + b
10731071
// ^ node
10741072
// ^ previous (node)
1075-
let previousPrevious = parentArray[idx - 2]
1073+
let previousPrevious = ctx.siblings[idx - 2]
10761074
if (previousPrevious && symbols.has(previousPrevious.value)) return
10771075

10781076
// a + b
10791077
// ^ node
10801078
// ^ next (node)
1081-
let nextNext = parentArray[idx + 2]
1079+
let nextNext = ctx.siblings[idx + 2]
10821080
if (nextNext && symbols.has(nextNext.value)) return
10831081

10841082
drop.add(previous)
@@ -1087,7 +1085,7 @@ const printArbitraryValueCache = new DefaultMap<string, string>((input) => {
10871085

10881086
// Leading and trailing whitespace
10891087
else if (node.kind === 'separator' && node.value.length > 0 && node.value.trim() === '') {
1090-
if (parentArray[0] === node || parentArray[parentArray.length - 1] === node) {
1088+
if (ctx.siblings[0] === node || ctx.siblings[ctx.siblings.length - 1] === node) {
10911089
drop.add(node)
10921090
}
10931091
}
@@ -1101,7 +1099,7 @@ const printArbitraryValueCache = new DefaultMap<string, string>((input) => {
11011099
// Wrap custom functions starting with `--`, in parentheses if preceeded by
11021100
// a symbol. E.g.: `calc(100%---spacing(2))` → `calc(100%-(--spacing(2)))`
11031101
else if (node.kind === 'function' && node.value.startsWith('--')) {
1104-
let idx = parentArray.indexOf(node) ?? -1
1102+
let idx = ctx.index
11051103

11061104
// When it's the first argument, then we don't have to wrap it in `(…)`
11071105
//
@@ -1115,15 +1113,15 @@ const printArbitraryValueCache = new DefaultMap<string, string>((input) => {
11151113
//
11161114
// E.g.: `min(100%,--spacing(2))` is readable, in fact
11171115
// `min(100%,(--spacing(2)))` would make it worse
1118-
let previous = parentArray[idx - 1]
1116+
let previous = ctx.siblings[idx - 1]
11191117
if (previous?.kind === 'separator' && previous.value === ',') return
11201118

11211119
// When it's part of a bigger list, aka no special symbols were used, then
11221120
// we don't have to wrap it either.
11231121
//
11241122
// E.g.: `shadow-[inset_0px_1px_--theme(--color-white/15%)]`, wrapping would look unnecessary:
11251123
// `shadow-[inset_0px_1px_(--theme(--color-white/15%))]`
1126-
let previousPrevious = parentArray[idx - 2]
1124+
let previousPrevious = ctx.siblings[idx - 2]
11271125
if (previousPrevious && !symbols.has(previousPrevious.value)) return
11281126

11291127
return WalkAction.ReplaceSkip({

packages/tailwindcss/src/canonicalize-candidates.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -980,10 +980,10 @@ function substituteFunctionsInValue(
980980
fallbackValues.length > 0 ? handle(path, ValueParser.toCss(fallbackValues)) : handle(path)
981981
if (replacement === null) return
982982

983-
if (ctx.parent) {
984-
let idx = ctx.parent.nodes.indexOf(node) - 1
983+
{
984+
let idx = ctx.index - 1
985985
while (idx !== -1) {
986-
let previous = ctx.parent.nodes[idx]
986+
let previous = ctx.siblings[idx]
987987
// Skip the space separator
988988
if (previous.kind === 'separator' && previous.value.trim() === '') {
989989
idx -= 1
@@ -2405,7 +2405,7 @@ function canonicalizeAst(designSystem: DesignSystem, ast: AstNode[], options: Si
24052405
// Ignore `--tw-{property}` if `{property}` exists with the same value
24062406
if (node.property.startsWith('--tw-')) {
24072407
if (
2408-
(ctx.parent?.nodes ?? []).some(
2408+
ctx.siblings.some(
24092409
(sibling) =>
24102410
sibling.kind === 'declaration' &&
24112411
node.value === sibling.value &&

packages/tailwindcss/src/walk.test.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -956,33 +956,35 @@ describe('AST Enter & Exit', () => {
956956
let visited: string[] = []
957957
walk(ast, {
958958
enter(node, ctx) {
959-
visited.push(`${' '.repeat(ctx.depth)} Enter(${node.kind})`)
959+
expect(ctx.index).toEqual(ctx.siblings.indexOf(node))
960+
visited.push(`${' '.repeat(ctx.depth)} Enter(${node.kind} @ ${ctx.index})`)
960961
},
961962
exit(node, ctx) {
962-
visited.push(`${' '.repeat(ctx.depth)} Exit(${node.kind})`)
963+
expect(ctx.index).toEqual(ctx.siblings.indexOf(node))
964+
visited.push(`${' '.repeat(ctx.depth)} Exit(${node.kind} @ ${ctx.index})`)
963965
},
964966
})
965967

966968
expect(`\n${visited.join('\n')}\n`).toMatchInlineSnapshot(`
967969
"
968-
Enter(a)
969-
Enter(b)
970-
Enter(c)
971-
Exit(c)
972-
Exit(b)
973-
Enter(d)
974-
Enter(e)
975-
Enter(f)
976-
Exit(f)
977-
Exit(e)
978-
Exit(d)
979-
Enter(g)
980-
Enter(h)
981-
Exit(h)
982-
Exit(g)
983-
Exit(a)
984-
Enter(i)
985-
Exit(i)
970+
Enter(a @ 0)
971+
Enter(b @ 0)
972+
Enter(c @ 0)
973+
Exit(c @ 0)
974+
Exit(b @ 0)
975+
Enter(d @ 1)
976+
Enter(e @ 0)
977+
Enter(f @ 0)
978+
Exit(f @ 0)
979+
Exit(e @ 0)
980+
Exit(d @ 1)
981+
Enter(g @ 2)
982+
Enter(h @ 0)
983+
Exit(h @ 0)
984+
Exit(g @ 2)
985+
Exit(a @ 0)
986+
Enter(i @ 1)
987+
Exit(i @ 1)
986988
"
987989
`)
988990
})

packages/tailwindcss/src/walk.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ type Parent<T> = T & { nodes: T[] }
3636
export interface VisitContext<T> {
3737
parent: Parent<T> | null
3838
depth: number
39+
index: number
40+
siblings: T[]
3941
path: () => T[]
4042
}
4143

@@ -68,6 +70,8 @@ function walkImplementation<T extends { nodes?: T[] }>(
6870
let ctx: VisitContext<T> = {
6971
parent: null,
7072
depth: 0,
73+
index: 0,
74+
siblings: ast,
7175
path() {
7276
let path: T[] = []
7377

@@ -99,9 +103,12 @@ function walkImplementation<T extends { nodes?: T[] }>(
99103
}
100104

101105
ctx.parent = parent
106+
ctx.siblings = nodes
102107

103108
// Enter phase (offsets are positive)
104109
if (offset >= 0) {
110+
ctx.index = offset
111+
105112
let node = nodes[offset]
106113
let result = enter(node, ctx) ?? WalkAction.Continue
107114

@@ -155,6 +162,7 @@ function walkImplementation<T extends { nodes?: T[] }>(
155162

156163
// Exit phase for nodes[~offset]
157164
let index = ~offset // Two's complement to get original offset
165+
ctx.index = index
158166
let node = nodes[index]
159167

160168
let result = exit(node, ctx) ?? WalkAction.Continue

0 commit comments

Comments
 (0)