Skip to content

Commit 48056f2

Browse files
Fix errors thrown by detecting content files with oxide (#945)
* Fix errors thrown by detecting content files with oxide (#942) * Add test * Cleanup code * Update version * Manually connect index.css to theme.css and utilities.css * Move comment --------- Co-authored-by: Jordan Pittman <[email protected]>
1 parent d25652b commit 48056f2

File tree

17 files changed

+470
-37
lines changed

17 files changed

+470
-37
lines changed

package-lock.json

Lines changed: 182 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/tailwindcss-language-server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@tailwindcss/forms": "0.5.3",
4040
"@tailwindcss/language-service": "*",
4141
"@tailwindcss/line-clamp": "0.4.2",
42+
"@tailwindcss/oxide": "^4.0.0-alpha.11",
4243
"@tailwindcss/typography": "0.5.7",
4344
"@types/color-name": "^1.1.3",
4445
"@types/culori": "^2.1.0",

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ function testFixture(fixture: string, details: any[]) {
2929
let configPath = path.relative(fixturePath, project.config.path)
3030

3131
expect(configPath).toEqual(detail?.config)
32+
33+
if (detail?.content) {
34+
let expected = detail?.content.map((path) => path.replace('{URL}', fixturePath))
35+
36+
let actual = project.documentSelector
37+
.filter((selector) => selector.priority === 1 /** content */)
38+
.map((selector) => selector.pattern)
39+
40+
expect(actual).toEqual(expected)
41+
}
3242
}
3343

3444
expect(projects).toHaveLength(details.length)
@@ -84,3 +94,16 @@ testFixture('v4/workspaces', [
8494
// { config: 'packages/style-export/lib.css' }, // Should this be included?
8595
{ config: 'packages/web/app.css' },
8696
])
97+
98+
testFixture('v4/auto-content', [
99+
//
100+
{
101+
config: 'src/app.css',
102+
content: [
103+
'{URL}/package.json',
104+
'{URL}/src/index.html',
105+
'{URL}/src/components/example.html',
106+
'{URL}/src/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}',
107+
],
108+
},
109+
])

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

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,10 @@ export class ProjectLocator {
303303
// Create a graph of all the CSS files that might (indirectly) use Tailwind
304304
let graph = new Graph<FileEntry>()
305305

306+
let indexPath: string | null = null
307+
let themePath: string | null = null
308+
let utilitiesPath: string | null = null
309+
306310
for (let file of imports) {
307311
graph.add(file.path, file)
308312

@@ -314,8 +318,28 @@ export class ProjectLocator {
314318

315319
graph.connect(file.path, importedPath)
316320
}
321+
322+
// Collect the index, theme, and utilities files for manual connection
323+
if (file.path.includes('node_modules/tailwindcss/index.css')) {
324+
indexPath = file.path
325+
} else if (file.path.includes('node_modules/tailwindcss/theme.css')) {
326+
themePath = file.path
327+
} else if (file.path.includes('node_modules/tailwindcss/utilities.css')) {
328+
utilitiesPath = file.path
329+
}
317330
}
318331

332+
// We flatten the index file on publish so there are no imports that
333+
// need to be resolved. But this messes with our graph traversal, so
334+
// we need to manually connect the index file to the theme and utilities
335+
// files so we do not get extra roots in the graph.
336+
// - node_modules/tailwindcss/index.css
337+
// -> node_modules/tailwindcss/theme.css
338+
// -> node_modules/tailwindcss/utilities.css
339+
340+
if (indexPath && themePath) graph.connect(indexPath, themePath)
341+
if (indexPath && utilitiesPath) graph.connect(indexPath, utilitiesPath)
342+
319343
for (let root of graph.roots()) {
320344
let config: ConfigEntry = configs.remember(root.path, () => ({
321345
source: 'css',
@@ -438,9 +462,9 @@ async function* contentSelectorsFromCssConfig(entry: ConfigEntry): AsyncIterable
438462
}
439463
} else if (item.kind === 'auto' && !auto) {
440464
auto = true
441-
for await (let file of detectContentFiles(entry.packageRoot)) {
465+
for await (let pattern of detectContentFiles(entry.packageRoot)) {
442466
yield {
443-
pattern: normalizePath(file),
467+
pattern,
444468
priority: DocumentSelectorPriority.CONTENT_FILE,
445469
}
446470
}
@@ -453,16 +477,21 @@ async function* detectContentFiles(base: string): AsyncIterable<string> {
453477
let oxidePath = resolveFrom(path.dirname(base), '@tailwindcss/oxide')
454478
oxidePath = pathToFileURL(oxidePath).href
455479

480+
const oxide: typeof import('@tailwindcss/oxide') = await import(oxidePath)
481+
456482
// This isn't a v4 project
457-
const oxide = await import(oxidePath)
458-
if (!oxide.scanDir) {
459-
return
460-
}
483+
if (!oxide.scanDir) return
461484

462-
let { files, globs } = await oxide.scanDir({ base, globs: true })
485+
let { files, globs } = oxide.scanDir({ base, globs: true })
463486

464-
yield* files
465-
yield* globs
487+
for (let file of files) {
488+
yield normalizePath(file)
489+
}
490+
491+
for (let { base, glob } of globs) {
492+
// Do not normalize the glob itself as it may contain escape sequences
493+
yield normalizePath(base) + '/' + glob
494+
}
466495
} catch {
467496
//
468497
}

packages/tailwindcss-language-server/tests/completions/completions.test.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,8 @@ withFixture('v4/basic', (c) => {
310310
let result = await completion({ lang, text, position, settings })
311311
let textEdit = expect.objectContaining({ range: { start: position, end: position } })
312312

313-
expect(result.items.length).toBe(12106)
314-
expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(215)
313+
expect(result.items.length).toBe(12312)
314+
expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(216)
315315
expect(result).toEqual({
316316
isIncomplete: false,
317317
items: expect.arrayContaining([
@@ -522,11 +522,12 @@ withFixture('v4/basic', (c) => {
522522

523523
expect(resolved).toEqual({
524524
...item,
525-
detail: 'font-size: 0.875rem /* 8.75px */; line-height: 1.25rem /* 12.5px */;',
525+
detail:
526+
'font-size: var(--font-size-sm, 0.875rem /* 8.75px */); line-height: var(--font-size-sm--line-height, 1.25rem /* 12.5px */);',
526527
documentation: {
527528
kind: 'markdown',
528529
value:
529-
'```css\n.text-sm {\n font-size: 0.875rem /* 8.75px */;\n line-height: 1.25rem /* 12.5px */;\n}\n```',
530+
'```css\n.text-sm {\n font-size: var(--font-size-sm, 0.875rem /* 8.75px */);\n line-height: var(--font-size-sm--line-height, 1.25rem /* 12.5px */);\n}\n```',
530531
},
531532
})
532533
})
@@ -548,7 +549,7 @@ withFixture('v4/basic', (c) => {
548549

549550
expect(resolved).toEqual({
550551
...item,
551-
detail: 'background-color: #ef4444;',
552+
detail: 'background-color: var(--color-red-500, #ef4444);',
552553
documentation: '#ef4444',
553554
})
554555
})
@@ -577,19 +578,19 @@ withFixture('v4/workspaces', (c) => {
577578

578579
expect(resolved[0]).toEqual({
579580
...items[0],
580-
detail: 'background-color: #8e3b46;',
581+
detail: 'background-color: var(--color-beet, #8e3b46);',
581582
documentation: '#8e3b46',
582583
})
583584

584585
expect(resolved[1]).toEqual({
585586
...items[1],
586-
detail: 'background-color: #ff9f00;',
587+
detail: 'background-color: var(--color-orangepeel, #ff9f00);',
587588
documentation: '#ff9f00',
588589
})
589590

590591
expect(resolved[2]).toEqual({
591592
...items[2],
592-
detail: 'background-color: #8e3b46;',
593+
detail: 'background-color: var(--color-style-main, #8e3b46);',
593594
documentation: '#8e3b46',
594595
})
595596
})

0 commit comments

Comments
 (0)