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