Skip to content

Commit 820d907

Browse files
Expose candidatesToAst to the language server (#19405)
This will be used to improve performance and potentially enable future features that require generated CSS source locations. Note: This is still 100% internal API. You can only access this via `__unstable__loadDesignSystem` for a reason. We may chance the structure of the arguments and/or return values as needed.
1 parent 478e959 commit 820d907

File tree

3 files changed

+73
-30
lines changed

3 files changed

+73
-30
lines changed

packages/tailwindcss/src/design-system.ts

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Polyfills } from '.'
2-
import { optimizeAst, toCss } from './ast'
2+
import { optimizeAst, toCss, type AstNode } from './ast'
33
import {
44
parseCandidate,
55
parseVariant,
@@ -19,11 +19,13 @@ import {
1919
type VariantEntry,
2020
} from './intellisense'
2121
import { getClassOrder } from './sort'
22+
import type { SourceLocation } from './source-maps/source'
2223
import { Theme, ThemeOptions, type ThemeKey } from './theme'
2324
import { Utilities, createUtilities, withAlpha } from './utilities'
2425
import { DefaultMap } from './utils/default-map'
2526
import { extractUsedVariables } from './utils/variables'
2627
import { Variants, createVariants, substituteAtVariant } from './variants'
28+
import { WalkAction, walk } from './walk'
2729

2830
export const enum CompileAstFlags {
2931
None = 0,
@@ -59,12 +61,16 @@ export type DesignSystem = {
5961

6062
// Used by IntelliSense
6163
candidatesToCss(classes: string[]): (string | null)[]
64+
candidatesToAst(classes: string[]): AstNode[][]
6265

6366
// General purpose storage
6467
storage: Record<symbol, unknown>
6568
}
6669

67-
export function buildDesignSystem(theme: Theme): DesignSystem {
70+
export function buildDesignSystem(
71+
theme: Theme,
72+
utilitiesSrc?: SourceLocation | undefined,
73+
): DesignSystem {
6874
let utilities = createUtilities(theme)
6975
let variants = createVariants(theme)
7076

@@ -109,6 +115,44 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
109115
}
110116
})
111117

118+
function candidatesToAst(classes: string[]): AstNode[][] {
119+
let result: AstNode[][] = []
120+
121+
for (let className of classes) {
122+
let wasValid = true
123+
124+
let { astNodes } = compileCandidates([className], designSystem, {
125+
onInvalidCandidate() {
126+
wasValid = false
127+
},
128+
})
129+
130+
if (utilitiesSrc) {
131+
walk(astNodes, (node) => {
132+
// We do this conditionally to preserve source locations from both
133+
// `@utility` and `@custom-variant`. Even though generated nodes are
134+
// cached this should be fine because `utilitiesNode.src` should not
135+
// change without a full rebuild which destroys the cache.
136+
node.src ??= utilitiesSrc
137+
return WalkAction.Continue
138+
})
139+
}
140+
141+
// Disable all polyfills to not unnecessarily pollute IntelliSense output
142+
astNodes = optimizeAst(astNodes, designSystem, Polyfills.None)
143+
144+
result.push(wasValid ? astNodes : [])
145+
}
146+
147+
return result
148+
}
149+
150+
function candidatesToCss(classes: string[]): (string | null)[] {
151+
return candidatesToAst(classes).map((nodes) => {
152+
return nodes.length > 0 ? toCss(nodes) : null
153+
})
154+
}
155+
112156
let designSystem: DesignSystem = {
113157
theme,
114158
utilities,
@@ -117,30 +161,8 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
117161
invalidCandidates: new Set(),
118162
important: false,
119163

120-
candidatesToCss(classes: string[]) {
121-
let result: (string | null)[] = []
122-
123-
for (let className of classes) {
124-
let wasInvalid = false
125-
126-
let { astNodes } = compileCandidates([className], this, {
127-
onInvalidCandidate() {
128-
wasInvalid = true
129-
},
130-
})
131-
132-
// Disable all polyfills to not unnecessarily pollute IntelliSense output
133-
astNodes = optimizeAst(astNodes, designSystem, Polyfills.None)
134-
135-
if (astNodes.length === 0 || wasInvalid) {
136-
result.push(null)
137-
} else {
138-
result.push(toCss(astNodes))
139-
}
140-
}
141-
142-
return result
143-
},
164+
candidatesToCss,
165+
candidatesToAst,
144166

145167
getClassOrder(classes) {
146168
return getClassOrder(this, classes)

packages/tailwindcss/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ async function parseCss(
595595
}
596596
})
597597

598-
let designSystem = buildDesignSystem(theme)
598+
let designSystem = buildDesignSystem(theme, utilitiesNode?.src)
599599

600600
if (important) {
601601
designSystem.important = important
@@ -855,7 +855,7 @@ export async function compile(
855855
}
856856

857857
export async function __unstable__loadDesignSystem(css: string, opts: CompileOptions = {}) {
858-
let result = await parseCss(CSS.parse(css), opts)
858+
let result = await parseCss(CSS.parse(css, { from: opts.from }), opts)
859859
return result.designSystem
860860
}
861861

packages/tailwindcss/src/intellisense.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect, test } from 'vitest'
22
import { __unstable__loadDesignSystem } from '.'
3+
import { decl, rule } from './ast'
34
import plugin from './plugin'
45
import { ThemeOptions } from './theme'
56

@@ -165,6 +166,26 @@ test('Can produce CSS per candidate using `candidatesToCss`', async () => {
165166
`)
166167
})
167168

169+
test('Can produce AST per candidate using `candidatesToAst`', async () => {
170+
let design = await loadDesignSystem()
171+
design.invalidCandidates = new Set(['bg-[#fff]'])
172+
173+
expect(
174+
design.candidatesToAst(['underline', 'i-dont-exist', 'bg-[#fff]', 'bg-[#000]', 'text-xs']),
175+
).toEqual([
176+
[rule('.underline', [decl('text-decoration-line', 'underline')])],
177+
[],
178+
[],
179+
[rule('.bg-\\[\\#000\\]', [decl('background-color', '#000')])],
180+
[
181+
rule('.text-xs', [
182+
decl('font-size', 'var(--text-xs)'),
183+
decl('line-height', 'var(--tw-leading, var(--text-xs--line-height))'),
184+
]),
185+
],
186+
])
187+
})
188+
168189
test('Utilities do not show wrapping selector in intellisense', async () => {
169190
let input = css`
170191
@import 'tailwindcss/utilities';
@@ -238,7 +259,7 @@ test('Utilities, when marked as important, show as important in intellisense', a
238259
test('Static utilities from plugins are listed in hovers and completions', async () => {
239260
let input = css`
240261
@import 'tailwindcss/utilities';
241-
@plugin "./plugin.js"l;
262+
@plugin "./plugin.js";
242263
`
243264

244265
let design = await __unstable__loadDesignSystem(input, {
@@ -275,7 +296,7 @@ test('Static utilities from plugins are listed in hovers and completions', async
275296
test('Functional utilities from plugins are listed in hovers and completions', async () => {
276297
let input = css`
277298
@import 'tailwindcss/utilities';
278-
@plugin "./plugin.js"l;
299+
@plugin "./plugin.js";
279300
`
280301

281302
let design = await __unstable__loadDesignSystem(input, {

0 commit comments

Comments
 (0)