Skip to content

Commit 5f74714

Browse files
Prep for new Oxide API in v4.1 (#1284)
1 parent c6a602a commit 5f74714

File tree

22 files changed

+191
-559
lines changed

22 files changed

+191
-559
lines changed

packages/tailwindcss-language-server/src/oxide.ts

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ declare namespace OxideV2 {
3636
}
3737
}
3838

39-
// This covers the Oxide API from v4.0.0-alpha.20+
40-
declare namespace OxideV3 {
39+
// This covers the Oxide API from v4.0.0-alpha.30+
40+
declare namespace OxideV3And4 {
4141
interface GlobEntry {
4242
base: string
4343
pattern: string
@@ -58,10 +58,37 @@ declare namespace OxideV3 {
5858
}
5959
}
6060

61+
// This covers the Oxide API from v4.1.0+
62+
declare namespace OxideV5 {
63+
interface GlobEntry {
64+
base: string
65+
pattern: string
66+
}
67+
68+
interface SourceEntry {
69+
base: string
70+
pattern: string
71+
negated: boolean
72+
}
73+
74+
interface ScannerOptions {
75+
sources: Array<SourceEntry>
76+
}
77+
78+
interface ScannerConstructor {
79+
new (options: ScannerOptions): Scanner
80+
}
81+
82+
interface Scanner {
83+
get files(): Array<string>
84+
get globs(): Array<GlobEntry>
85+
}
86+
}
87+
6188
interface Oxide {
6289
scanDir?(options: OxideV1.ScanOptions): OxideV1.ScanResult
6390
scanDir?(options: OxideV2.ScanOptions): OxideV2.ScanResult
64-
Scanner?: OxideV3.ScannerConstructor
91+
Scanner?: OxideV3And4.ScannerConstructor | OxideV5.ScannerConstructor
6592
}
6693

6794
async function loadOxideAtPath(id: string): Promise<Oxide | null> {
@@ -78,11 +105,17 @@ interface GlobEntry {
78105
pattern: string
79106
}
80107

108+
interface SourceEntry {
109+
base: string
110+
pattern: string
111+
negated: boolean
112+
}
113+
81114
interface ScanOptions {
82115
oxidePath: string
83116
oxideVersion: string
84117
basePath: string
85-
sources: Array<GlobEntry>
118+
sources: Array<SourceEntry>
86119
}
87120

88121
interface ScanResult {
@@ -118,38 +151,58 @@ export async function scan(options: ScanOptions): Promise<ScanResult | null> {
118151
}
119152

120153
// V2
121-
if (lte(options.oxideVersion, '4.0.0-alpha.19')) {
154+
else if (lte(options.oxideVersion, '4.0.0-alpha.19')) {
122155
let result = oxide.scanDir({
123156
base: options.basePath,
124-
sources: options.sources,
157+
sources: options.sources.map((g) => ({ base: g.base, pattern: g.pattern })),
125158
})
126159

127160
return {
128161
files: result.files,
129-
globs: result.globs,
162+
globs: result.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
130163
}
131164
}
132165

133166
// V3
134-
if (lte(options.oxideVersion, '4.0.0-alpha.30')) {
135-
let scanner = new oxide.Scanner({
167+
else if (lte(options.oxideVersion, '4.0.0-alpha.30')) {
168+
let scanner = new (oxide.Scanner as OxideV3And4.ScannerConstructor)({
136169
detectSources: { base: options.basePath },
137-
sources: options.sources,
170+
sources: options.sources.map((g) => ({ base: g.base, pattern: g.pattern })),
138171
})
139172

140173
return {
141174
files: scanner.files,
142-
globs: scanner.globs,
175+
globs: scanner.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
143176
}
144177
}
145178

146179
// V4
147-
let scanner = new oxide.Scanner({
148-
sources: [{ base: options.basePath, pattern: '**/*' }, ...options.sources],
149-
})
180+
else if (lte(options.oxideVersion, '4.0.9999')) {
181+
let scanner = new (oxide.Scanner as OxideV3And4.ScannerConstructor)({
182+
sources: [
183+
{ base: options.basePath, pattern: '**/*' },
184+
...options.sources.map((g) => ({ base: g.base, pattern: g.pattern })),
185+
],
186+
})
150187

151-
return {
152-
files: scanner.files,
153-
globs: scanner.globs,
188+
return {
189+
files: scanner.files,
190+
globs: scanner.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
191+
}
192+
}
193+
194+
// V5
195+
else {
196+
let scanner = new (oxide.Scanner as OxideV5.ScannerConstructor)({
197+
sources: [
198+
{ base: options.basePath, pattern: '**/*', negated: false },
199+
...options.sources.map((g) => ({ base: g.base, pattern: g.pattern, negated: g.negated })),
200+
],
201+
})
202+
203+
return {
204+
files: scanner.files,
205+
globs: scanner.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
206+
}
154207
}
155208
}

packages/tailwindcss-language-server/src/project-locator.test.ts

Lines changed: 119 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ProjectLocator } from './project-locator'
44
import { URL, fileURLToPath } from 'url'
55
import { Settings } from '@tailwindcss/language-service/src/util/state'
66
import { createResolver } from './resolver'
7-
import { css, defineTest, js, json, scss, Storage, TestUtils } from './testing'
7+
import { css, defineTest, html, js, json, scss, Storage, symlinkTo, TestUtils } from './testing'
88
import { normalizePath } from './utils'
99

1010
let settings: Settings = {
@@ -141,58 +141,129 @@ testFixture('v4/workspaces', [
141141
},
142142
])
143143

144-
testFixture('v4/auto-content', [
145-
//
146-
{
147-
config: 'src/app.css',
148-
content: [
149-
'{URL}/*',
150-
'{URL}/package.json',
151-
'{URL}/src/index.html',
152-
'{URL}/src/components/example.html',
153-
'{URL}/src/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
154-
],
144+
testLocator({
145+
name: 'automatic content detection with Oxide',
146+
fs: {
147+
'package.json': json`
148+
{
149+
"dependencies": {
150+
"tailwindcss": "^4.0.15",
151+
"@tailwindcss/oxide": "^4.0.15"
152+
}
153+
}
154+
`,
155+
'src/index.html': html`<div class="flex">Test</div>`,
156+
'src/app.css': css`
157+
@import 'tailwindcss';
158+
`,
159+
'src/components/example.html': html`<div class="underline">Test</div>`,
155160
},
156-
])
161+
expected: [
162+
{
163+
config: '/src/app.css',
164+
content: [
165+
'/*',
166+
'/package.json',
167+
'/src/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
168+
'/src/components/example.html',
169+
'/src/index.html',
170+
],
171+
},
172+
],
173+
})
157174

158-
testFixture('v4/auto-content-split', [
159-
{
160-
config: 'src/app.css',
161-
content: [
162-
'{URL}/*',
163-
'{URL}/package.json',
164-
'{URL}/src/index.html',
165-
'{URL}/src/components/example.html',
166-
'{URL}/src/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
167-
],
175+
testLocator({
176+
name: 'automatic content detection with Oxide using split config',
177+
fs: {
178+
'package.json': json`
179+
{
180+
"dependencies": {
181+
"tailwindcss": "^4.0.15",
182+
"@tailwindcss/oxide": "^4.0.15"
183+
}
184+
}
185+
`,
186+
'src/index.html': html`<div class="flex">Test</div>`,
187+
'src/app.css': css`
188+
@import 'tailwindcss/preflight' layer(base);
189+
@import 'tailwindcss/theme' layer(theme);
190+
@import 'tailwindcss/utilities' layer(utilities);
191+
`,
192+
'src/components/example.html': html`<div class="underline">Test</div>`,
168193
},
169-
])
194+
expected: [
195+
{
196+
config: '/src/app.css',
197+
content: [
198+
'/*',
199+
'/package.json',
200+
'/src/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
201+
'/src/components/example.html',
202+
'/src/index.html',
203+
],
204+
},
205+
],
206+
})
170207

171-
testFixture('v4/custom-source', [
172-
//
173-
{
174-
config: 'admin/app.css',
175-
content: [
176-
'{URL}/*',
177-
'{URL}/admin/foo.bin',
178-
'{URL}/admin/{**/*.bin,**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}}',
179-
'{URL}/package.json',
180-
'{URL}/shared.html',
181-
'{URL}/web/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
182-
],
183-
},
184-
{
185-
config: 'web/app.css',
186-
content: [
187-
'{URL}/*',
188-
'{URL}/admin/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
189-
'{URL}/package.json',
190-
'{URL}/shared.html',
191-
'{URL}/web/bar.bin',
192-
'{URL}/web/{**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue},*.bin}',
193-
],
208+
testLocator({
209+
name: 'automatic content detection with custom sources',
210+
fs: {
211+
'package.json': json`
212+
{
213+
"dependencies": {
214+
"tailwindcss": "^4.0.15",
215+
"@tailwindcss/oxide": "^4.0.15"
216+
}
217+
}
218+
`,
219+
'admin/app.css': css`
220+
@import './tw.css';
221+
@import './ui.css';
222+
`,
223+
'admin/tw.css': css`
224+
@import 'tailwindcss';
225+
@source './**/*.bin';
226+
`,
227+
'admin/ui.css': css`
228+
@theme {
229+
--color-potato: #907a70;
230+
}
231+
`,
232+
'admin/foo.bin': html`<p class="underline">Admin</p>`,
233+
234+
'web/app.css': css`
235+
@import 'tailwindcss';
236+
@source './*.bin';
237+
`,
238+
'web/bar.bin': html`<p class="underline">Web</p>`,
239+
240+
'shared.html': html`<p>I belong to no one!</p>`,
194241
},
195-
])
242+
expected: [
243+
{
244+
config: '/admin/app.css',
245+
content: [
246+
'/*',
247+
'/admin/foo.bin',
248+
'/admin/{**/*.bin,**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}}',
249+
'/package.json',
250+
'/shared.html',
251+
'/web/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
252+
],
253+
},
254+
{
255+
config: '/web/app.css',
256+
content: [
257+
'/*',
258+
'/admin/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
259+
'/package.json',
260+
'/shared.html',
261+
'/web/bar.bin',
262+
'/web/{**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue},*.bin}',
263+
],
264+
},
265+
],
266+
})
196267

197268
testFixture('v4/missing-files', [
198269
//

packages/tailwindcss-language-server/src/project-locator.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -627,12 +627,9 @@ async function* detectContentFiles(
627627
resolver: Resolver,
628628
): AsyncIterable<string> {
629629
try {
630-
let oxidePath = await resolver.resolveJsId('@tailwindcss/oxide', path.dirname(base))
630+
let oxidePath = await resolver.resolveJsId('@tailwindcss/oxide', base)
631631
oxidePath = pathToFileURL(oxidePath).href
632-
let oxidePackageJsonPath = await resolver.resolveJsId(
633-
'@tailwindcss/oxide/package.json',
634-
path.dirname(base),
635-
)
632+
let oxidePackageJsonPath = await resolver.resolveJsId('@tailwindcss/oxide/package.json', base)
636633
let oxidePackageJson = JSON.parse(await fs.readFile(oxidePackageJsonPath, 'utf8'))
637634

638635
let result = await oxide.scan({

0 commit comments

Comments
 (0)