From a6dd67317b28fad0a756e317431a54d5a172c6e5 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 27 Aug 2025 11:50:43 -0400 Subject: [PATCH 1/2] Suggest default variants when they also support arbitrary values --- .../tests/completions/completions.test.js | 69 +++++++++++++++++++ .../src/completionProvider.ts | 40 +++++++---- 2 files changed, 95 insertions(+), 14 deletions(-) diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js index 8fb828cc..11957475 100644 --- a/packages/tailwindcss-language-server/tests/completions/completions.test.js +++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js @@ -1082,3 +1082,72 @@ defineTest({ expect(Object.fromEntries(requests)).toEqual(map) }, }) + +defineTest({ + name: 'Completions show for variants that support default values *and* arbitrary values', + fs: { + 'app.css': css` + @import 'tailwindcss'; + @plugin "./plugin.js"; + `, + 'plugin.js': js` + export default function ({ matchVariant }) { + matchVariant('foo', (value) => ':is(' + value + ')', { + values: { + DEFAULT: 'default', + a: 'a', + } + }) + + matchVariant('bar', (value) => { + if (!value) return [] + return ':is(' + value + ')' + }, { + values: { + a: 'a', + } + }) + } + `, + }, + prepare: async ({ root }) => ({ client: await createClient({ root }) }), + handle: async ({ client }) => { + let document = await client.open({ + lang: 'html', + text: html`
`, + }) + + //
+ // ^ + let list = await document.completions({ line: 0, character: 12 }) + let items = list?.items ?? [] + + // The default version of this variant is suggested + let item1 = items.find((item) => item.label.startsWith('foo:')) + expect(item1?.detail).toEqual(':is(default)') + + // As are any values + // TODO: This test requires Tailwind CSS v4.1.13 + // let item2 = items.find((item) => item.label.startsWith('foo-a:')) + // expect(item2?.detail).toEqual(':is(a)') + + // Ditto with arbitrary values + let item3 = items.find((item) => item.label.startsWith('foo-[]:')) + expect(item3).toBeDefined() + expect(item3?.detail).toEqual(undefined) + + // This variant doesn't have a default so it's omitted + let item4 = items.find((item) => item.label.startsWith('bar:')) + expect(item4).not.toBeDefined() + + // But does have a value so it is + // TODO: This test requires Tailwind CSS v4.1.13 + // let item5 = items.find((item) => item.label.startsWith('bar-a:')) + // expect(item5?.detail).toEqual(':is(a)') + + // And it supports arbitrary values so it should be as well + let item6 = items.find((item) => item.label.startsWith('bar-[]:')) + expect(item6).toBeDefined() + expect(item6?.detail).toEqual(undefined) + }, +}) diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts index 570ca535..f64eb2fb 100644 --- a/packages/tailwindcss-language-service/src/completionProvider.ts +++ b/packages/tailwindcss-language-service/src/completionProvider.ts @@ -188,24 +188,36 @@ export function completionsFromClassList( // }, }), ) - } else { - let resultingVariants = [...existingVariants, variant.name] + } - let selectors: string[] = [] + let hasDefault = false + let resultingVariants = [...existingVariants, variant.name] - try { - selectors = variant.selectors() - } catch (err) { - // If the selectors function fails we don't want to crash the whole completion process - console.log('Error while trying to get selectors for variant') - console.log({ - variant, - err, - }) + let selectors: string[] = [] - continue - } + try { + selectors = variant.selectors() + hasDefault = true + } catch (err) { + // If the selectors function fails we don't want to crash the whole completion process + console.log('Error while trying to get selectors for variant') + console.log({ + variant, + err, + }) + + continue + } + // Commentary: + // The check for `selectors.length` was previously omitted because of the `force` variant that + // existed before the v4.0 release. It was used to place a utility after other base utilities + // and before any using variants. It was dropped before v4.0. An empty selector set is a good + // marker for "this outputs nothing" and as such should be omitted from suggestions. + // + // This does mean that the force variant won't be suggested if IntelliSense is used with + // earlier pre-release versions but this is fine. + if (hasDefault && selectors.length > 0) { items.push( variantItem({ label: `${variant.name}${sep}`, From b7ccf3edc922d4c0ab9af35978f31116b4b05b74 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 27 Aug 2025 12:30:49 -0400 Subject: [PATCH 2/2] 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 ec1f3408..f310e488 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -3,6 +3,7 @@ ## Prerelease - Publish our fork of the CSS language server ([#1437](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1437)) +- Suggest default variant values when they also support arbitrary values ([#1439](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1439)) ## 0.14.26