Skip to content

Commit a6231b7

Browse files
Ensure auto complete suggestions work when using matchUtilities (#14589)
Discovered `matchUtilities(…)` wasn't populating intellisense on v4 when working on Tailwind Play earlier today. This PR fixes this so plugins using matchUtilities also have intellisense suggestions.
1 parent dc69802 commit a6231b7

File tree

3 files changed

+203
-0
lines changed

3 files changed

+203
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121
- Ensure the CSS `theme()` function resolves to the right value in some compatibility situations ([#14614](https://github.com/tailwindlabs/tailwindcss/pull/14614))
2222
- Fix issue that could cause the CLI to crash when files are deleted while watching ([#14616](https://github.com/tailwindlabs/tailwindcss/pull/14616))
2323
- Ensure custom variants using the JS API have access to modifiers ([#14637](https://github.com/tailwindlabs/tailwindcss/pull/14637))
24+
- Ensure auto complete suggestions work when using `matchUtilities` ([#14589](https://github.com/tailwindlabs/tailwindcss/pull/14589))
2425
- _Upgrade (experimental)_: Ensure CSS before a layer stays unlayered when running codemods ([#14596](https://github.com/tailwindlabs/tailwindcss/pull/14596))
2526
- _Upgrade (experimental)_: Resolve issues where some prefixed candidates were not properly migrated ([#14600](https://github.com/tailwindlabs/tailwindcss/pull/14600))
2627

packages/tailwindcss/src/compat/plugin-api.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,32 @@ export function buildPluginApi(
341341
designSystem.utilities.functional(name, compileFn, {
342342
types,
343343
})
344+
345+
designSystem.utilities.suggest(name, () => {
346+
let values = options?.values ?? {}
347+
let valueKeys = new Set<string | null>(Object.keys(values))
348+
349+
// The `__BARE_VALUE__` key is a special key used to ensure bare values
350+
// work even with legacy configs and plugins
351+
valueKeys.delete('__BARE_VALUE__')
352+
353+
// The `DEFAULT` key is represented as `null` in the utility API
354+
if (valueKeys.has('DEFAULT')) {
355+
valueKeys.delete('DEFAULT')
356+
valueKeys.add(null)
357+
}
358+
359+
let modifiers = options?.modifiers ?? {}
360+
let modifierKeys = modifiers === 'any' ? [] : Object.keys(modifiers)
361+
362+
return [
363+
{
364+
supportsNegative: options?.supportsNegativeValues ?? false,
365+
values: Array.from(valueKeys),
366+
modifiers: modifierKeys,
367+
},
368+
]
369+
})
344370
}
345371
},
346372

packages/tailwindcss/src/intellisense.test.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect, test } from 'vitest'
22
import { __unstable__loadDesignSystem } from '.'
33
import { buildDesignSystem } from './design-system'
4+
import plugin from './plugin'
45
import { Theme } from './theme'
56

67
const css = String.raw
@@ -174,3 +175,178 @@ test('Utilities, when marked as important, show as important in intellisense', a
174175
]
175176
`)
176177
})
178+
179+
test('Static utilities from plugins are listed in hovers and completions', async () => {
180+
let input = css`
181+
@import 'tailwindcss/utilities';
182+
@plugin "./plugin.js"l;
183+
`
184+
185+
let design = await __unstable__loadDesignSystem(input, {
186+
loadStylesheet: async (_, base) => ({
187+
base,
188+
content: '@tailwind utilities;',
189+
}),
190+
loadModule: async () => ({
191+
base: '',
192+
module: plugin(({ addUtilities }) => {
193+
addUtilities({
194+
'.custom-utility': {
195+
color: 'red',
196+
},
197+
})
198+
}),
199+
}),
200+
})
201+
202+
expect(design.candidatesToCss(['custom-utility'])).toMatchInlineSnapshot(`
203+
[
204+
".custom-utility {
205+
color: red;
206+
}
207+
",
208+
]
209+
`)
210+
211+
expect(design.getClassList().map((entry) => entry[0])).toContain('custom-utility')
212+
})
213+
214+
test('Functional utilities from plugins are listed in hovers and completions', async () => {
215+
let input = css`
216+
@import 'tailwindcss/utilities';
217+
@plugin "./plugin.js"l;
218+
`
219+
220+
let design = await __unstable__loadDesignSystem(input, {
221+
loadStylesheet: async (_, base) => ({
222+
base,
223+
content: '@tailwind utilities;',
224+
}),
225+
loadModule: async () => ({
226+
base: '',
227+
module: plugin(({ matchUtilities }) => {
228+
matchUtilities(
229+
{
230+
'custom-1': (value) => ({
231+
color: value,
232+
}),
233+
},
234+
{
235+
values: {
236+
red: '#ff0000',
237+
green: '#ff0000',
238+
},
239+
},
240+
)
241+
242+
matchUtilities(
243+
{
244+
'custom-2': (value, { modifier }) => ({
245+
color: `${value} / ${modifier ?? '0%'}`,
246+
}),
247+
},
248+
{
249+
values: {
250+
red: '#ff0000',
251+
green: '#ff0000',
252+
},
253+
modifiers: {
254+
'50': '50%',
255+
'75': '75%',
256+
},
257+
},
258+
)
259+
260+
matchUtilities(
261+
{
262+
'custom-3': (value, { modifier }) => ({
263+
color: `${value} / ${modifier ?? '0%'}`,
264+
}),
265+
},
266+
{
267+
values: {
268+
red: '#ff0000',
269+
green: '#ff0000',
270+
},
271+
modifiers: 'any',
272+
},
273+
)
274+
}),
275+
}),
276+
})
277+
278+
expect(design.candidatesToCss(['custom-1-red', 'custom-1-green', 'custom-1-unknown']))
279+
.toMatchInlineSnapshot(`
280+
[
281+
".custom-1-red {
282+
color: #ff0000;
283+
}
284+
",
285+
".custom-1-green {
286+
color: #ff0000;
287+
}
288+
",
289+
null,
290+
]
291+
`)
292+
293+
expect(design.candidatesToCss(['custom-2-red', 'custom-2-green', 'custom-2-unknown']))
294+
.toMatchInlineSnapshot(`
295+
[
296+
".custom-2-red {
297+
color: #ff0000 / 0%;
298+
}
299+
",
300+
".custom-2-green {
301+
color: #ff0000 / 0%;
302+
}
303+
",
304+
null,
305+
]
306+
`)
307+
308+
expect(design.candidatesToCss(['custom-2-red/50', 'custom-2-red/75', 'custom-2-red/unknown']))
309+
.toMatchInlineSnapshot(`
310+
[
311+
".custom-2-red\\/50 {
312+
color: #ff0000 / 50%;
313+
}
314+
",
315+
".custom-2-red\\/75 {
316+
color: #ff0000 / 75%;
317+
}
318+
",
319+
null,
320+
]
321+
`)
322+
323+
let classMap = new Map(design.getClassList())
324+
let classNames = Array.from(classMap.keys())
325+
326+
// matchUtilities without modifiers
327+
expect(classNames).toContain('custom-1-red')
328+
expect(classMap.get('custom-1-red')?.modifiers).toEqual([])
329+
330+
expect(classNames).toContain('custom-1-green')
331+
expect(classMap.get('custom-1-green')?.modifiers).toEqual([])
332+
333+
expect(classNames).not.toContain('custom-1-unknown')
334+
335+
// matchUtilities with a set list of modifiers
336+
expect(classNames).toContain('custom-2-red')
337+
expect(classMap.get('custom-2-red')?.modifiers).toEqual(['50', '75'])
338+
339+
expect(classNames).toContain('custom-2-green')
340+
expect(classMap.get('custom-2-green')?.modifiers).toEqual(['50', '75'])
341+
342+
expect(classNames).not.toContain('custom-2-unknown')
343+
344+
// matchUtilities with a any modifiers
345+
expect(classNames).toContain('custom-3-red')
346+
expect(classMap.get('custom-3-red')?.modifiers).toEqual([])
347+
348+
expect(classNames).toContain('custom-3-green')
349+
expect(classMap.get('custom-3-green')?.modifiers).toEqual([])
350+
351+
expect(classNames).not.toContain('custom-3-unknown')
352+
})

0 commit comments

Comments
 (0)