From 6b306ba89b75a26bd420c61582242bb8251515df Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 1 Apr 2025 10:22:18 -0400 Subject: [PATCH 01/10] =?UTF-8?q?Hide=20completions=20from=20CSS=20languag?= =?UTF-8?q?e=20server=20inside=20`@import=20"=E2=80=A6"=20source(=E2=80=A6?= =?UTF-8?q?)`=20(#1091)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This hides completions from the CSS language server when inside `@import source(…)`, `@import theme(…)`, and `@import prefix(…)` Because of the way we fake stuff to the underlying service it thinks these are valid places to provide standard CSS completions but they're not. before: Screenshot 2025-03-28 at 21 32 08 after: Screenshot 2025-03-28 at 21 34 38 --- .../src/language/css-server.ts | 56 ++++++++++++++++++- .../tests/css/css-server.test.ts | 46 +++++++++++++++ packages/vscode-tailwindcss/CHANGELOG.md | 2 +- 3 files changed, 101 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss-language-server/src/language/css-server.ts b/packages/tailwindcss-language-server/src/language/css-server.ts index a43910b22..73e967fcc 100644 --- a/packages/tailwindcss-language-server/src/language/css-server.ts +++ b/packages/tailwindcss-language-server/src/language/css-server.ts @@ -13,7 +13,7 @@ import { CompletionItemKind, Connection, } from 'vscode-languageserver/node' -import { TextDocument } from 'vscode-languageserver-textdocument' +import { Position, TextDocument } from 'vscode-languageserver-textdocument' import { Utils, URI } from 'vscode-uri' import { getLanguageModelCache } from './languageModelCache' import { Stylesheet } from 'vscode-css-languageservice' @@ -121,6 +121,7 @@ export class CssServer { async function withDocumentAndSettings( uri: string, callback: (result: { + original: TextDocument document: TextDocument settings: LanguageSettings | undefined }) => T | Promise, @@ -130,13 +131,64 @@ export class CssServer { return null } return await callback({ + original: document, document: createVirtualCssDocument(document), settings: await getDocumentSettings(document), }) } + function isInImportDirective(doc: TextDocument, pos: Position) { + let text = doc.getText({ + start: { line: pos.line, character: 0 }, + end: pos, + }) + + // Scan backwards to see if we're inside an `@import` directive + let foundImport = false + let foundDirective = false + + for (let i = text.length - 1; i >= 0; i--) { + let char = text[i] + if (char === '\n') break + + if (char === '(' && !foundDirective) { + if (text.startsWith(' source(', i - 7)) { + foundDirective = true + } + + // + else if (text.startsWith(' theme(', i - 6)) { + foundDirective = true + } + + // + else if (text.startsWith(' prefix(', i - 7)) { + foundDirective = true + } + } + + // + else if (char === '@' && !foundImport) { + if (text.startsWith('@import ', i)) { + foundImport = true + } + } + } + + return foundImport && foundDirective + } + connection.onCompletion(async ({ textDocument, position }, _token) => - withDocumentAndSettings(textDocument.uri, async ({ document, settings }) => { + withDocumentAndSettings(textDocument.uri, async ({ original, document, settings }) => { + // If we're inside source(…), prefix(…), or theme(…), don't show + // completions from the CSS language server + if (isInImportDirective(original, position)) { + return { + isIncomplete: false, + items: [], + } + } + let result = await cssLanguageService.doComplete2( document, position, diff --git a/packages/tailwindcss-language-server/tests/css/css-server.test.ts b/packages/tailwindcss-language-server/tests/css/css-server.test.ts index e8970e08a..388e94af7 100644 --- a/packages/tailwindcss-language-server/tests/css/css-server.test.ts +++ b/packages/tailwindcss-language-server/tests/css/css-server.test.ts @@ -689,3 +689,49 @@ defineTest({ expect(await doc.diagnostics()).toEqual([]) }, }) + +defineTest({ + name: 'completions are hidden inside @import source(…)/theme(…)/prefix(…) functions', + prepare: async ({ root }) => ({ + client: await createClient({ + server: 'css', + root, + }), + }), + handle: async ({ client }) => { + let doc = await client.open({ + lang: 'tailwindcss', + name: 'file-1.css', + text: css` + @import './file.css' source(none); + @import './file.css' theme(inline); + @import './file.css' prefix(tw); + @import './file.css' source(none) theme(inline) prefix(tw); + `, + }) + + // @import './file.css' source(none) + // ^ + // @import './file.css' theme(inline); + // ^ + // @import './file.css' prefix(tw); + // ^ + let completionsA = await doc.completions({ line: 0, character: 29 }) + let completionsB = await doc.completions({ line: 1, character: 28 }) + let completionsC = await doc.completions({ line: 2, character: 29 }) + + expect(completionsA).toEqual({ isIncomplete: false, items: [] }) + expect(completionsB).toEqual({ isIncomplete: false, items: [] }) + expect(completionsC).toEqual({ isIncomplete: false, items: [] }) + + // @import './file.css' source(none) theme(inline) prefix(tw); + // ^ ^ ^ + let completionsD = await doc.completions({ line: 3, character: 29 }) + let completionsE = await doc.completions({ line: 3, character: 41 }) + let completionsF = await doc.completions({ line: 3, character: 56 }) + + expect(completionsD).toEqual({ isIncomplete: false, items: [] }) + expect(completionsE).toEqual({ isIncomplete: false, items: [] }) + expect(completionsF).toEqual({ isIncomplete: false, items: [] }) + }, +}) diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index 31a8ca66a..824781252 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -2,7 +2,7 @@ ## Prerelease -- Nothing yet! +- Hide completions from CSS language server inside `@import "…" source(…)` ([#1091](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1091)) # 0.14.12 From b92d170d45324501dc4541fa7f7c2f689e9cab58 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 2 Apr 2025 10:14:58 -0400 Subject: [PATCH 02/10] Bump to Tailwind CSS v4.1.1 (#1294) --- .../tailwindcss-language-server/package.json | 4 +- .../src/project-locator.test.ts | 52 +++++---- .../src/util/v4/design-system.ts | 8 ++ .../tests/colors/colors.test.js | 4 +- .../tests/completions/completions.test.js | 26 ++--- .../tests/env/v4.test.js | 22 ++-- .../tests/fixtures/v4/basic/package-lock.json | 8 +- .../tests/fixtures/v4/basic/package.json | 2 +- .../v4/css-loading-js/package-lock.json | 8 +- .../fixtures/v4/css-loading-js/package.json | 2 +- .../v4/dependencies/package-lock.json | 8 +- .../fixtures/v4/dependencies/package.json | 2 +- .../v4/invalid-import-order/package-lock.json | 8 +- .../v4/invalid-import-order/package.json | 2 +- .../v4/missing-files/package-lock.json | 8 +- .../fixtures/v4/missing-files/package.json | 2 +- .../v4/multi-config/package-lock.json | 8 +- .../fixtures/v4/multi-config/package.json | 2 +- .../v4/path-mappings/package-lock.json | 8 +- .../fixtures/v4/path-mappings/package.json | 2 +- .../fixtures/v4/with-prefix/package-lock.json | 8 +- .../fixtures/v4/with-prefix/package.json | 2 +- .../fixtures/v4/workspaces/package-lock.json | 8 +- .../tests/fixtures/v4/workspaces/package.json | 2 +- .../tests/hover/hover.test.js | 21 ++-- .../src/util/color.ts | 30 ++++- .../src/util/rewriting/var-fallbacks.ts | 8 ++ packages/vscode-tailwindcss/CHANGELOG.md | 2 + pnpm-lock.yaml | 108 +++++++++--------- 29 files changed, 213 insertions(+), 162 deletions(-) diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json index dd812e3c9..d96255dd3 100644 --- a/packages/tailwindcss-language-server/package.json +++ b/packages/tailwindcss-language-server/package.json @@ -40,7 +40,7 @@ "@tailwindcss/forms": "0.5.3", "@tailwindcss/language-service": "workspace:*", "@tailwindcss/line-clamp": "0.4.2", - "@tailwindcss/oxide": "^4.0.15", + "@tailwindcss/oxide": "^4.1.0", "@tailwindcss/typography": "0.5.7", "@types/braces": "3.0.1", "@types/color-name": "^1.1.3", @@ -84,7 +84,7 @@ "rimraf": "3.0.2", "stack-trace": "0.0.10", "tailwindcss": "3.4.17", - "tailwindcss-v4": "npm:tailwindcss@4.0.6", + "tailwindcss-v4": "npm:tailwindcss@4.1.1", "tsconfck": "^3.1.4", "tsconfig-paths": "^4.2.0", "typescript": "5.3.3", diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts index 429f0b27b..3699c8402 100644 --- a/packages/tailwindcss-language-server/src/project-locator.test.ts +++ b/packages/tailwindcss-language-server/src/project-locator.test.ts @@ -123,6 +123,7 @@ testFixture('v4/workspaces', [ '{URL}/packages/admin/**', '{URL}/packages/admin/app.css', '{URL}/packages/admin/package.json', + '{URL}/packages/admin/tw.css', ], }, { @@ -147,8 +148,8 @@ testLocator({ 'package.json': json` { "dependencies": { - "tailwindcss": "^4.0.15", - "@tailwindcss/oxide": "^4.0.15" + "tailwindcss": "4.1.0", + "@tailwindcss/oxide": "4.1.0" } } `, @@ -164,7 +165,7 @@ testLocator({ content: [ '/*', '/package.json', - '/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}', + '/src/**/*.{aspx,astro,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}', '/src/components/example.html', '/src/index.html', ], @@ -178,8 +179,8 @@ testLocator({ 'package.json': json` { "dependencies": { - "tailwindcss": "^4.0.15", - "@tailwindcss/oxide": "^4.0.15" + "tailwindcss": "4.1.0", + "@tailwindcss/oxide": "4.1.0" } } `, @@ -197,7 +198,7 @@ testLocator({ content: [ '/*', '/package.json', - '/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}', + '/src/**/*.{aspx,astro,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}', '/src/components/example.html', '/src/index.html', ], @@ -211,8 +212,8 @@ testLocator({ 'package.json': json` { "dependencies": { - "tailwindcss": "^4.0.15", - "@tailwindcss/oxide": "^4.0.15" + "tailwindcss": "4.1.0", + "@tailwindcss/oxide": "4.1.0" } } `, @@ -245,36 +246,40 @@ testLocator({ content: [ '/*', '/admin/foo.bin', - '/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}}', + '/admin/tw.css', + '/admin/ui.css', + '/admin/{**/*.bin,**/*.{aspx,astro,bin,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}}', '/package.json', '/shared.html', - '/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}', + '/web/**/*.{aspx,astro,bin,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}', + '/web/app.css', ], }, { config: '/web/app.css', content: [ '/*', - '/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}', + '/admin/**/*.{aspx,astro,bin,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}', + '/admin/app.css', + '/admin/tw.css', + '/admin/ui.css', '/package.json', '/shared.html', '/web/bar.bin', - '/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}', + '/web/{**/*.{aspx,astro,bin,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue},*.bin}', ], }, ], }) testLocator({ - // TODO: Enable once v4.1 is released - options: { skip: true }, name: 'automatic content detection with negative custom sources', fs: { 'package.json': json` { "dependencies": { - "tailwindcss": "0.0.0-insiders.3e53e25", - "@tailwindcss/oxide": "0.0.0-insiders.3e53e25" + "tailwindcss": "4.1.0", + "@tailwindcss/oxide": "4.1.0" } } `, @@ -293,7 +298,7 @@ testLocator({ '/*', '/package.json', '/src/index.html', - '/src/{**/*.html,**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}}', + '/src/{**/*.html,**/*.{aspx,astro,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}}', ], }, ], @@ -303,7 +308,7 @@ testFixture('v4/missing-files', [ // { config: 'app.css', - content: ['{URL}/*', '{URL}/package.json'], + content: ['{URL}/*', '{URL}/i-exist.css', '{URL}/package.json'], }, ]) @@ -314,7 +319,8 @@ testFixture('v4/path-mappings', [ content: [ '{URL}/*', '{URL}/package.json', - '{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}', + '{URL}/src/**/*.{aspx,astro,cjs,css,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,json,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}', + '{URL}/src/a/file.css', '{URL}/src/a/my-config.ts', '{URL}/src/a/my-plugin.ts', '{URL}/tsconfig.json', @@ -326,7 +332,7 @@ testFixture('v4/invalid-import-order', [ // { config: 'tailwind.css', - content: ['{URL}/*', '{URL}/package.json'], + content: ['{URL}/*', '{URL}/a.css', '{URL}/b.css', '{URL}/package.json'], }, ]) @@ -338,7 +344,7 @@ testLocator({ 'package.json': json` { "dependencies": { - "tailwindcss": "^4.0.2" + "tailwindcss": "4.1.0" } } `, @@ -386,7 +392,7 @@ testLocator({ 'package.json': json` { "dependencies": { - "tailwindcss": "4.0.6" + "tailwindcss": "4.1.0" } } `, @@ -415,7 +421,7 @@ testLocator({ }, expected: [ { - version: '4.0.6', + version: '4.1.0', config: '/src/articles/articles.css', content: [], }, diff --git a/packages/tailwindcss-language-server/src/util/v4/design-system.ts b/packages/tailwindcss-language-server/src/util/v4/design-system.ts index 05d2ecd3e..f312b95c0 100644 --- a/packages/tailwindcss-language-server/src/util/v4/design-system.ts +++ b/packages/tailwindcss-language-server/src/util/v4/design-system.ts @@ -219,6 +219,14 @@ export async function loadDesignSystem( Object.assign(design, { dependencies: () => dependencies, + // TODOs: + // + // 1. Remove PostCSS parsing — its roughly 60% of the processing time + // ex: compiling 19k classes take 650ms and 400ms of that is PostCSS + // + // - Replace `candidatesToCss` with a `candidatesToAst` API + // First step would be to convert to a PostCSS AST by transforming the nodes directly + // Then it would be to drop the PostCSS AST representation entirely in all v4 code paths compile(classes: string[]): (postcss.Root | null)[] { let css = design.candidatesToCss(classes) let errors: any[] = [] diff --git a/packages/tailwindcss-language-server/tests/colors/colors.test.js b/packages/tailwindcss-language-server/tests/colors/colors.test.js index 5016bacc3..4780a4fb2 100644 --- a/packages/tailwindcss-language-server/tests/colors/colors.test.js +++ b/packages/tailwindcss-language-server/tests/colors/colors.test.js @@ -334,7 +334,7 @@ defineTest({ expect(c.project).toMatchObject({ tailwind: { - version: '4.0.6', + version: '4.1.1', isDefaultVersion: true, }, }) @@ -373,7 +373,7 @@ defineTest({ expect(c.project).toMatchObject({ tailwind: { - version: '4.0.6', + version: '4.1.1', isDefaultVersion: true, }, }) diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js index dbe9a3522..feb4999f1 100644 --- a/packages/tailwindcss-language-server/tests/completions/completions.test.js +++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js @@ -313,8 +313,8 @@ withFixture('v4/basic', (c) => { let result = await completion({ lang, text, position, settings }) let textEdit = expect.objectContaining({ range: { start: position, end: position } }) - expect(result.items.length).toBe(13172) - expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(317) + expect(result.items.length).toBe(19283) + expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(346) expect(result).toEqual({ isIncomplete: false, items: expect.arrayContaining([ @@ -488,7 +488,7 @@ withFixture('v4/basic', (c) => { }) // Make sure `@slot` is NOT suggested by default - expect(result.items.length).toBe(7) + expect(result.items.length).toBe(8) expect(result.items).not.toEqual( expect.arrayContaining([ expect.objectContaining({ kind: 14, label: '@slot', sortText: '-0000000' }), @@ -627,7 +627,7 @@ withFixture('v4/basic', (c) => { expect(resolved).toEqual({ ...item, - detail: 'background-color: oklch(0.637 0.237 25.331);', + detail: 'background-color: oklch(63.7% 0.237 25.331);', documentation: '#fb2c36', }) }) @@ -692,7 +692,7 @@ defineTest({ // ^ let completion = await document.completions({ line: 0, character: 23 }) - expect(completion?.items.length).toBe(12289) + expect(completion?.items.length).toBe(19236) }, }) @@ -714,7 +714,7 @@ defineTest({ // ^ let completion = await document.completions({ line: 0, character: 22 }) - expect(completion?.items.length).toBe(12289) + expect(completion?.items.length).toBe(19236) }, }) @@ -736,7 +736,7 @@ defineTest({ // ^ let completion = await document.completions({ line: 0, character: 31 }) - expect(completion?.items.length).toBe(12289) + expect(completion?.items.length).toBe(19236) }, }) @@ -765,7 +765,7 @@ defineTest({ // ^ let completion = await document.completions({ line: 0, character: 20 }) - expect(completion?.items.length).toBe(12289) + expect(completion?.items.length).toBe(19236) }, }) @@ -796,7 +796,7 @@ defineTest({ // ^ let completion = await document.completions({ line: 1, character: 22 }) - expect(completion?.items.length).toBe(12289) + expect(completion?.items.length).toBe(19236) }, }) @@ -886,24 +886,24 @@ defineTest({ // ^ let completionA = await document.completions({ line: 0, character: 13 }) - expect(completionA?.items.length).toBe(12289) + expect(completionA?.items.length).toBe(19236) // return ; // ^ let completionB = await document.completions({ line: 3, character: 30 }) - expect(completionB?.items.length).toBe(12289) + expect(completionB?.items.length).toBe(19236) // return ; // ^ let completionC = await document.completions({ line: 7, character: 30 }) - expect(completionC?.items.length).toBe(12289) + expect(completionC?.items.length).toBe(19236) // let y = cva(""); // ^ let completionD = await document.completions({ line: 10, character: 13 }) - expect(completionD?.items.length).toBe(12289) + expect(completionD?.items.length).toBe(19236) }, }) diff --git a/packages/tailwindcss-language-server/tests/env/v4.test.js b/packages/tailwindcss-language-server/tests/env/v4.test.js index 632eb1a2a..dc33c79f2 100644 --- a/packages/tailwindcss-language-server/tests/env/v4.test.js +++ b/packages/tailwindcss-language-server/tests/env/v4.test.js @@ -21,7 +21,7 @@ defineTest({ expect(await client.project()).toMatchObject({ tailwind: { - version: '4.0.6', + version: '4.1.1', isDefaultVersion: true, }, }) @@ -49,7 +49,7 @@ defineTest({ }, }) - expect(completion?.items.length).toBe(12288) + expect(completion?.items.length).toBe(19235) }, }) @@ -137,7 +137,7 @@ defineTest({ expect(await client.project()).toMatchObject({ tailwind: { - version: '4.0.6', + version: '4.1.1', isDefaultVersion: true, }, }) @@ -188,7 +188,7 @@ defineTest({ 'package.json': json` { "dependencies": { - "tailwindcss": "4.0.1" + "tailwindcss": "4.1.1" } } `, @@ -205,7 +205,7 @@ defineTest({ expect(await client.project()).toMatchObject({ tailwind: { - version: '4.0.1', + version: '4.1.1', isDefaultVersion: false, }, }) @@ -233,7 +233,7 @@ defineTest({ }, }) - expect(completion?.items.length).toBe(12288) + expect(completion?.items.length).toBe(19235) }, }) @@ -243,7 +243,7 @@ defineTest({ 'package.json': json` { "dependencies": { - "tailwindcss": "4.0.1" + "tailwindcss": "4.1.1" } } `, @@ -270,7 +270,7 @@ defineTest({ expect(await client.project()).toMatchObject({ tailwind: { - version: '4.0.1', + version: '4.1.1', isDefaultVersion: false, }, }) @@ -322,7 +322,7 @@ defineTest({ expect(await client.project()).toMatchObject({ tailwind: { - version: '4.0.6', + version: '4.1.1', isDefaultVersion: true, }, }) @@ -354,7 +354,7 @@ defineTest({ 'package.json': json` { "dependencies": { - "tailwindcss": "4.0.1" + "tailwindcss": "4.1.1" } } `, @@ -831,7 +831,7 @@ defineTest({ expect(await client.project()).toMatchObject({ tailwind: { - version: '4.0.6', + version: '4.1.1', isDefaultVersion: true, }, }) diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/basic/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/basic/package-lock.json index 6275c3dc3..ab6be9775 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/basic/package-lock.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/basic/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } }, "node_modules/tailwindcss": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz", - "integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz", + "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==", "license": "MIT" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/basic/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/basic/package.json index b6cb53b1e..43b975c93 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/basic/package.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/basic/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/css-loading-js/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/css-loading-js/package-lock.json index 5089dc65d..b92fb8488 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/css-loading-js/package-lock.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/css-loading-js/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } }, "node_modules/tailwindcss": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz", - "integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz", + "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==", "license": "MIT" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/css-loading-js/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/css-loading-js/package.json index b6cb53b1e..43b975c93 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/css-loading-js/package.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/css-loading-js/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/dependencies/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/dependencies/package-lock.json index 555ee6602..f4352dc60 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/dependencies/package-lock.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/dependencies/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } }, "node_modules/tailwindcss": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz", - "integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz", + "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==", "license": "MIT" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/dependencies/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/dependencies/package.json index b6cb53b1e..43b975c93 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/dependencies/package.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/dependencies/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package-lock.json index 24d978d86..0a12b281b 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package-lock.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } }, "node_modules/tailwindcss": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz", - "integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz", + "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==", "license": "MIT" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package.json index b6cb53b1e..43b975c93 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/invalid-import-order/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package-lock.json index ed4d2d9a8..c95b25d7b 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package-lock.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } }, "node_modules/tailwindcss": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz", - "integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz", + "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==", "license": "MIT" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package.json index b6cb53b1e..43b975c93 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/missing-files/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/multi-config/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/multi-config/package-lock.json index c4664645d..58a06ceec 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/multi-config/package-lock.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/multi-config/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } }, "node_modules/tailwindcss": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz", - "integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz", + "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==", "license": "MIT" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/multi-config/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/multi-config/package.json index b6cb53b1e..43b975c93 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/multi-config/package.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/multi-config/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package-lock.json index 651bf7c97..e6cc18e9a 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package-lock.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } }, "node_modules/tailwindcss": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz", - "integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz", + "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==", "license": "MIT" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package.json index b6cb53b1e..43b975c93 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/path-mappings/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/with-prefix/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/with-prefix/package-lock.json index 1e5486aeb..34a2cc131 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/with-prefix/package-lock.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/with-prefix/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } }, "node_modules/tailwindcss": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz", - "integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz", + "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==", "license": "MIT" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/with-prefix/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/with-prefix/package.json index b6cb53b1e..43b975c93 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/with-prefix/package.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/with-prefix/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } } diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/workspaces/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/workspaces/package-lock.json index b622b7b76..a88643baf 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/workspaces/package-lock.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/workspaces/package-lock.json @@ -8,7 +8,7 @@ "packages/*" ], "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } }, "node_modules/@private/admin": { @@ -32,9 +32,9 @@ "link": true }, "node_modules/tailwindcss": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz", - "integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz", + "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==", "license": "MIT" }, "packages/admin": { diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/workspaces/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/workspaces/package.json index aa5e54db9..9291956a3 100644 --- a/packages/tailwindcss-language-server/tests/fixtures/v4/workspaces/package.json +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/workspaces/package.json @@ -3,6 +3,6 @@ "packages/*" ], "dependencies": { - "tailwindcss": "^4.0.15" + "tailwindcss": "4.1.1" } } diff --git a/packages/tailwindcss-language-server/tests/hover/hover.test.js b/packages/tailwindcss-language-server/tests/hover/hover.test.js index ac97e414d..373773401 100644 --- a/packages/tailwindcss-language-server/tests/hover/hover.test.js +++ b/packages/tailwindcss-language-server/tests/hover/hover.test.js @@ -214,7 +214,7 @@ withFixture('v4/basic', (c) => { text: '
', position: { line: 0, character: 13 }, expected: - '.bg-red-500 {\n background-color: var(--color-red-500) /* oklch(0.637 0.237 25.331) = #fb2c36 */;\n}', + '.bg-red-500 {\n background-color: var(--color-red-500) /* oklch(63.7% 0.237 25.331) = #fb2c36 */;\n}', expectedRange: { start: { line: 0, character: 12 }, end: { line: 0, character: 22 }, @@ -231,16 +231,15 @@ withFixture('v4/basic', (c) => { }, }) - test.todo('arbitrary value with theme function') - // testHover('arbitrary value with theme function', { - // text: '
', - // position: { line: 0, character: 13 }, - // expected: '.p-\\[theme\\(spacing\\.4\\)\\] {\n' + ' padding: 1rem /* 16px */;\n' + '}', - // expectedRange: { - // start: { line: 0, character: 12 }, - // end: { line: 0, character: 32 }, - // }, - // }) + testHover('arbitrary value with theme function', { + text: '
', + position: { line: 0, character: 13 }, + expected: '.p-\\[theme\\(spacing\\.4\\)\\] {\n' + ' padding: 1rem /* 16px */;\n' + '}', + expectedRange: { + start: { line: 0, character: 12 }, + end: { line: 0, character: 32 }, + }, + }) testHover('arbitrary property', { text: '
', diff --git a/packages/tailwindcss-language-service/src/util/color.ts b/packages/tailwindcss-language-service/src/util/color.ts index 4b0d3b84d..a1a99d661 100644 --- a/packages/tailwindcss-language-service/src/util/color.ts +++ b/packages/tailwindcss-language-service/src/util/color.ts @@ -57,7 +57,7 @@ const colorRegex = new RegExp( ) function getColorsInString(state: State, str: string): (culori.Color | KeywordColor)[] { - if (/(?:box|drop)-shadow/.test(str)) return [] + if (/(?:box|drop)-shadow/.test(str) && !/--tw-drop-shadow/.test(str)) return [] function toColor(match: RegExpMatchArray) { let color = match[1].replace(/var\([^)]+\)/, '1') @@ -85,6 +85,17 @@ function getColorFromDecls( ) { return false } + + // ignore mask-image & mask-composite + if (prop === 'mask-image' || prop === 'mask-composite') { + return false + } + + // ignore `--tw-drop-shadow` + if (prop === '--tw-drop-shadow') { + return false + } + return true }) @@ -177,8 +188,25 @@ function getColorFromRoot(state: State, css: postcss.Root): culori.Color | Keywo return getColorFromDecls(state, decls) } +let isNegative = /^-/ +let isNumericUtility = + /^-?((min-|max-)?[wh]|z|start|order|opacity|rounded|row|col|size|basis|end|duration|ease|font|top|left|bottom|right|inset|leading|cursor|(space|scale|skew|rotate)-[xyz]|gap(-[xy])?|(scroll-)?[pm][trblxyse]?)-/ +let isMaskUtility = /^-?mask-/ + +function isLikelyColorless(className: string) { + if (isNegative.test(className)) return true + // TODO: This is **not** correct but is intentional because there are 5k mask utilities and a LOT of them are colors + // This causes a massive slowdown when building the design system + if (isMaskUtility.test(className)) return true + if (isNumericUtility.test(className)) return true + return false +} + export function getColor(state: State, className: string): culori.Color | KeywordColor | null { if (state.v4) { + // FIXME: This is a performance optimization and not strictly correct + if (isLikelyColorless(className)) return null + let css = state.designSystem.compile([className])[0] let color = getColorFromRoot(state, css) diff --git a/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts b/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts index 728b53bf9..91dcc91db 100644 --- a/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts +++ b/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts @@ -17,6 +17,14 @@ export function replaceCssVarsWithFallbacks(state: State, str: string): string { return fallback } + if ( + name === '--tw-text-shadow-alpha' || + name === '--tw-drop-shadow-alpha' || + name === '--tw-shadow-alpha' + ) { + return '100%' + } + // Don't touch it since there's no suitable replacement return null }, diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index 824781252..421543f54 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -3,6 +3,8 @@ ## Prerelease - Hide completions from CSS language server inside `@import "…" source(…)` ([#1091](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1091)) +- Bump bundled v4 fallback to v4.1.1 ([#1294](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1294)) +- Show color swatches for most new v4.1 utilities ([#1294](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1294)) # 0.14.12 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 071263e70..e879d3a6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,8 +51,8 @@ importers: specifier: 0.4.2 version: 0.4.2(tailwindcss@3.4.17) '@tailwindcss/oxide': - specifier: ^4.0.15 - version: 4.0.15 + specifier: ^4.1.0 + version: 4.1.0 '@tailwindcss/typography': specifier: 0.5.7 version: 0.5.7(tailwindcss@3.4.17) @@ -183,8 +183,8 @@ importers: specifier: 3.4.17 version: 3.4.17 tailwindcss-v4: - specifier: npm:tailwindcss@4.0.6 - version: tailwindcss@4.0.6 + specifier: npm:tailwindcss@4.1.1 + version: tailwindcss@4.1.1 tsconfck: specifier: ^3.1.4 version: 3.1.4(typescript@5.3.3) @@ -890,74 +890,74 @@ packages: peerDependencies: tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' - '@tailwindcss/oxide-android-arm64@4.0.15': - resolution: {integrity: sha512-EBuyfSKkom7N+CB3A+7c0m4+qzKuiN0WCvzPvj5ZoRu4NlQadg/mthc1tl5k9b5ffRGsbDvP4k21azU4VwVk3Q==} + '@tailwindcss/oxide-android-arm64@4.1.0': + resolution: {integrity: sha512-UredFljuHey2Kh5qyYfQVBr0Xfq70ZE5Df6i5IubNYQGs2JXXT4VL0SIUjwzHx5W9T6t7dT7banunlV6lthGPQ==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.0.15': - resolution: {integrity: sha512-ObVAnEpLepMhV9VoO0JSit66jiN5C4YCqW3TflsE9boo2Z7FIjV80RFbgeL2opBhtxbaNEDa6D0/hq/EP03kgQ==} + '@tailwindcss/oxide-darwin-arm64@4.1.0': + resolution: {integrity: sha512-QHQ/46lRVwH9zEBNiRk8AJ3Af4pMq6DuZAI//q323qrPOXjsRdrhLsH9LUO3mqBfHr5EZNUxN3Am5vpO89sntw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.0.15': - resolution: {integrity: sha512-IElwoFhUinOr9MyKmGTPNi1Rwdh68JReFgYWibPWTGuevkHkLWKEflZc2jtI5lWZ5U9JjUnUfnY43I4fEXrc4g==} + '@tailwindcss/oxide-darwin-x64@4.1.0': + resolution: {integrity: sha512-lEMgYHCvQQ6x2KOZ4FwnPprwfnc+UnjzwXRqEYIhB/NlYvXQD1QMf7oKEDRqy94DiZaYox9ZRfG2YJOBgM0UkA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.0.15': - resolution: {integrity: sha512-6BLLqyx7SIYRBOnTZ8wgfXANLJV5TQd3PevRJZp0vn42eO58A2LykRKdvL1qyPfdpmEVtF+uVOEZ4QTMqDRAWA==} + '@tailwindcss/oxide-freebsd-x64@4.1.0': + resolution: {integrity: sha512-9fdImTc+2lA5yHqJ61oeTXfCtzylNOzJVFhyWwVQAJESJJbVCPnj6f+b+Zf/AYAdKQfS6FCThbPEahkQrDCgLQ==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.15': - resolution: {integrity: sha512-Zy63EVqO9241Pfg6G0IlRIWyY5vNcWrL5dd2WAKVJZRQVeolXEf1KfjkyeAAlErDj72cnyXObEZjMoPEKHpdNw==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.0': + resolution: {integrity: sha512-HB0bTkUOuTLLSdadyRhKE9yps4/ZBjrojbHTPMSvvf/8yBLZRPpWb+A6IgW5R+2A2AL4KhVPgLwWfoXsErxJFg==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.0.15': - resolution: {integrity: sha512-2NemGQeaTbtIp1Z2wyerbVEJZTkAWhMDOhhR5z/zJ75yMNf8yLnE+sAlyf6yGDNr+1RqvWrRhhCFt7i0CIxe4Q==} + '@tailwindcss/oxide-linux-arm64-gnu@4.1.0': + resolution: {integrity: sha512-+QtYCwvKLjC46h6RikKkpELJWrpiMMtgyK0aaqhwPLEx1icGgIhwz8dqrkAiqbFRE0KiRrE2aenhYoEkplyRmA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.0.15': - resolution: {integrity: sha512-342GVnhH/6PkVgKtEzvNVuQ4D+Q7B7qplvuH20Cfz9qEtydG6IQczTZ5IT4JPlh931MG1NUCVxg+CIorr1WJyw==} + '@tailwindcss/oxide-linux-arm64-musl@4.1.0': + resolution: {integrity: sha512-nApadFKM9GauzuPZPlt6TKfELavMHqJ0gVd+GYkYBTwr2t9KhgCAb2sKiFDDIhs1a7gOjsU7P1lEauv3iKFp+Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.0.15': - resolution: {integrity: sha512-g76GxlKH124RuGqacCEFc2nbzRl7bBrlC8qDQMiUABkiifDRHOIUjgKbLNG4RuR9hQAD/MKsqZ7A8L08zsoBrw==} + '@tailwindcss/oxide-linux-x64-gnu@4.1.0': + resolution: {integrity: sha512-cp0Rf9Wit2kZHhrV8HIoDFD8dxU2+ZTCFCFbDj3a07pGyyPwLCJm5H5VipKXgYrBaLmlYu73ERidW0S5sdEXEg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.0.15': - resolution: {integrity: sha512-Gg/Y1XrKEvKpq6WeNt2h8rMIKOBj/W3mNa5NMvkQgMC7iO0+UNLrYmt6zgZufht66HozNpn+tJMbbkZ5a3LczA==} + '@tailwindcss/oxide-linux-x64-musl@4.1.0': + resolution: {integrity: sha512-4/wf42XWBJGXsOS6BhgPhdQbg/qyfdZ1nZvTL9sJoxYN+Ah+cfY5Dd7R0smzI8hmgCRt3TD1lYb72ChTyIA59w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-win32-arm64-msvc@4.0.15': - resolution: {integrity: sha512-7QtSSJwYZ7ZK1phVgcNZpuf7c7gaCj8Wb0xjliligT5qCGCp79OV2n3SJummVZdw4fbTNKUOYMO7m1GinppZyA==} + '@tailwindcss/oxide-win32-arm64-msvc@4.1.0': + resolution: {integrity: sha512-caXJJ0G6NwGbcoxEYdH3MZYN84C3PldaMdAEPMU6bjJXURQlKdSlQ/Ecis7/nSgBkMkicZyhqWmb36Tw/BFSIw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.0.15': - resolution: {integrity: sha512-JQ5H+5MLhOjpgNp6KomouE0ZuKmk3hO5h7/ClMNAQ8gZI2zkli3IH8ZqLbd2DVfXDbdxN2xvooIEeIlkIoSCqw==} + '@tailwindcss/oxide-win32-x64-msvc@4.1.0': + resolution: {integrity: sha512-ZHXRXRxB7HBmkUE8U13nmkGGYfR1I2vsuhiYjeDDUFIYpk1BL6caU8hvzkSlL/X5CAQNdIUUJRGom5I0ZyfJOA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.0.15': - resolution: {integrity: sha512-e0uHrKfPu7JJGMfjwVNyt5M0u+OP8kUmhACwIRlM+JNBuReDVQ63yAD1NWe5DwJtdaHjugNBil76j+ks3zlk6g==} + '@tailwindcss/oxide@4.1.0': + resolution: {integrity: sha512-A33oyZKpPFH08d7xkl13Dc8OTsbPhsuls0z9gUCxIHvn8c1BsUACddQxL6HwaeJR1fSYyXZUw8bdWcD8bVawpQ==} engines: {node: '>= 10'} '@tailwindcss/typography@0.5.7': @@ -2440,8 +2440,8 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tailwindcss@4.0.6: - resolution: {integrity: sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw==} + tailwindcss@4.1.1: + resolution: {integrity: sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==} tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} @@ -3105,52 +3105,52 @@ snapshots: dependencies: tailwindcss: 3.4.17 - '@tailwindcss/oxide-android-arm64@4.0.15': + '@tailwindcss/oxide-android-arm64@4.1.0': optional: true - '@tailwindcss/oxide-darwin-arm64@4.0.15': + '@tailwindcss/oxide-darwin-arm64@4.1.0': optional: true - '@tailwindcss/oxide-darwin-x64@4.0.15': + '@tailwindcss/oxide-darwin-x64@4.1.0': optional: true - '@tailwindcss/oxide-freebsd-x64@4.0.15': + '@tailwindcss/oxide-freebsd-x64@4.1.0': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.15': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.0': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.0.15': + '@tailwindcss/oxide-linux-arm64-gnu@4.1.0': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.0.15': + '@tailwindcss/oxide-linux-arm64-musl@4.1.0': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.0.15': + '@tailwindcss/oxide-linux-x64-gnu@4.1.0': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.0.15': + '@tailwindcss/oxide-linux-x64-musl@4.1.0': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.0.15': + '@tailwindcss/oxide-win32-arm64-msvc@4.1.0': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.0.15': + '@tailwindcss/oxide-win32-x64-msvc@4.1.0': optional: true - '@tailwindcss/oxide@4.0.15': + '@tailwindcss/oxide@4.1.0': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.0.15 - '@tailwindcss/oxide-darwin-arm64': 4.0.15 - '@tailwindcss/oxide-darwin-x64': 4.0.15 - '@tailwindcss/oxide-freebsd-x64': 4.0.15 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.15 - '@tailwindcss/oxide-linux-arm64-gnu': 4.0.15 - '@tailwindcss/oxide-linux-arm64-musl': 4.0.15 - '@tailwindcss/oxide-linux-x64-gnu': 4.0.15 - '@tailwindcss/oxide-linux-x64-musl': 4.0.15 - '@tailwindcss/oxide-win32-arm64-msvc': 4.0.15 - '@tailwindcss/oxide-win32-x64-msvc': 4.0.15 + '@tailwindcss/oxide-android-arm64': 4.1.0 + '@tailwindcss/oxide-darwin-arm64': 4.1.0 + '@tailwindcss/oxide-darwin-x64': 4.1.0 + '@tailwindcss/oxide-freebsd-x64': 4.1.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.0 + '@tailwindcss/oxide-linux-x64-musl': 4.1.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.0 '@tailwindcss/typography@0.5.7(tailwindcss@3.4.17)': dependencies: @@ -4710,7 +4710,7 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@4.0.6: {} + tailwindcss@4.1.1: {} tapable@2.2.1: {} From 97cfd2df7474bd0bcdf705ddf5f5d82aeb8a57a8 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 2 Apr 2025 10:19:13 -0400 Subject: [PATCH 03/10] Add CSS `var()` hovers when using v4 (#1289) This adds support for hovering over things like `var(--color-red-500)` or `var(--breakpoint-xl)` and showing the value from the theme like we already support with the `theme()` function. Additionally, I've improved the hovers for v4 theme keys. They now appear in a `@theme` block like you'd see them in your CSS and are also syntax highlighted. --- .../tests/hover/hover.test.js | 82 ++++++++++++++++++- .../getInvalidConfigPathDiagnostics.ts | 5 ++ .../src/hoverProvider.ts | 17 +++- .../src/util/find.ts | 6 +- .../src/util/state.ts | 2 +- packages/vscode-tailwindcss/CHANGELOG.md | 2 + 6 files changed, 108 insertions(+), 6 deletions(-) diff --git a/packages/tailwindcss-language-server/tests/hover/hover.test.js b/packages/tailwindcss-language-server/tests/hover/hover.test.js index 373773401..379f41996 100644 --- a/packages/tailwindcss-language-server/tests/hover/hover.test.js +++ b/packages/tailwindcss-language-server/tests/hover/hover.test.js @@ -1,5 +1,7 @@ -import { test } from 'vitest' +import { expect, test } from 'vitest' import { withFixture } from '../common' +import { css, defineTest } from '../../src/testing' +import { createClient } from '../utils/client' withFixture('basic', (c) => { async function testHover( @@ -396,7 +398,14 @@ withFixture('v4/basic', (c) => { expected: { contents: { kind: 'markdown', - value: ['```plaintext', '80rem /* 1280px */', '```'].join('\n'), + value: [ + // + '```css', + '@theme {', + ' --breakpoint-xl: 80rem /* 1280px */;', + '}', + '```', + ].join('\n'), }, range: { start: { line: 0, character: 23 }, @@ -544,3 +553,72 @@ withFixture('v4/path-mappings', (c) => { }, }) }) + +defineTest({ + name: 'Can hover showing theme values used in var(…) and theme(…) functions', + fs: { + 'app.css': css` + @import 'tailwindcss'; + `, + }, + + prepare: async ({ root }) => ({ client: await createClient({ root }) }), + + handle: async ({ client }) => { + let doc = await client.open({ + lang: 'css', + text: css` + .foo { + color: theme(--color-black); + } + .bar { + color: var(--color-black); + } + `, + }) + + // color: theme(--color-black); + // ^ + let hoverTheme = await doc.hover({ line: 1, character: 18 }) + + // color: var(--color-black); + // ^ + let hoverVar = await doc.hover({ line: 4, character: 16 }) + + expect(hoverTheme).toEqual({ + contents: { + kind: 'markdown', + value: [ + // + '```css', + '@theme {', + ' --color-black: #000;', + '}', + '```', + ].join('\n'), + }, + range: { + start: { line: 1, character: 15 }, + end: { line: 1, character: 28 }, + }, + }) + + expect(hoverVar).toEqual({ + contents: { + kind: 'markdown', + value: [ + // + '```css', + '@theme {', + ' --color-black: #000;', + '}', + '```', + ].join('\n'), + }, + range: { + start: { line: 4, character: 13 }, + end: { line: 4, character: 26 }, + }, + }) + }, +}) diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts index d20655e33..76864281c 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts @@ -186,6 +186,11 @@ export function getInvalidConfigPathDiagnostics( findHelperFunctionsInDocument(state, document).forEach((helperFn) => { let base = helperFn.helper === 'theme' ? ['theme'] : [] + + // var(…) may not refer to theme values but other values in the cascade + // so they can't be unconditionally validated + if (helperFn.helper === 'var') return + let result = validateConfigPath(state, helperFn.path, base) if (result.isValid === true) { diff --git a/packages/tailwindcss-language-service/src/hoverProvider.ts b/packages/tailwindcss-language-service/src/hoverProvider.ts index 1db68bed8..583cc80f9 100644 --- a/packages/tailwindcss-language-service/src/hoverProvider.ts +++ b/packages/tailwindcss-language-service/src/hoverProvider.ts @@ -53,6 +53,8 @@ async function provideCssHelperHover( for (let helperFn of helperFns) { if (!isWithinRange(position, helperFn.ranges.path)) continue + if (helperFn.helper === 'var' && !state.v4) continue + let validated = validateConfigPath( state, helperFn.path, @@ -67,8 +69,21 @@ async function provideCssHelperHover( value = addPixelEquivalentsToValue(value, settings.tailwindCSS.rootFontSize) } + let lines = ['```plaintext', value, '```'] + + if (state.v4 && helperFn.path.startsWith('--')) { + lines = [ + // + '```css', + '@theme {', + ` ${helperFn.path}: ${value};`, + '}', + '```', + ] + } + return { - contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') }, + contents: { kind: 'markdown', value: lines.join('\n') }, range: helperFn.ranges.path, } } diff --git a/packages/tailwindcss-language-service/src/util/find.ts b/packages/tailwindcss-language-service/src/util/find.ts index 032187988..9118403da 100644 --- a/packages/tailwindcss-language-service/src/util/find.ts +++ b/packages/tailwindcss-language-service/src/util/find.ts @@ -405,7 +405,7 @@ export function findHelperFunctionsInRange( ): DocumentHelperFunction[] { const text = getTextWithoutComments(doc, 'css', range) let matches = findAll( - /(?[\W])(?config|theme|--theme)(?\(\s*)(?[^)]*?)\s*\)/g, + /(?[\W])(?config|theme|--theme|var)(?\(\s*)(?[^)]*?)\s*\)/g, text, ) @@ -450,10 +450,12 @@ export function findHelperFunctionsInRange( match.groups.helper.length + match.groups.innerPrefix.length - let helper: 'config' | 'theme' = 'config' + let helper: 'config' | 'theme' | 'var' = 'config' if (match.groups.helper === 'theme' || match.groups.helper === '--theme') { helper = 'theme' + } else if (match.groups.helper === 'var') { + helper = 'var' } return { diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index 119e5f59b..8c819eb8f 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -161,7 +161,7 @@ export type DocumentClassName = { } export type DocumentHelperFunction = { - helper: 'theme' | 'config' + helper: 'theme' | 'config' | 'var' path: string ranges: { full: Range diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index 421543f54..d55074be3 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -5,6 +5,8 @@ - Hide completions from CSS language server inside `@import "…" source(…)` ([#1091](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1091)) - Bump bundled v4 fallback to v4.1.1 ([#1294](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1294)) - Show color swatches for most new v4.1 utilities ([#1294](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1294)) +- Support theme key hovers in the CSS `var()` function ([#1289](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1289)) +- Show theme key hovers inside `@theme` for better context and syntax highlighting ([#1289](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1289)) # 0.14.12 From 5ffa254c4d239747755283795f91209296303c15 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 2 Apr 2025 10:26:31 -0400 Subject: [PATCH 04/10] 0.14.13 --- packages/tailwindcss-language-server/package.json | 2 +- packages/tailwindcss-language-service/package.json | 2 +- packages/vscode-tailwindcss/CHANGELOG.md | 4 ++++ packages/vscode-tailwindcss/package.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json index d96255dd3..7d142c903 100644 --- a/packages/tailwindcss-language-server/package.json +++ b/packages/tailwindcss-language-server/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/language-server", - "version": "0.14.12", + "version": "0.14.13", "description": "Tailwind CSS Language Server", "license": "MIT", "repository": { diff --git a/packages/tailwindcss-language-service/package.json b/packages/tailwindcss-language-service/package.json index 48fae16ee..23907838e 100644 --- a/packages/tailwindcss-language-service/package.json +++ b/packages/tailwindcss-language-service/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/language-service", - "version": "0.14.12", + "version": "0.14.13", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index d55074be3..6a8c2dac5 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -2,6 +2,10 @@ ## Prerelease +- Nothing yet! + +# 0.14.13 + - Hide completions from CSS language server inside `@import "…" source(…)` ([#1091](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1091)) - Bump bundled v4 fallback to v4.1.1 ([#1294](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1294)) - Show color swatches for most new v4.1 utilities ([#1294](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1294)) diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json index 26af65d38..4f55fb4a6 100644 --- a/packages/vscode-tailwindcss/package.json +++ b/packages/vscode-tailwindcss/package.json @@ -1,6 +1,6 @@ { "name": "vscode-tailwindcss", - "version": "0.14.12", + "version": "0.14.13", "displayName": "Tailwind CSS IntelliSense", "description": "Intelligent Tailwind CSS tooling for VS Code", "author": "Brad Cornes ", From 6c573a801d6afc65649951adb91cd5bb7b927a52 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 4 Apr 2025 13:16:28 -0400 Subject: [PATCH 05/10] v4: Improve DX around completions when prefixes are in use (#1292) Fixes #1291 - [x] Really needs tests --- .../tests/completions/completions.test.js | 74 +++++++++++++++++++ .../src/completionProvider.ts | 36 ++++----- .../src/util/getVariantsFromClassName.ts | 6 ++ 3 files changed, 98 insertions(+), 18 deletions(-) diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js index feb4999f1..ce5c7831e 100644 --- a/packages/tailwindcss-language-server/tests/completions/completions.test.js +++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js @@ -740,6 +740,80 @@ defineTest({ }, }) +defineTest({ + name: 'v4: Completions show after a variant arbitrary value, using prefixes', + fs: { + 'app.css': css` + @import 'tailwindcss' prefix(tw); + `, + }, + prepare: async ({ root }) => ({ client: await createClient({ root }) }), + handle: async ({ client }) => { + let document = await client.open({ + lang: 'html', + text: '
', + }) + + //
+ // ^ + let completion = await document.completions({ line: 0, character: 26 }) + + expect(completion?.items.length).toBe(19236) + }, +}) + +defineTest({ + name: 'v4: Variant and utility suggestions show prefix when one has been typed', + fs: { + 'app.css': css` + @import 'tailwindcss' prefix(tw); + `, + }, + prepare: async ({ root }) => ({ client: await createClient({ root }) }), + handle: async ({ client }) => { + let document = await client.open({ + lang: 'html', + text: '
', + }) + + //
+ // ^ + let completion = await document.completions({ line: 0, character: 12 }) + + expect(completion?.items.length).toBe(19237) + + // Verify that variants and utilities are all prefixed + let prefixed = completion.items.filter((item) => !item.label.startsWith('tw:')) + expect(prefixed).toHaveLength(0) + }, +}) + +defineTest({ + name: 'v4: Variant and utility suggestions hide prefix when it has been typed', + fs: { + 'app.css': css` + @import 'tailwindcss' prefix(tw); + `, + }, + prepare: async ({ root }) => ({ client: await createClient({ root }) }), + handle: async ({ client }) => { + let document = await client.open({ + lang: 'html', + text: '
', + }) + + //
+ // ^ + let completion = await document.completions({ line: 0, character: 15 }) + + expect(completion?.items.length).toBe(19236) + + // Verify that no variants and utilities have prefixes + let prefixed = completion.items.filter((item) => item.label.startsWith('tw:')) + expect(prefixed).toHaveLength(0) + }, +}) + defineTest({ name: 'v4: Completions show inside class functions in JS/TS files', fs: { diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts index 5c0c55528..843e9a8e8 100644 --- a/packages/tailwindcss-language-service/src/completionProvider.ts +++ b/packages/tailwindcss-language-service/src/completionProvider.ts @@ -261,29 +261,25 @@ export function completionsFromClassList( // TODO: This is a bit of a hack if (prefix.length > 0) { - // No variants seen: suggest the prefix only + // No variants seen: + // - suggest the prefix as a variant + // - Modify the remaining items to include the prefix in the variant name if (existingVariants.length === 0) { - items = items.slice(0, 1) + items = items.map((item, idx) => { + if (idx === 0) return item - return withDefaults( - { - isIncomplete: false, - items, - }, - { - data: { - ...(state.completionItemData ?? {}), - ...(important ? { important } : {}), - variants: existingVariants, - }, - range: replacementRange, - }, - state.editor.capabilities.itemDefaults, - ) + item.label = `${prefix}:${item.label}` + + if (item.textEditText) { + item.textEditText = `${prefix}:${item.textEditText}` + } + + return item + }) } // The first variant is not the prefix: don't suggest anything - if (existingVariants[0] !== prefix) { + if (existingVariants.length > 0 && existingVariants[0] !== prefix) { return null } } @@ -304,6 +300,10 @@ export function completionsFromClassList( documentation = formatColor(color) } + if (prefix.length > 0 && existingVariants.length === 0) { + className = `${prefix}:${className}` + } + items.push({ label: className, kind, diff --git a/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts b/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts index c30e729af..823b20f81 100644 --- a/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts +++ b/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts @@ -33,6 +33,12 @@ export function getVariantsFromClassName( // NOTE: This should never happen if (!state.designSystem) return false + let prefix = state.designSystem.theme.prefix ?? '' + + if (prefix !== '') { + className = `${prefix}:${className}` + } + // We don't use `compile()` so there's no overhead from PostCSS let compiled = state.designSystem.candidatesToCss([className]) From f8313abe86a548745ddbdcf6942e359a69706b9e Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 7 Apr 2025 12:57:56 -0400 Subject: [PATCH 06/10] Fix problem with too many ripgrep processes being spawned by VSCode (#1287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We run a file search on a per-workspace basis but we wait until a file has been opened. However, there are two big problems here: - A file being "opened" does **not** mean it is visible. Just that an extension has, effectively, taken an interest in it, can read its contents, etc. This happens for things like tsconfig files, some files inside a `.git` folder, etc… - We're running the search any time we see an opened document. What should happen is that we run the search when a document is opened _and visible_, the language server has not started, and we need to checking a workspace folder that has not been searched yet. This code here needs to be restructured to ensure that these searches only run when they are needed. If the searches don't return anything or time out then they should not be run again. Notifications from file watching should take care of the rest in case the initial search turned up nothing and the user adds a file that should cause the server to start. Fixes #986 (for real maybe this time??) --- packages/vscode-tailwindcss/CHANGELOG.md | 2 +- packages/vscode-tailwindcss/src/analyze.ts | 93 +++++++++ packages/vscode-tailwindcss/src/api.ts | 49 +++++ packages/vscode-tailwindcss/src/exclusions.ts | 49 +++++ packages/vscode-tailwindcss/src/extension.ts | 190 +++--------------- 5 files changed, 222 insertions(+), 161 deletions(-) create mode 100644 packages/vscode-tailwindcss/src/analyze.ts create mode 100644 packages/vscode-tailwindcss/src/api.ts create mode 100644 packages/vscode-tailwindcss/src/exclusions.ts diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index 6a8c2dac5..661c8d174 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -2,7 +2,7 @@ ## Prerelease -- Nothing yet! +- Only scan the file system once when needed ([#1287](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1287)) # 0.14.13 diff --git a/packages/vscode-tailwindcss/src/analyze.ts b/packages/vscode-tailwindcss/src/analyze.ts new file mode 100644 index 000000000..ec9b2b9a3 --- /dev/null +++ b/packages/vscode-tailwindcss/src/analyze.ts @@ -0,0 +1,93 @@ +import { workspace, RelativePattern, CancellationToken, Uri, WorkspaceFolder } from 'vscode' +import braces from 'braces' +import { CONFIG_GLOB, CSS_GLOB } from '@tailwindcss/language-server/src/lib/constants' +import { getExcludePatterns } from './exclusions' + +export interface SearchOptions { + folders: readonly WorkspaceFolder[] + token: CancellationToken +} + +export async function anyWorkspaceFoldersNeedServer({ folders, token }: SearchOptions) { + // An explicit config file setting means we need the server + for (let folder of folders) { + let settings = workspace.getConfiguration('tailwindCSS', folder) + let configFilePath = settings.get('experimental.configFile') + + // No setting provided + if (!configFilePath) continue + + // Ths config file may be a string: + // A path pointing to a CSS or JS config file + if (typeof configFilePath === 'string') return true + + // Ths config file may be an object: + // A map of config files to one or more globs + // + // If we get an empty object the language server will do a search anyway so + // we'll act as if no option was passed to be consistent + if (typeof configFilePath === 'object' && Object.values(configFilePath).length > 0) return true + } + + let configs: Array<() => Thenable> = [] + let stylesheets: Array<() => Thenable> = [] + + for (let folder of folders) { + let exclusions = getExcludePatterns(folder).flatMap((pattern) => braces.expand(pattern)) + let exclude = `{${exclusions.join(',').replace(/{/g, '%7B').replace(/}/g, '%7D')}}` + + configs.push(() => + workspace.findFiles( + new RelativePattern(folder, `**/${CONFIG_GLOB}`), + exclude, + undefined, + token, + ), + ) + + stylesheets.push(() => + workspace.findFiles(new RelativePattern(folder, `**/${CSS_GLOB}`), exclude, undefined, token), + ) + } + + // If we find a config file then we need the server + let configUrls = await Promise.all(configs.map((fn) => fn())) + for (let group of configUrls) { + if (group.length > 0) { + return true + } + } + + // If we find a possibly-related stylesheet then we need the server + // The step is done last because it requires reading individual files + // to determine if the server should be started. + // + // This is also, unfortunately, prone to starting the server unncessarily + // in projects that don't use TailwindCSS so we do this one-by-one instead + // of all at once to keep disk I/O low. + let stylesheetUrls = await Promise.all(stylesheets.map((fn) => fn())) + for (let group of stylesheetUrls) { + for (let file of group) { + if (await fileMayBeTailwindRelated(file)) { + return true + } + } + } +} + +let HAS_CONFIG = /@config\s*['"]/ +let HAS_IMPORT = /@import\s*['"]/ +let HAS_TAILWIND = /@tailwind\s*[^;]+;/ +let HAS_THEME = /@theme\s*\{/ + +export async function fileMayBeTailwindRelated(uri: Uri) { + let buffer = await workspace.fs.readFile(uri) + let contents = buffer.toString() + + return ( + HAS_CONFIG.test(contents) || + HAS_IMPORT.test(contents) || + HAS_TAILWIND.test(contents) || + HAS_THEME.test(contents) + ) +} diff --git a/packages/vscode-tailwindcss/src/api.ts b/packages/vscode-tailwindcss/src/api.ts new file mode 100644 index 000000000..d7f67de64 --- /dev/null +++ b/packages/vscode-tailwindcss/src/api.ts @@ -0,0 +1,49 @@ +import { workspace, CancellationTokenSource, OutputChannel, ExtensionContext, Uri } from 'vscode' +import { anyWorkspaceFoldersNeedServer, fileMayBeTailwindRelated } from './analyze' + +interface ApiOptions { + context: ExtensionContext + outputChannel: OutputChannel +} + +export async function createApi({ context, outputChannel }: ApiOptions) { + let folderAnalysis: Promise | null = null + + async function workspaceNeedsLanguageServer() { + if (folderAnalysis) return folderAnalysis + + let source: CancellationTokenSource | null = new CancellationTokenSource() + source.token.onCancellationRequested(() => { + source?.dispose() + source = null + + outputChannel.appendLine( + 'Server was not started. Search for Tailwind CSS-related files was taking too long.', + ) + }) + + // Cancel the search after roughly 15 seconds + setTimeout(() => source?.cancel(), 15_000) + context.subscriptions.push(source) + + folderAnalysis ??= anyWorkspaceFoldersNeedServer({ + token: source.token, + folders: workspace.workspaceFolders ?? [], + }) + + let result = await folderAnalysis + source?.dispose() + return result + } + + async function stylesheetNeedsLanguageServer(uri: Uri) { + outputChannel.appendLine(`Checking if ${uri.fsPath} may be Tailwind-related…`) + + return fileMayBeTailwindRelated(uri) + } + + return { + workspaceNeedsLanguageServer, + stylesheetNeedsLanguageServer, + } +} diff --git a/packages/vscode-tailwindcss/src/exclusions.ts b/packages/vscode-tailwindcss/src/exclusions.ts new file mode 100644 index 000000000..46ffd599a --- /dev/null +++ b/packages/vscode-tailwindcss/src/exclusions.ts @@ -0,0 +1,49 @@ +import { + workspace, + type WorkspaceConfiguration, + type ConfigurationScope, + type WorkspaceFolder, +} from 'vscode' +import picomatch from 'picomatch' +import * as path from 'node:path' + +function getGlobalExcludePatterns(scope: ConfigurationScope | null): string[] { + return Object.entries(workspace.getConfiguration('files', scope)?.get('exclude') ?? []) + .filter(([, value]) => value === true) + .map(([key]) => key) + .filter(Boolean) +} + +export function getExcludePatterns(scope: ConfigurationScope | null): string[] { + return [ + ...getGlobalExcludePatterns(scope), + ...(workspace.getConfiguration('tailwindCSS', scope).get('files.exclude')).filter( + Boolean, + ), + ] +} + +export function isExcluded(file: string, folder: WorkspaceFolder): boolean { + for (let pattern of getExcludePatterns(folder)) { + let matcher = picomatch(path.join(folder.uri.fsPath, pattern)) + + if (matcher(file)) { + return true + } + } + + return false +} + +export function mergeExcludes( + settings: WorkspaceConfiguration, + scope: ConfigurationScope | null, +): any { + return { + ...settings, + files: { + ...settings.files, + exclude: getExcludePatterns(scope), + }, + } +} diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts index a37486160..467d4e085 100755 --- a/packages/vscode-tailwindcss/src/extension.ts +++ b/packages/vscode-tailwindcss/src/extension.ts @@ -4,9 +4,7 @@ import type { TextDocument, WorkspaceFolder, ConfigurationScope, - WorkspaceConfiguration, Selection, - CancellationToken, } from 'vscode' import { workspace as Workspace, @@ -16,8 +14,6 @@ import { SymbolInformation, Position, Range, - RelativePattern, - CancellationTokenSource, } from 'vscode' import type { DocumentFilter, @@ -34,11 +30,11 @@ import { languages as defaultLanguages } from '@tailwindcss/language-service/src import * as semver from '@tailwindcss/language-service/src/util/semver' import isObject from '@tailwindcss/language-service/src/util/isObject' import namedColors from 'color-name' -import picomatch from 'picomatch' import { CONFIG_GLOB, CSS_GLOB } from '@tailwindcss/language-server/src/lib/constants' -import braces from 'braces' import normalizePath from 'normalize-path' import * as servers from './servers/index' +import { isExcluded, mergeExcludes } from './exclusions' +import { createApi } from './api' const colorNames = Object.keys(namedColors) @@ -52,60 +48,6 @@ function getUserLanguages(folder?: WorkspaceFolder): Record { return isObject(langs) ? langs : {} } -function getGlobalExcludePatterns(scope: ConfigurationScope | null): string[] { - return Object.entries(Workspace.getConfiguration('files', scope)?.get('exclude') ?? []) - .filter(([, value]) => value === true) - .map(([key]) => key) - .filter(Boolean) -} - -function getExcludePatterns(scope: ConfigurationScope | null): string[] { - return [ - ...getGlobalExcludePatterns(scope), - ...(Workspace.getConfiguration('tailwindCSS', scope).get('files.exclude')).filter( - Boolean, - ), - ] -} - -function isExcluded(file: string, folder: WorkspaceFolder): boolean { - for (let pattern of getExcludePatterns(folder)) { - let matcher = picomatch(path.join(folder.uri.fsPath, pattern)) - - if (matcher(file)) { - return true - } - } - - return false -} - -function mergeExcludes(settings: WorkspaceConfiguration, scope: ConfigurationScope | null): any { - return { - ...settings, - files: { - ...settings.files, - exclude: getExcludePatterns(scope), - }, - } -} - -async function fileMayBeTailwindRelated(uri: Uri) { - let contents = (await Workspace.fs.readFile(uri)).toString() - - let HAS_CONFIG = /@config\s*['"]/ - let HAS_IMPORT = /@import\s*['"]/ - let HAS_TAILWIND = /@tailwind\s*[^;]+;/ - let HAS_THEME = /@theme\s*\{/ - - return ( - HAS_CONFIG.test(contents) || - HAS_IMPORT.test(contents) || - HAS_TAILWIND.test(contents) || - HAS_THEME.test(contents) - ) -} - function selectionsAreEqual( aSelections: readonly Selection[], bSelections: readonly Selection[], @@ -177,6 +119,12 @@ function resetActiveTextEditorContext(): void { export async function activate(context: ExtensionContext) { let outputChannel = Window.createOutputChannel(CLIENT_NAME) + + let api = await createApi({ + context, + outputChannel, + }) + context.subscriptions.push(outputChannel) context.subscriptions.push( commands.registerCommand('tailwindCSS.showOutput', () => { @@ -266,10 +214,10 @@ export async function activate(context: ExtensionContext) { let configWatcher = Workspace.createFileSystemWatcher(`**/${CONFIG_GLOB}`, false, true, true) configWatcher.onDidCreate(async (uri) => { + if (currentClient) return let folder = Workspace.getWorkspaceFolder(uri) - if (!folder || isExcluded(uri.fsPath, folder)) { - return - } + if (!folder || isExcluded(uri.fsPath, folder)) return + await bootWorkspaceClient() }) @@ -278,13 +226,12 @@ export async function activate(context: ExtensionContext) { let cssWatcher = Workspace.createFileSystemWatcher(`**/${CSS_GLOB}`, false, false, true) async function bootClientIfCssFileMayBeTailwindRelated(uri: Uri) { + if (currentClient) return let folder = Workspace.getWorkspaceFolder(uri) - if (!folder || isExcluded(uri.fsPath, folder)) { - return - } - if (await fileMayBeTailwindRelated(uri)) { - await bootWorkspaceClient() - } + if (!folder || isExcluded(uri.fsPath, folder)) return + if (!(await api.stylesheetNeedsLanguageServer(uri))) return + + await bootWorkspaceClient() } cssWatcher.onDidCreate(bootClientIfCssFileMayBeTailwindRelated) @@ -578,111 +525,34 @@ export async function activate(context: ExtensionContext) { return client } - async function bootClientIfNeeded(): Promise { - if (currentClient) { - return - } - - let source: CancellationTokenSource | null = new CancellationTokenSource() - source.token.onCancellationRequested(() => { - source?.dispose() - source = null - outputChannel.appendLine( - 'Server was not started. Search for Tailwind CSS-related files was taking too long.', - ) - }) - - // Cancel the search after roughly 15 seconds - setTimeout(() => source?.cancel(), 15_000) - - if (!(await anyFolderNeedsLanguageServer(Workspace.workspaceFolders ?? [], source!.token))) { - source?.dispose() - return - } - - source?.dispose() - - await bootWorkspaceClient() - } - - async function anyFolderNeedsLanguageServer( - folders: readonly WorkspaceFolder[], - token: CancellationToken, - ): Promise { - for (let folder of folders) { - if (await folderNeedsLanguageServer(folder, token)) { - return true - } - } - - return false - } - - async function folderNeedsLanguageServer( - folder: WorkspaceFolder, - token: CancellationToken, - ): Promise { - let settings = Workspace.getConfiguration('tailwindCSS', folder) - if (settings.get('experimental.configFile') !== null) { - return true - } - - let exclude = `{${getExcludePatterns(folder) - .flatMap((pattern) => braces.expand(pattern)) - .join(',') - .replace(/{/g, '%7B') - .replace(/}/g, '%7D')}}` - - let configFiles = await Workspace.findFiles( - new RelativePattern(folder, `**/${CONFIG_GLOB}`), - exclude, - 1, - token, - ) - - for (let file of configFiles) { - return true - } - - let cssFiles = await Workspace.findFiles( - new RelativePattern(folder, `**/${CSS_GLOB}`), - exclude, - undefined, - token, - ) - - for (let file of cssFiles) { - outputChannel.appendLine(`Checking if ${file.fsPath} may be Tailwind-related…`) - - if (await fileMayBeTailwindRelated(file)) { - return true - } - } - - return false - } - + /** + * Note that this method can fire *many* times even for documents that are + * not in a visible editor. It's critical that this doesn't start any + * expensive operations more than is necessary. + */ async function didOpenTextDocument(document: TextDocument): Promise { if (document.languageId === 'tailwindcss') { servers.css.boot(context, outputChannel) } + if (currentClient) return + // We are only interested in language mode text - if (document.uri.scheme !== 'file') { - return - } + if (document.uri.scheme !== 'file') return - let uri = document.uri - let folder = Workspace.getWorkspaceFolder(uri) + let folder = Workspace.getWorkspaceFolder(document.uri) // Files outside a folder can't be handled. This might depend on the language. // Single file languages like JSON might handle files outside the workspace folders. - if (!folder) return + if (!folder || isExcluded(document.uri.fsPath, folder)) return + + if (!(await api.workspaceNeedsLanguageServer())) return - await bootClientIfNeeded() + await bootWorkspaceClient() } context.subscriptions.push(Workspace.onDidOpenTextDocument(didOpenTextDocument)) + Workspace.textDocuments.forEach(didOpenTextDocument) context.subscriptions.push( Workspace.onDidChangeWorkspaceFolders(async () => { From 09ae8ffb93116cbe32ace77dea7b8dc7ebffd027 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 18 Mar 2025 10:33:28 -0400 Subject: [PATCH 07/10] Add test for recursive symlinks --- .../src/project-locator.test.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts index 3699c8402..030bc5857 100644 --- a/packages/tailwindcss-language-server/src/project-locator.test.ts +++ b/packages/tailwindcss-language-server/src/project-locator.test.ts @@ -428,6 +428,40 @@ testLocator({ ], }) +testLocator({ + name: 'Recursive symlinks do not cause infinite traversal loops', + fs: { + 'src/a/b/c/index.css': css` + @import 'tailwindcss'; + `, + 'src/a/b/c/z': symlinkTo('src'), + 'src/a/b/x': symlinkTo('src'), + 'src/a/b/y': symlinkTo('src'), + 'src/a/b/z': symlinkTo('src'), + 'src/a/x': symlinkTo('src'), + + 'src/b/c/d/z': symlinkTo('src'), + 'src/b/c/d/index.css': css``, + 'src/b/c/x': symlinkTo('src'), + 'src/b/c/y': symlinkTo('src'), + 'src/b/c/z': symlinkTo('src'), + 'src/b/x': symlinkTo('src'), + + 'src/c/d/e/z': symlinkTo('src'), + 'src/c/d/x': symlinkTo('src'), + 'src/c/d/y': symlinkTo('src'), + 'src/c/d/z': symlinkTo('src'), + 'src/c/x': symlinkTo('src'), + }, + expected: [ + { + version: '4.0.6 (bundled)', + config: '/src/a/b/c/index.css', + content: [], + }, + ], +}) + // --- function testLocator({ From f4b14889c5dbdd381eb4fdbc8c09545c1b7244c1 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sat, 22 Mar 2025 10:10:39 -0400 Subject: [PATCH 08/10] Replace `fast-glob` with `tinyglobby` --- .../tailwindcss-language-server/package.json | 2 +- .../src/project-locator.ts | 9 ++-- .../tests/prepare.mjs | 12 +++-- pnpm-lock.yaml | 49 +++++++++---------- 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json index 7d142c903..c4dc2984f 100644 --- a/packages/tailwindcss-language-server/package.json +++ b/packages/tailwindcss-language-server/package.json @@ -67,7 +67,6 @@ "dset": "3.1.4", "enhanced-resolve": "^5.16.1", "esbuild": "^0.25.0", - "fast-glob": "3.2.4", "find-up": "5.0.0", "jiti": "^2.3.3", "klona": "2.0.4", @@ -85,6 +84,7 @@ "stack-trace": "0.0.10", "tailwindcss": "3.4.17", "tailwindcss-v4": "npm:tailwindcss@4.1.1", + "tinyglobby": "^0.2.12", "tsconfck": "^3.1.4", "tsconfig-paths": "^4.2.0", "typescript": "5.3.3", diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts index c04738bf8..de562b633 100644 --- a/packages/tailwindcss-language-server/src/project-locator.ts +++ b/packages/tailwindcss-language-server/src/project-locator.ts @@ -1,7 +1,6 @@ -import * as os from 'node:os' import * as path from 'node:path' import * as fs from 'node:fs/promises' -import glob from 'fast-glob' +import { glob } from 'tinyglobby' import picomatch from 'picomatch' import type { Settings } from '@tailwindcss/language-service/src/util/state' import { CONFIG_GLOB, CSS_GLOB } from './lib/constants' @@ -276,14 +275,14 @@ export class ProjectLocator { private async findConfigs(): Promise { // Look for config files and CSS files - let files = await glob([`**/${CONFIG_GLOB}`, `**/${CSS_GLOB}`], { + let files = await glob({ + patterns: [`**/${CONFIG_GLOB}`, `**/${CSS_GLOB}`], cwd: this.base, ignore: this.settings.tailwindCSS.files.exclude, onlyFiles: true, absolute: true, - suppressErrors: true, + followSymbolicLinks: true, dot: true, - concurrency: Math.max(os.cpus().length, 1), }) let realpaths = await Promise.all(files.map((file) => fs.realpath(file))) diff --git a/packages/tailwindcss-language-server/tests/prepare.mjs b/packages/tailwindcss-language-server/tests/prepare.mjs index 19b2c6d5a..c529e6300 100644 --- a/packages/tailwindcss-language-server/tests/prepare.mjs +++ b/packages/tailwindcss-language-server/tests/prepare.mjs @@ -2,19 +2,23 @@ import { exec } from 'node:child_process' import * as path from 'node:path' import { fileURLToPath } from 'node:url' import { promisify } from 'node:util' -import glob from 'fast-glob' +import { glob } from 'tinyglobby' const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const fixtures = glob.sync(['tests/fixtures/*/package.json', 'tests/fixtures/v4/*/package.json'], { - cwd: path.resolve(__dirname, '..'), +const root = path.resolve(__dirname, '..') + +const fixtures = await glob({ + cwd: root, + patterns: ['tests/fixtures/*/package.json', 'tests/fixtures/v4/*/package.json'], + absolute: true, }) const execAsync = promisify(exec) await Promise.all( fixtures.map(async (fixture) => { - console.log(`Installing dependencies for ${fixture}`) + console.log(`Installing dependencies for ${path.relative(root, fixture)}`) await execAsync('npm install', { cwd: path.dirname(fixture) }) }), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e879d3a6a..c4e0edafc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,9 +131,6 @@ importers: esbuild: specifier: ^0.25.0 version: 0.25.0 - fast-glob: - specifier: 3.2.4 - version: 3.2.4 find-up: specifier: 5.0.0 version: 5.0.0 @@ -185,6 +182,9 @@ importers: tailwindcss-v4: specifier: npm:tailwindcss@4.1.1 version: tailwindcss@4.1.1 + tinyglobby: + specifier: ^0.2.12 + version: 0.2.12 tsconfck: specifier: ^3.1.4 version: 3.1.4(typescript@5.3.3) @@ -1480,10 +1480,6 @@ packages: resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==} engines: {node: '>=12.0.0'} - fast-glob@3.2.4: - resolution: {integrity: sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==} - engines: {node: '>=8'} - fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -1494,6 +1490,14 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.4.3: + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1815,10 +1819,6 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} - engines: {node: '>=8.6'} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -2467,6 +2467,10 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.12: + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} + engines: {node: '>=12.0.0'} + tinypool@1.0.2: resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -3717,15 +3721,6 @@ snapshots: expect-type@1.2.0: {} - fast-glob@3.2.4: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.7 - picomatch: 2.3.1 - fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3742,6 +3737,10 @@ snapshots: dependencies: pend: 1.2.0 + fdir@6.4.3(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -4045,11 +4044,6 @@ snapshots: merge2@1.4.1: {} - micromatch@4.0.7: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -4743,6 +4737,11 @@ snapshots: tinyexec@0.3.2: {} + tinyglobby@0.2.12: + dependencies: + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 + tinypool@1.0.2: {} tinyrainbow@2.0.0: {} From 7ff6f78300d12eac32a3a54d5b15478055cfe596 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sat, 22 Mar 2025 14:31:40 -0400 Subject: [PATCH 09/10] Make sure to specify symlink type This is required for the tests to function properly on Windows --- .../src/project-locator.test.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts index 030bc5857..d8c35479b 100644 --- a/packages/tailwindcss-language-server/src/project-locator.test.ts +++ b/packages/tailwindcss-language-server/src/project-locator.test.ts @@ -434,24 +434,24 @@ testLocator({ 'src/a/b/c/index.css': css` @import 'tailwindcss'; `, - 'src/a/b/c/z': symlinkTo('src'), - 'src/a/b/x': symlinkTo('src'), - 'src/a/b/y': symlinkTo('src'), - 'src/a/b/z': symlinkTo('src'), - 'src/a/x': symlinkTo('src'), + 'src/a/b/c/z': symlinkTo('src', 'dir'), + 'src/a/b/x': symlinkTo('src', 'dir'), + 'src/a/b/y': symlinkTo('src', 'dir'), + 'src/a/b/z': symlinkTo('src', 'dir'), + 'src/a/x': symlinkTo('src', 'dir'), - 'src/b/c/d/z': symlinkTo('src'), + 'src/b/c/d/z': symlinkTo('src', 'dir'), 'src/b/c/d/index.css': css``, - 'src/b/c/x': symlinkTo('src'), - 'src/b/c/y': symlinkTo('src'), - 'src/b/c/z': symlinkTo('src'), - 'src/b/x': symlinkTo('src'), - - 'src/c/d/e/z': symlinkTo('src'), - 'src/c/d/x': symlinkTo('src'), - 'src/c/d/y': symlinkTo('src'), - 'src/c/d/z': symlinkTo('src'), - 'src/c/x': symlinkTo('src'), + 'src/b/c/x': symlinkTo('src', 'dir'), + 'src/b/c/y': symlinkTo('src', 'dir'), + 'src/b/c/z': symlinkTo('src', 'dir'), + 'src/b/x': symlinkTo('src', 'dir'), + + 'src/c/d/e/z': symlinkTo('src', 'dir'), + 'src/c/d/x': symlinkTo('src', 'dir'), + 'src/c/d/y': symlinkTo('src', 'dir'), + 'src/c/d/z': symlinkTo('src', 'dir'), + 'src/c/x': symlinkTo('src', 'dir'), }, expected: [ { From 9af5f2bd94e56bba19e4dcbc2f8e413a1dae6e0b Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 7 Apr 2025 13:00:55 -0400 Subject: [PATCH 10/10] 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 661c8d174..1750eb693 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -3,6 +3,7 @@ ## Prerelease - Only scan the file system once when needed ([#1287](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1287)) +- Don't follow recursive symlinks when searching for projects ([#1270](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1270)) # 0.14.13