From 5be733c4a521bd6bfb9ebb6839b725042c969574 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 22 Apr 2025 12:43:44 -0400
Subject: [PATCH 1/4] Guard against recursive theme key lookup
---
.../src/util/rewriting/var-fallbacks.ts | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts b/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts
index 91dcc91d..2eb8c918 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts
@@ -3,14 +3,25 @@ import { resolveVariableValue } from './lookup'
import { replaceCssVars } from './replacements'
export function replaceCssVarsWithFallbacks(state: State, str: string): string {
+ let seen = new Set()
+
return replaceCssVars(str, {
replace({ name, fallback }) {
// Replace with the value from the design system first. The design system
// take precedences over other sources as that emulates the behavior of a
// browser where the fallback is only used if the variable is defined.
if (state.designSystem && name.startsWith('--')) {
+ // TODO: This isn't quite right as we might skip expanding a variable
+ // that should be expanded
+ if (seen.has(name)) return null
let value = resolveVariableValue(state.designSystem, name)
- if (value !== null) return value
+ if (value !== null) {
+ if (value.includes('var(')) {
+ seen.add(name)
+ }
+
+ return value
+ }
}
if (fallback) {
From eae26f8747592ddefd391bc7dfdde1d7396d74f2 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 22 Apr 2025 12:50:19 -0400
Subject: [PATCH 2/4] Add test
---
.../src/util/rewriting/index.test.ts | 40 +++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts b/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
index 3adbdb44..d0eabbda 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
@@ -84,6 +84,46 @@ test('replacing CSS variables with their fallbacks (when they have them)', () =>
expect(replaceCssVarsWithFallbacks(state, 'var(--circular-3)')).toBe('var(--circular-2)')
})
+test('recursive theme replacements', () => {
+ let map = new Map([
+ ['--color-a', 'var(--color-a)'],
+ ['--color-b', 'rgb(var(--color-b))'],
+ ['--color-c', 'rgb(var(--channel) var(--channel) var(--channel))'],
+ ['--channel', '255'],
+
+ ['--color-d', 'rgb(var(--indirect) var(--indirect) var(--indirect))'],
+ ['--indirect', 'var(--channel)'],
+ ['--channel', '255'],
+
+ ['--mutual-a', 'calc(var(--mutual-b) * 1)'],
+ ['--mutual-b', 'calc(var(--mutual-a) + 1)'],
+ ])
+
+ let state: State = {
+ enabled: true,
+ designSystem: {
+ theme: { prefix: null } as any,
+ resolveThemeValue: (name) => map.get(name) ?? null,
+ } as DesignSystem,
+ }
+
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-a)')).toBe('var(--color-a)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-b)')).toBe('rgb(var(--color-b))')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-c)')).toBe('rgb(255 255 255)')
+
+ // This one is wrong but fixing it without breaking the infinite recursion guard is complex
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-d)')).toBe(
+ 'rgb(255 var(--indirect) var(--indirect))',
+ )
+
+ expect(replaceCssVarsWithFallbacks(state, 'var(--mutual-a)')).toBe(
+ 'calc(calc(var(--mutual-a) + 1) * 1)',
+ )
+ expect(replaceCssVarsWithFallbacks(state, 'var(--mutual-b)')).toBe(
+ 'calc(calc(var(--mutual-b) * 1) + 1)',
+ )
+})
+
test('Evaluating CSS calc expressions', () => {
expect(replaceCssCalc('calc(1px + 1px)', (node) => evaluateExpression(node.value))).toBe('2px')
expect(replaceCssCalc('calc(1px * 4)', (node) => evaluateExpression(node.value))).toBe('4px')
From 3590c1c9e4274185c6de87163e5887d868474c36 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 22 Apr 2025 12:55:33 -0400
Subject: [PATCH 3/4] Update tests
---
.../src/util/rewriting/index.test.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts b/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
index d0eabbda..de51a2b7 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
@@ -79,9 +79,9 @@ test('replacing CSS variables with their fallbacks (when they have them)', () =>
expect(replaceCssVarsWithFallbacks(state, 'var(--level-3)')).toBe('blue')
// Circular replacements don't cause infinite loops
- expect(replaceCssVarsWithFallbacks(state, 'var(--circular-1)')).toBe('var(--circular-3)')
- expect(replaceCssVarsWithFallbacks(state, 'var(--circular-2)')).toBe('var(--circular-1)')
- expect(replaceCssVarsWithFallbacks(state, 'var(--circular-3)')).toBe('var(--circular-2)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--circular-1)')).toBe('var(--circular-1)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--circular-2)')).toBe('var(--circular-2)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--circular-3)')).toBe('var(--circular-3)')
})
test('recursive theme replacements', () => {
From ed6a56607dc2e0fb596d3bd6385c82c22095bb10 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 23 Apr 2025 11:14:25 -0400
Subject: [PATCH 4/4] Update changelog
---
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 4162495c..a734adf3 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -4,6 +4,7 @@
- Warn when using a blocklisted class in v4 ([#1310](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1310))
- Support class function hovers in Svelte and HTML `` blocks ([#1311](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1311))
+- Guard against recursive theme key lookup ([#1332](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1332))
# 0.14.15