From b4b3a2a0351a2135e1bd34df7a22aae53288e540 Mon Sep 17 00:00:00 2001
From: Laurynas Grigutis <34269850+LaurynasGr@users.noreply.github.com>
Date: Wed, 19 Mar 2025 15:54:20 +0200
Subject: [PATCH 01/93] Add `tailwindCSS.classFunctions` setting (#1258)
This PR adds `tailwindCSS.classFunctions` option to the settings to add
simple and performant class completions, hover previews, linting etc.
for such cases:
```ts
const classes = cn(
'pointer-events-auto relative flex bg-red-500',
'items-center justify-between overflow-hidden',
'md:min-w-[20rem] md:max-w-[37.5rem] md:py-sm pl-md py-xs pr-xs gap-sm w-full',
'data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)]',
Date.now() > 15000 ? 'text-red-200' : 'text-red-700',
'data-[swipe=move]:transition-none',
)
```

```ts
const variants = cva(
cn(
'pointer-events-auto relative flex bg-green-500',
'md:min-w-[20rem] md:max-w-[37.5rem] md:py-sm pl-md py-xs pr-xs gap-sm w-full',
'md:h-[calc(100%-2rem)]',
'bg-red-700',
),
{
variants: {
mobile: {
default: 'bottom-0 left-0',
fullScreen: `
inset-0
md:h-[calc(100%-2rem)]
rounded-none
bg-blue-700
`,
},
},
defaultVariants: {
mobile: 'default',
},
},
)
```

```ts
const tagged = cn`
pointer-events-auto relative flex bg-red-500
items-center justify-between overflow-hidden
md:min-w-[20rem] md:max-w-[37.5rem] md:py-sm pl-md py-xs pr-xs gap-sm w-full
data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)]
md:h-[calc(100%-2rem)] text-green-700
data-[swipe=move]:transition-none
`
```

---------
Co-authored-by: Laurynas Grigutis
Co-authored-by: Jordan Pittman
---
.../tailwindcss-language-server/src/config.ts | 41 +-
.../tests/utils/client.ts | 2 +-
.../tests/utils/configuration.ts | 41 +-
.../tests/utils/types.ts | 9 -
.../tailwindcss-language-service/package.json | 2 +
.../scripts/build.mjs | 2 +-
.../scripts/tsconfig.build.json | 4 +
.../src/completionProvider.ts | 23 +-
.../tailwindcss-language-service/src/types.ts | 9 +
.../src/util/find.test.ts | 800 ++++++++++++++++--
.../src/util/find.ts | 143 +++-
.../src/util/lexers.ts | 9 +
.../src/util/resolveRange.ts | 16 -
.../src/util/state.ts | 73 +-
.../tsconfig.json | 2 +-
packages/vscode-tailwindcss/README.md | 20 +
packages/vscode-tailwindcss/package.json | 8 +
pnpm-lock.yaml | 11 +
18 files changed, 999 insertions(+), 216 deletions(-)
delete mode 100644 packages/tailwindcss-language-server/tests/utils/types.ts
create mode 100644 packages/tailwindcss-language-service/scripts/tsconfig.build.json
create mode 100644 packages/tailwindcss-language-service/src/types.ts
delete mode 100644 packages/tailwindcss-language-service/src/util/resolveRange.ts
diff --git a/packages/tailwindcss-language-server/src/config.ts b/packages/tailwindcss-language-server/src/config.ts
index d8364d06..a5e68f7d 100644
--- a/packages/tailwindcss-language-server/src/config.ts
+++ b/packages/tailwindcss-language-server/src/config.ts
@@ -1,6 +1,9 @@
import merge from 'deepmerge'
import { isObject } from './utils'
-import type { Settings } from '@tailwindcss/language-service/src/util/state'
+import {
+ getDefaultTailwindSettings,
+ type Settings,
+} from '@tailwindcss/language-service/src/util/state'
import type { Connection } from 'vscode-languageserver'
export interface SettingsCache {
@@ -8,40 +11,6 @@ export interface SettingsCache {
clear(): void
}
-function getDefaultSettings(): Settings {
- return {
- editor: { tabSize: 2 },
- tailwindCSS: {
- inspectPort: null,
- emmetCompletions: false,
- classAttributes: ['class', 'className', 'ngClass', 'class:list'],
- codeActions: true,
- hovers: true,
- suggestions: true,
- validate: true,
- colorDecorators: true,
- rootFontSize: 16,
- lint: {
- cssConflict: 'warning',
- invalidApply: 'error',
- invalidScreen: 'error',
- invalidVariant: 'error',
- invalidConfigPath: 'error',
- invalidTailwindDirective: 'error',
- invalidSourceDirective: 'error',
- recommendedVariantOrder: 'warning',
- },
- showPixelEquivalents: true,
- includeLanguages: {},
- files: { exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'] },
- experimental: {
- classRegex: [],
- configFile: null,
- },
- },
- }
-}
-
export function createSettingsCache(connection: Connection): SettingsCache {
const cache: Map = new Map()
@@ -73,7 +42,7 @@ export function createSettingsCache(connection: Connection): SettingsCache {
tailwindCSS = isObject(tailwindCSS) ? tailwindCSS : {}
return merge(
- getDefaultSettings(),
+ getDefaultTailwindSettings(),
{ editor, tailwindCSS },
{ arrayMerge: (_destinationArray, sourceArray, _options) => sourceArray },
)
diff --git a/packages/tailwindcss-language-server/tests/utils/client.ts b/packages/tailwindcss-language-server/tests/utils/client.ts
index f7ee6e94..d6d317bb 100644
--- a/packages/tailwindcss-language-server/tests/utils/client.ts
+++ b/packages/tailwindcss-language-server/tests/utils/client.ts
@@ -44,7 +44,7 @@ import { createConfiguration, Configuration } from './configuration'
import { clearLanguageBoundariesCache } from '@tailwindcss/language-service/src/util/getLanguageBoundaries'
import { DefaultMap } from '../../src/util/default-map'
import { connect, ConnectOptions } from './connection'
-import type { DeepPartial } from './types'
+import type { DeepPartial } from '@tailwindcss/language-service/src/types'
export interface DocumentDescriptor {
/**
diff --git a/packages/tailwindcss-language-server/tests/utils/configuration.ts b/packages/tailwindcss-language-server/tests/utils/configuration.ts
index 2b4d6fbc..8c08a518 100644
--- a/packages/tailwindcss-language-server/tests/utils/configuration.ts
+++ b/packages/tailwindcss-language-server/tests/utils/configuration.ts
@@ -1,4 +1,7 @@
-import type { Settings } from '@tailwindcss/language-service/src/util/state'
+import {
+ getDefaultTailwindSettings,
+ type Settings,
+} from '@tailwindcss/language-service/src/util/state'
import { URI } from 'vscode-uri'
import type { DeepPartial } from './types'
import { CacheMap } from '../../src/cache-map'
@@ -10,41 +13,7 @@ export interface Configuration {
}
export function createConfiguration(): Configuration {
- let defaults: Settings = {
- editor: {
- tabSize: 2,
- },
- tailwindCSS: {
- inspectPort: null,
- emmetCompletions: false,
- includeLanguages: {},
- classAttributes: ['class', 'className', 'ngClass', 'class:list'],
- suggestions: true,
- hovers: true,
- codeActions: true,
- validate: true,
- showPixelEquivalents: true,
- rootFontSize: 16,
- colorDecorators: true,
- lint: {
- cssConflict: 'warning',
- invalidApply: 'error',
- invalidScreen: 'error',
- invalidVariant: 'error',
- invalidConfigPath: 'error',
- invalidTailwindDirective: 'error',
- invalidSourceDirective: 'error',
- recommendedVariantOrder: 'warning',
- },
- experimental: {
- classRegex: [],
- configFile: {},
- },
- files: {
- exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'],
- },
- },
- }
+ let defaults = getDefaultTailwindSettings()
/**
* Settings per file or directory URI
diff --git a/packages/tailwindcss-language-server/tests/utils/types.ts b/packages/tailwindcss-language-server/tests/utils/types.ts
deleted file mode 100644
index 6be62396..00000000
--- a/packages/tailwindcss-language-server/tests/utils/types.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export type DeepPartial = {
- [P in keyof T]?: T[P] extends (infer U)[]
- ? U[]
- : T[P] extends (...args: any) => any
- ? T[P] | undefined
- : T[P] extends object
- ? DeepPartial
- : T[P]
-}
diff --git a/packages/tailwindcss-language-service/package.json b/packages/tailwindcss-language-service/package.json
index f1047879..d5fddaaa 100644
--- a/packages/tailwindcss-language-service/package.json
+++ b/packages/tailwindcss-language-service/package.json
@@ -41,9 +41,11 @@
},
"devDependencies": {
"@types/css.escape": "^1.5.2",
+ "@types/dedent": "^0.7.2",
"@types/line-column": "^1.0.2",
"@types/node": "^18.19.33",
"@types/stringify-object": "^4.0.5",
+ "dedent": "^1.5.3",
"esbuild": "^0.25.0",
"esbuild-node-externals": "^1.9.0",
"minimist": "^1.2.8",
diff --git a/packages/tailwindcss-language-service/scripts/build.mjs b/packages/tailwindcss-language-service/scripts/build.mjs
index 913debc3..128426be 100644
--- a/packages/tailwindcss-language-service/scripts/build.mjs
+++ b/packages/tailwindcss-language-service/scripts/build.mjs
@@ -30,7 +30,7 @@ let build = await esbuild.context({
// Call the tsc command to generate the types
spawnSync(
'tsc',
- ['--emitDeclarationOnly', '--outDir', path.resolve(__dirname, '../dist')],
+ ['-p', path.resolve(__dirname, './tsconfig.build.json'), '--emitDeclarationOnly', '--outDir', path.resolve(__dirname, '../dist')],
{
stdio: 'inherit',
},
diff --git a/packages/tailwindcss-language-service/scripts/tsconfig.build.json b/packages/tailwindcss-language-service/scripts/tsconfig.build.json
new file mode 100644
index 00000000..e80bb38f
--- /dev/null
+++ b/packages/tailwindcss-language-service/scripts/tsconfig.build.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../tsconfig.json",
+ "exclude": ["../src/**/*.test.ts"]
+}
\ No newline at end of file
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index c67eb0f5..fe43bb68 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -15,7 +15,7 @@ import removeMeta from './util/removeMeta'
import { formatColor, getColor, getColorFromValue } from './util/color'
import { isHtmlContext, isHtmlDoc, isVueDoc } from './util/html'
import { isCssContext } from './util/css'
-import { findLast, matchClassAttributes } from './util/find'
+import { findLast, matchClassAttributes, matchClassFunctions } from './util/find'
import { stringifyConfigValue, stringifyCss } from './util/stringify'
import { stringifyScreen, Screen } from './util/screens'
import isObject from './util/isObject'
@@ -45,6 +45,8 @@ import type { ThemeEntry } from './util/v4'
import { segment } from './util/segment'
import { resolveKnownThemeKeys, resolveKnownThemeNamespaces } from './util/v4/theme-keys'
import { SEARCH_RANGE } from './util/constants'
+import { getLanguageBoundaries } from './util/getLanguageBoundaries'
+import { isWithinRange } from './util/isWithinRange'
let isUtil = (className) =>
Array.isArray(className.__info)
@@ -747,6 +749,25 @@ async function provideClassAttributeCompletions(
let matches = matchClassAttributes(str, settings.classAttributes)
+ let boundaries = getLanguageBoundaries(state, document)
+
+ for (let boundary of boundaries ?? []) {
+ let isJsContext = boundary.type === 'js' || boundary.type === 'jsx'
+ if (!isJsContext) continue
+ if (!settings.classFunctions?.length) continue
+ if (!isWithinRange(position, boundary.range)) continue
+
+ let str = document.getText(boundary.range)
+ let offset = document.offsetAt(boundary.range.start)
+ let fnMatches = matchClassFunctions(str, settings.classFunctions)
+
+ fnMatches.forEach((match) => {
+ if (match.index) match.index += offset
+ })
+
+ matches.push(...fnMatches)
+ }
+
if (matches.length === 0) {
return null
}
diff --git a/packages/tailwindcss-language-service/src/types.ts b/packages/tailwindcss-language-service/src/types.ts
new file mode 100644
index 00000000..c84f0c7a
--- /dev/null
+++ b/packages/tailwindcss-language-service/src/types.ts
@@ -0,0 +1,9 @@
+export type DeepPartial = {
+ [P in keyof T]?: T[P] extends ((...args: any) => any) | ReadonlyArray | Date
+ ? T[P]
+ : T[P] extends (infer U)[]
+ ? U[]
+ : T[P] extends object
+ ? DeepPartial
+ : T[P]
+}
diff --git a/packages/tailwindcss-language-service/src/util/find.test.ts b/packages/tailwindcss-language-service/src/util/find.test.ts
index 90dbcfb3..f9f17a96 100644
--- a/packages/tailwindcss-language-service/src/util/find.test.ts
+++ b/packages/tailwindcss-language-service/src/util/find.test.ts
@@ -1,81 +1,743 @@
-import type { State } from './state'
+import { createState, getDefaultTailwindSettings, Settings, type DocumentClassList } from './state'
import { test } from 'vitest'
import { TextDocument } from 'vscode-languageserver-textdocument'
import { findClassListsInHtmlRange } from './find'
+import type { DeepPartial } from '../types'
+import dedent from 'dedent'
-test('test', async ({ expect }) => {
- let content = [
- //
- '',
- ' ',
- '',
- ].join('\n')
-
- let doc = TextDocument.create('file://file.astro', 'astro', 1, content)
- let state: State = {
- blocklist: [],
+const js = dedent
+const html = dedent
+
+test('class regex works in astro', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.astro',
+ lang: 'astro',
+ settings: {
+ tailwindCSS: {
+ classAttributes: ['class'],
+ experimental: {
+ classRegex: [
+ ['cva\\(([^)]*)\\)', '["\'`]([^"\'`]*).*?["\'`]'],
+ ['cn\\(([^)]*)\\)', '["\'`]([^"\'`]*).*?["\'`]'],
+ ],
+ },
+ },
+ },
+ content: [
+ '',
+ ' ',
+ '',
+ ],
+ })
+
+ let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'html')
+
+ expect(classLists).toEqual([
+ {
+ classList: 'p-4 sm:p-2 $',
+ range: {
+ start: { line: 0, character: 10 },
+ end: { line: 0, character: 22 },
+ },
+ },
+ {
+ classList: 'underline',
+ range: {
+ start: { line: 0, character: 33 },
+ end: { line: 0, character: 42 },
+ },
+ },
+ {
+ classList: 'line-through',
+ range: {
+ start: { line: 0, character: 46 },
+ end: { line: 0, character: 58 },
+ },
+ },
+ ])
+})
+
+test('find class lists in functions', async ({ expect }) => {
+ let fileA = createDocument({
+ name: 'file.jsx',
+ lang: 'javascriptreact',
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx', 'cva'],
+ },
+ },
+ content: js`
+ // These should match
+ let classes = clsx(
+ 'flex p-4',
+ 'block sm:p-0',
+ Date.now() > 100 ? 'text-white' : 'text-black',
+ )
+
+ // These should match
+ let classes = cva(
+ 'flex p-4',
+ 'block sm:p-0',
+ Date.now() > 100 ? 'text-white' : 'text-black',
+ )
+ `,
+ })
+
+ let fileB = createDocument({
+ name: 'file.jsx',
+ lang: 'javascriptreact',
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx', 'cva'],
+ },
+ },
+ content: js`
+ let classes = cn(
+ 'flex p-4',
+ 'block sm:p-0',
+ Date.now() > 100 ? 'text-white' : 'text-black',
+ )
+ `,
+ })
+
+ let classListsA = await findClassListsInHtmlRange(fileA.state, fileA.doc, 'js')
+ let classListsB = await findClassListsInHtmlRange(fileB.state, fileB.doc, 'js')
+
+ expect(classListsA).toEqual([
+ // from clsx(…)
+ {
+ classList: 'flex p-4',
+ range: {
+ start: { line: 2, character: 3 },
+ end: { line: 2, character: 11 },
+ },
+ },
+ {
+ classList: 'block sm:p-0',
+ range: {
+ start: { line: 3, character: 3 },
+ end: { line: 3, character: 15 },
+ },
+ },
+ {
+ classList: 'text-white',
+ range: {
+ start: { line: 4, character: 22 },
+ end: { line: 4, character: 32 },
+ },
+ },
+ {
+ classList: 'text-black',
+ range: {
+ start: { line: 4, character: 37 },
+ end: { line: 4, character: 47 },
+ },
+ },
+
+ // from cva(…)
+ {
+ classList: 'flex p-4',
+ range: {
+ start: { line: 9, character: 3 },
+ end: { line: 9, character: 11 },
+ },
+ },
+ {
+ classList: 'block sm:p-0',
+ range: {
+ start: { line: 10, character: 3 },
+ end: { line: 10, character: 15 },
+ },
+ },
+ {
+ classList: 'text-white',
+ range: {
+ start: { line: 11, character: 22 },
+ end: { line: 11, character: 32 },
+ },
+ },
+ {
+ classList: 'text-black',
+ range: {
+ start: { line: 11, character: 37 },
+ end: { line: 11, character: 47 },
+ },
+ },
+ ])
+
+ // none from cn(…) since it's not in the list of class functions
+ expect(classListsB).toEqual([])
+})
+
+test('find class lists in nested fn calls', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.jsx',
+ lang: 'javascriptreact',
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx', 'cva'],
+ },
+ },
+
+ content: js`
+ // NOTE: All strings inside a matched class function will be treated as class lists
+ // TODO: Nested calls tha are *not* class functions should have their content ignored
+ let classes = clsx(
+ 'flex',
+ cn({
+ 'bg-red-500': true,
+ 'text-white': Date.now() > 100,
+ }),
+ clsx(
+ 'fixed',
+ 'absolute inset-0'
+ ),
+ cva(
+ ['bottom-0', 'border'],
+ {
+ variants: {
+ mobile: {
+ default: 'bottom-0 left-0',
+ large: \`
+ inset-0
+ rounded-none
+ \`,
+ },
+ }
+ }
+ )
+ )
+ `,
+ })
+
+ let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'html')
+
+ expect(classLists).toMatchObject([
+ {
+ classList: 'flex',
+ range: {
+ start: { line: 3, character: 3 },
+ end: { line: 3, character: 7 },
+ },
+ },
+
+ // TODO: This should be ignored because they're inside cn(…)
+ {
+ classList: 'bg-red-500',
+ range: {
+ start: { line: 5, character: 5 },
+ end: { line: 5, character: 15 },
+ },
+ },
+
+ // TODO: This should be ignored because they're inside cn(…)
+ {
+ classList: 'text-white',
+ range: {
+ start: { line: 6, character: 5 },
+ end: { line: 6, character: 15 },
+ },
+ },
+
+ {
+ classList: 'fixed',
+ range: {
+ start: { line: 9, character: 5 },
+ end: { line: 9, character: 10 },
+ },
+ },
+ {
+ classList: 'absolute inset-0',
+ range: {
+ start: { line: 10, character: 5 },
+ end: { line: 10, character: 21 },
+ },
+ },
+ {
+ classList: 'bottom-0',
+ range: {
+ start: { line: 13, character: 6 },
+ end: { line: 13, character: 14 },
+ },
+ },
+ {
+ classList: 'border',
+ range: {
+ start: { line: 13, character: 18 },
+ end: { line: 13, character: 24 },
+ },
+ },
+ {
+ classList: 'bottom-0 left-0',
+ range: {
+ start: { line: 17, character: 20 },
+ end: { line: 17, character: 35 },
+ },
+ },
+ {
+ classList: `inset-0\n rounded-none\n `,
+ range: {
+ start: { line: 19, character: 12 },
+ // TODO: Fix the range calculation. Its wrong on this one
+ end: { line: 20, character: 24 },
+ },
+ },
+ ])
+})
+
+test('find class lists in nested fn calls (only nested matches)', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.jsx',
+ lang: 'javascriptreact',
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx', 'cva'],
+ },
+ },
+
+ content: js`
+ let classes = cn(
+ 'flex',
+ cn({
+ 'bg-red-500': true,
+ 'text-white': Date.now() > 100,
+ }),
+ // NOTE: The only class lists appear inside this function because cn is
+ // not in the list of class functions
+ clsx(
+ 'fixed',
+ 'absolute inset-0'
+ ),
+ )
+ `,
+ })
+
+ let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'html')
+
+ expect(classLists).toMatchObject([
+ {
+ classList: 'fixed',
+ range: {
+ start: { line: 9, character: 5 },
+ end: { line: 9, character: 10 },
+ },
+ },
+ {
+ classList: 'absolute inset-0',
+ range: {
+ start: { line: 10, character: 5 },
+ end: { line: 10, character: 21 },
+ },
+ },
+ ])
+})
+
+test('find class lists in tagged template literals', async ({ expect }) => {
+ let fileA = createDocument({
+ name: 'file.jsx',
+ lang: 'javascriptreact',
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx', 'cva'],
+ },
+ },
+ content: js`
+ // These should match
+ let classes = clsx\`
+ flex p-4
+ block sm:p-0
+ \${Date.now() > 100 ? 'text-white' : 'text-black'}
+ \`
+
+ // These should match
+ let classes = cva\`
+ flex p-4
+ block sm:p-0
+ \${Date.now() > 100 ? 'text-white' : 'text-black'}
+ \`
+ `,
+ })
+
+ let fileB = createDocument({
+ name: 'file.jsx',
+ lang: 'javascriptreact',
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx', 'cva'],
+ },
+ },
+ content: js`
+ let classes = cn\`
+ flex p-4
+ block sm:p-0
+ \${Date.now() > 100 ? 'text-white' : 'text-black'}
+ \`
+ `,
+ })
+
+ let classListsA = await findClassListsInHtmlRange(fileA.state, fileA.doc, 'js')
+ let classListsB = await findClassListsInHtmlRange(fileB.state, fileB.doc, 'js')
+
+ expect(classListsA).toEqual([
+ // from clsx`…`
+ {
+ classList: 'flex p-4\n block sm:p-0\n $',
+ range: {
+ start: { line: 2, character: 2 },
+ end: { line: 4, character: 3 },
+ },
+ },
+ {
+ classList: 'text-white',
+ range: {
+ start: { line: 4, character: 24 },
+ end: { line: 4, character: 34 },
+ },
+ },
+ {
+ classList: 'text-black',
+ range: {
+ start: { line: 4, character: 39 },
+ end: { line: 4, character: 49 },
+ },
+ },
+
+ // from cva`…`
+ {
+ classList: 'flex p-4\n block sm:p-0\n $',
+ range: {
+ start: { line: 9, character: 2 },
+ end: { line: 11, character: 3 },
+ },
+ },
+ {
+ classList: 'text-white',
+ range: {
+ start: { line: 11, character: 24 },
+ end: { line: 11, character: 34 },
+ },
+ },
+ {
+ classList: 'text-black',
+ range: {
+ start: { line: 11, character: 39 },
+ end: { line: 11, character: 49 },
+ },
+ },
+ ])
+
+ // none from cn`…` since it's not in the list of class functions
+ expect(classListsB).toEqual([])
+})
+
+test('classFunctions can be a regex', async ({ expect }) => {
+ let fileA = createDocument({
+ name: 'file.jsx',
+ lang: 'javascriptreact',
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['tw\\.[a-z]+'],
+ },
+ },
+ content: js`
+ let classes = tw.div('flex p-4')
+ `,
+ })
+
+ let fileB = createDocument({
+ name: 'file.jsx',
+ lang: 'javascriptreact',
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['tw\\.[a-z]+'],
+ },
+ },
+ content: js`
+ let classes = tw.div.foo('flex p-4')
+ `,
+ })
+
+ let classListsA = await findClassListsInHtmlRange(fileA.state, fileA.doc, 'js')
+ let classListsB = await findClassListsInHtmlRange(fileB.state, fileB.doc, 'js')
+
+ expect(classListsA).toEqual([
+ {
+ classList: 'flex p-4',
+ range: {
+ start: { line: 0, character: 22 },
+ end: { line: 0, character: 30 },
+ },
+ },
+ ])
+
+ // none from tw.div.foo(`…`) since it does not match a class function
+ expect(classListsB).toEqual([])
+})
+
+test('classFunctions regexes only match on function names', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.jsx',
+ lang: 'javascriptreact',
+ settings: {
+ tailwindCSS: {
+ // A function name itself cannot contain a `:`
+ classFunctions: [':\\s*tw\\.[a-z]+'],
+ },
+ },
+ content: js`
+ let classes = tw.div('flex p-4')
+ let classes = { foo: tw.div('flex p-4') }
+ `,
+ })
+
+ let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'js')
+
+ expect(classLists).toEqual([])
+})
+
+test('Finds consecutive instances of a class function', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.js',
+ lang: 'javascript',
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['cn'],
+ },
+ },
+ content: js`
+ export const list = [
+ cn('relative flex bg-red-500'),
+ cn('relative flex bg-red-500'),
+ cn('relative flex bg-red-500'),
+ ]
+ `,
+ })
+
+ let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'js')
+
+ expect(classLists).toEqual([
+ {
+ classList: 'relative flex bg-red-500',
+ range: {
+ start: { line: 1, character: 6 },
+ end: { line: 1, character: 30 },
+ },
+ },
+ {
+ classList: 'relative flex bg-red-500',
+ range: {
+ start: { line: 2, character: 6 },
+ end: { line: 2, character: 30 },
+ },
+ },
+ {
+ classList: 'relative flex bg-red-500',
+ range: {
+ start: { line: 3, character: 6 },
+ end: { line: 3, character: 30 },
+ },
+ },
+ ])
+})
+
+test('classFunctions & classAttributes should not duplicate matches', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.jsx',
+ lang: 'javascriptreact',
+ settings: {
+ tailwindCSS: {
+ classAttributes: ['className'],
+ classFunctions: ['cva', 'clsx'],
+ },
+ },
+ content: js`
+ const Component = ({ className }) => (
+
+ CONTENT
+
+ )
+ const OtherComponent = ({ className }) => (
+
+ CONTENT
+
+ )
+ `,
+ })
+
+ let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'js')
+
+ expect(classLists).toEqual([
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 3, character: 7 },
+ end: { line: 3, character: 20 },
+ },
+ },
+ {
+ classList: 'inset-0 md:h-[calc(100%-2rem)]',
+ range: {
+ start: { line: 4, character: 7 },
+ end: { line: 4, character: 37 },
+ },
+ },
+ {
+ classList: 'rounded-none bg-blue-700',
+ range: {
+ start: { line: 5, character: 12 },
+ end: { line: 5, character: 36 },
+ },
+ },
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 14, character: 7 },
+ end: { line: 14, character: 20 },
+ },
+ },
+ {
+ classList: 'inset-0 md:h-[calc(100%-2rem)]',
+ range: {
+ start: { line: 15, character: 7 },
+ end: { line: 15, character: 37 },
+ },
+ },
+ {
+ classList: 'rounded-none bg-blue-700',
+ range: {
+ start: { line: 16, character: 12 },
+ end: { line: 16, character: 36 },
+ },
+ },
+ ])
+})
+
+test('classFunctions should only match in JS-like contexts', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.html',
+ lang: 'html',
+ settings: {
+ tailwindCSS: {
+ classAttributes: ['className'],
+ classFunctions: ['clsx'],
+ },
+ },
+ content: html`
+
+ clsx('relative flex') clsx('relative flex')
+
+
+
+
+
+ clsx('relative flex') clsx('relative flex')
+
+
+
+ `,
+ })
+
+ let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'js')
+
+ expect(classLists).toEqual([
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 5, character: 16 },
+ end: { line: 5, character: 29 },
+ },
+ },
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 6, character: 16 },
+ end: { line: 6, character: 29 },
+ },
+ },
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 14, character: 16 },
+ end: { line: 14, character: 29 },
+ },
+ },
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 15, character: 16 },
+ end: { line: 15, character: 29 },
+ },
+ },
+ ])
+})
+
+function createDocument({
+ name,
+ lang,
+ content,
+ settings,
+}: {
+ name: string
+ lang: string
+ content: string | string[]
+ settings: DeepPartial
+}) {
+ let doc = TextDocument.create(
+ `file://${name}`,
+ lang,
+ 1,
+ typeof content === 'string' ? content : content.join('\n'),
+ )
+ let defaults = getDefaultTailwindSettings()
+ let state = createState({
editor: {
- userLanguages: {},
getConfiguration: async () => ({
- editor: {
- tabSize: 1,
- },
+ ...defaults,
+ ...settings,
tailwindCSS: {
- classAttributes: ['class'],
- experimental: {
- classRegex: [
- ['cva\\(([^)]*)\\)', '["\'`]([^"\'`]*).*?["\'`]'],
- ['cn\\(([^)]*)\\)', '["\'`]([^"\'`]*).*?["\'`]'],
- ],
- },
- } as any,
- }),
- } as any,
- } as any
-
- let classLists = await findClassListsInHtmlRange(state, doc, 'html')
-
- expect(classLists).toMatchInlineSnapshot(`
- [
- {
- "classList": "p-4 sm:p-2 $",
- "range": {
- "end": {
- "character": 22,
- "line": 0,
+ ...defaults.tailwindCSS,
+ ...settings.tailwindCSS,
+ lint: {
+ ...defaults.tailwindCSS.lint,
+ ...(settings.tailwindCSS?.lint ?? {}),
},
- "start": {
- "character": 10,
- "line": 0,
- },
- },
- },
- {
- "classList": "underline",
- "range": {
- "end": {
- "character": 42,
- "line": 0,
+ experimental: {
+ ...defaults.tailwindCSS.experimental,
+ ...(settings.tailwindCSS?.experimental ?? {}),
},
- "start": {
- "character": 33,
- "line": 0,
+ files: {
+ ...defaults.tailwindCSS.files,
+ ...(settings.tailwindCSS?.files ?? {}),
},
},
- },
- {
- "classList": "line-through",
- "range": {
- "end": {
- "character": 58,
- "line": 0,
- },
- "start": {
- "character": 46,
- "line": 0,
- },
+ editor: {
+ ...defaults.editor,
+ ...settings.editor,
},
- },
- ]
- `)
-})
+ }),
+ },
+ })
+
+ return {
+ doc,
+ state,
+ }
+}
diff --git a/packages/tailwindcss-language-service/src/util/find.ts b/packages/tailwindcss-language-service/src/util/find.ts
index f38d7a16..03218798 100644
--- a/packages/tailwindcss-language-service/src/util/find.ts
+++ b/packages/tailwindcss-language-service/src/util/find.ts
@@ -9,7 +9,7 @@ import { isJsxContext } from './js'
import { dedupeByRange, flatten } from './array'
import { getClassAttributeLexer, getComputedClassAttributeLexer } from './lexers'
import { getLanguageBoundaries } from './getLanguageBoundaries'
-import { resolveRange } from './resolveRange'
+import { absoluteRange } from './absoluteRange'
import { getTextWithoutComments } from './doc'
import { isSemicolonlessCssLanguage } from './languages'
import { customClassesIn } from './classes'
@@ -164,20 +164,66 @@ export function matchClassAttributes(text: string, attributes: string[]): RegExp
return findAll(new RegExp(re.source.replace('ATTRS', attrs.join('|')), 'gi'), text)
}
+export function matchClassFunctions(text: string, fnNames: string[]): RegExpMatchArray[] {
+ // 1. Validate the list of function name patterns provided by the user
+ let names = fnNames.filter((x) => typeof x === 'string')
+ if (names.length === 0) return []
+
+ // 2. Extract function names in the document
+ // This is intentionally scoped to JS syntax for now but should be extended to
+ // other languages in the future
+ //
+ // This regex the JS pattern for an identifier + function call with some
+ // additional constraints:
+ //
+ // - It needs to be in an expression position — so it must be preceded by
+ // whitespace, parens, curlies, commas, whitespace, etc…
+ // - It must look like a fn call or a tagged template literal
+ let FN_NAMES = /(?<=^|[:=,;\s{()])([\p{ID_Start}$_][\p{ID_Continue}$_.]*)[(`]/dgiu
+ let foundFns = findAll(FN_NAMES, text)
+
+ // 3. Match against the function names in the document
+ let re = /^(NAMES)$/
+ let isClassFn = new RegExp(re.source.replace('NAMES', names.join('|')), 'i')
+
+ let matches = foundFns.filter((fn) => isClassFn.test(fn[1]))
+
+ return matches
+}
+
export async function findClassListsInHtmlRange(
state: State,
doc: TextDocument,
type: 'html' | 'js' | 'jsx',
range?: Range,
): Promise {
+ if (!state.editor) return []
+
const text = getTextWithoutComments(doc, type, range)
- const matches = matchClassAttributes(
- text,
- (await state.editor.getConfiguration(doc.uri)).tailwindCSS.classAttributes,
- )
+ const settings = (await state.editor.getConfiguration(doc.uri)).tailwindCSS
+ const matches = matchClassAttributes(text, settings.classAttributes)
- const result: DocumentClassList[] = []
+ let boundaries = getLanguageBoundaries(state, doc)
+
+ for (let boundary of boundaries ?? []) {
+ let isJsContext = boundary.type === 'js' || boundary.type === 'jsx'
+ if (!isJsContext) continue
+ if (!settings.classFunctions?.length) continue
+
+ let str = doc.getText(boundary.range)
+ let offset = doc.offsetAt(boundary.range.start)
+ let fnMatches = matchClassFunctions(str, settings.classFunctions)
+
+ fnMatches.forEach((match) => {
+ if (match.index) match.index += offset
+ })
+
+ matches.push(...fnMatches)
+ }
+
+ const existingResultSet = new Set()
+ const results: DocumentClassList[] = []
matches.forEach((match) => {
const subtext = text.substr(match.index + match[0].length - 1)
@@ -222,46 +268,53 @@ export async function findClassListsInHtmlRange(
})
}
- result.push(
- ...classLists
- .map(({ value, offset }) => {
- if (value.trim() === '') {
- return null
- }
+ classLists.forEach(({ value, offset }) => {
+ if (value.trim() === '') {
+ return null
+ }
- const before = value.match(/^\s*/)
- const beforeOffset = before === null ? 0 : before[0].length
- const after = value.match(/\s*$/)
- const afterOffset = after === null ? 0 : -after[0].length
-
- const start = indexToPosition(
- text,
- match.index + match[0].length - 1 + offset + beforeOffset,
- )
- const end = indexToPosition(
- text,
- match.index + match[0].length - 1 + offset + value.length + afterOffset,
- )
-
- return {
- classList: value.substr(beforeOffset, value.length + afterOffset),
- range: {
- start: {
- line: (range?.start.line || 0) + start.line,
- character: (end.line === 0 ? range?.start.character || 0 : 0) + start.character,
- },
- end: {
- line: (range?.start.line || 0) + end.line,
- character: (end.line === 0 ? range?.start.character || 0 : 0) + end.character,
- },
- },
- }
- })
- .filter((x) => x !== null),
- )
+ const before = value.match(/^\s*/)
+ const beforeOffset = before === null ? 0 : before[0].length
+ const after = value.match(/\s*$/)
+ const afterOffset = after === null ? 0 : -after[0].length
+
+ const start = indexToPosition(text, match.index + match[0].length - 1 + offset + beforeOffset)
+ const end = indexToPosition(
+ text,
+ match.index + match[0].length - 1 + offset + value.length + afterOffset,
+ )
+
+ const result: DocumentClassList = {
+ classList: value.substr(beforeOffset, value.length + afterOffset),
+ range: {
+ start: {
+ line: (range?.start.line || 0) + start.line,
+ character: (end.line === 0 ? range?.start.character || 0 : 0) + start.character,
+ },
+ end: {
+ line: (range?.start.line || 0) + end.line,
+ character: (end.line === 0 ? range?.start.character || 0 : 0) + end.character,
+ },
+ },
+ }
+
+ const resultKey = [
+ result.classList,
+ result.range.start.line,
+ result.range.start.character,
+ result.range.end.line,
+ result.range.end.character,
+ ].join(':')
+
+ // No need to add the result if it was already matched
+ if (!existingResultSet.has(resultKey)) {
+ existingResultSet.add(resultKey)
+ results.push(result)
+ }
+ })
})
- return result
+ return results
}
export async function findClassListsInRange(
@@ -407,14 +460,14 @@ export function findHelperFunctionsInRange(
helper,
path,
ranges: {
- full: resolveRange(
+ full: absoluteRange(
{
start: indexToPosition(text, startIndex),
end: indexToPosition(text, startIndex + match.groups.path.length),
},
range,
),
- path: resolveRange(
+ path: absoluteRange(
{
start: indexToPosition(text, startIndex + quotesBefore.length),
end: indexToPosition(text, startIndex + quotesBefore.length + path.length),
diff --git a/packages/tailwindcss-language-service/src/util/lexers.ts b/packages/tailwindcss-language-service/src/util/lexers.ts
index 38ecc2d7..a8315f51 100644
--- a/packages/tailwindcss-language-service/src/util/lexers.ts
+++ b/packages/tailwindcss-language-service/src/util/lexers.ts
@@ -29,6 +29,14 @@ const classAttributeStates: () => { [x: string]: moo.Rules } = () => ({
rbrace: { match: new RegExp('(? {
start2: { match: "'", push: 'singleClassList' },
start3: { match: '{', push: 'interpBrace' },
start4: { match: '`', push: 'tickClassList' },
+ start5: { match: '(', push: 'interpParen' },
},
...classAttributeStates(),
})
diff --git a/packages/tailwindcss-language-service/src/util/resolveRange.ts b/packages/tailwindcss-language-service/src/util/resolveRange.ts
deleted file mode 100644
index d90fa5b9..00000000
--- a/packages/tailwindcss-language-service/src/util/resolveRange.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { Range } from 'vscode-languageserver'
-
-export function resolveRange(range: Range, relativeTo?: Range) {
- return {
- start: {
- line: (relativeTo?.start.line || 0) + range.start.line,
- character:
- (range.end.line === 0 ? relativeTo?.start.character || 0 : 0) + range.start.character,
- },
- end: {
- line: (relativeTo?.start.line || 0) + range.end.line,
- character:
- (range.end.line === 0 ? relativeTo?.start.character || 0 : 0) + range.end.character,
- },
- }
-}
diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts
index 95afe8ec..3bdb1bc4 100644
--- a/packages/tailwindcss-language-service/src/util/state.ts
+++ b/packages/tailwindcss-language-service/src/util/state.ts
@@ -46,6 +46,7 @@ export type TailwindCssSettings = {
emmetCompletions: boolean
includeLanguages: Record
classAttributes: string[]
+ classFunctions: string[]
suggestions: boolean
hovers: boolean
codeActions: boolean
@@ -64,7 +65,7 @@ export type TailwindCssSettings = {
recommendedVariantOrder: DiagnosticSeveritySetting
}
experimental: {
- classRegex: string[]
+ classRegex: string[] | [string, string][]
configFile: string | Record | null
}
files: {
@@ -171,3 +172,73 @@ export type ClassNameMeta = {
scope: string[]
context: string[]
}
+
+/**
+ * @internal
+ */
+export function getDefaultTailwindSettings(): Settings {
+ return {
+ editor: { tabSize: 2 },
+ tailwindCSS: {
+ inspectPort: null,
+ emmetCompletions: false,
+ classAttributes: ['class', 'className', 'ngClass', 'class:list'],
+ classFunctions: [],
+ codeActions: true,
+ hovers: true,
+ suggestions: true,
+ validate: true,
+ colorDecorators: true,
+ rootFontSize: 16,
+ lint: {
+ cssConflict: 'warning',
+ invalidApply: 'error',
+ invalidScreen: 'error',
+ invalidVariant: 'error',
+ invalidConfigPath: 'error',
+ invalidTailwindDirective: 'error',
+ invalidSourceDirective: 'error',
+ recommendedVariantOrder: 'warning',
+ },
+ showPixelEquivalents: true,
+ includeLanguages: {},
+ files: { exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'] },
+ experimental: {
+ classRegex: [],
+ configFile: null,
+ },
+ },
+ }
+}
+
+/**
+ * @internal
+ */
+export function createState(
+ partial: Omit, 'editor'> & {
+ editor?: Partial
+ },
+): State {
+ return {
+ enabled: true,
+ ...partial,
+ editor: {
+ get connection(): Connection {
+ throw new Error('Not implemented')
+ },
+ folder: '/',
+ userLanguages: {},
+ capabilities: {
+ configuration: true,
+ diagnosticRelatedInformation: true,
+ itemDefaults: [],
+ },
+ getConfiguration: () => {
+ throw new Error('Not implemented')
+ },
+ getDocumentSymbols: async () => [],
+ readDirectory: async () => [],
+ ...partial.editor,
+ },
+ }
+}
diff --git a/packages/tailwindcss-language-service/tsconfig.json b/packages/tailwindcss-language-service/tsconfig.json
index 605ece3c..883356e7 100644
--- a/packages/tailwindcss-language-service/tsconfig.json
+++ b/packages/tailwindcss-language-service/tsconfig.json
@@ -1,6 +1,5 @@
{
"include": ["src", "../../types"],
- "exclude": ["src/**/*.test.ts"],
"compilerOptions": {
"module": "NodeNext",
"lib": ["ES2022"],
@@ -15,6 +14,7 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "NodeNext",
+ "skipLibCheck": true,
"jsx": "react",
"esModuleInterop": true
}
diff --git a/packages/vscode-tailwindcss/README.md b/packages/vscode-tailwindcss/README.md
index 62165308..efc937cc 100644
--- a/packages/vscode-tailwindcss/README.md
+++ b/packages/vscode-tailwindcss/README.md
@@ -90,6 +90,26 @@ Enable completions when using [Emmet](https://emmet.io/)-style syntax, for examp
The HTML attributes for which to provide class completions, hover previews, linting etc. **Default: `class`, `className`, `ngClass`, `class:list`**
+### `tailwindCSS.classFunctions`
+
+Functions in which to provide completions, hover previews, linting etc. Currently, this works for both function calls and tagged template literals in JavaScript / TypeScript.
+
+Example:
+
+```json
+{
+ "tailwindCSS.classFunctions": ["tw", "clsx"]
+}
+```
+
+```javascript
+let classes = tw`flex bg-red-500`
+let classes2 = clsx([
+ "flex bg-red-500",
+ { "text-red-500": true }
+])
+```
+
### `tailwindCSS.colorDecorators`
Controls whether the editor should render inline color decorators for Tailwind CSS classes and helper functions. **Default: `true`**
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 3b5fd76a..036ca936 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -184,6 +184,14 @@
],
"markdownDescription": "The HTML attributes for which to provide class completions, hover previews, linting etc."
},
+ "tailwindCSS.classFunctions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "default": [],
+ "markdownDescription": "The function or tagged template literal names for which to provide class completions, hover previews, linting etc."
+ },
"tailwindCSS.suggestions": {
"type": "boolean",
"default": true,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f1048aaa..2378a376 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -300,6 +300,9 @@ importers:
'@types/css.escape':
specifier: ^1.5.2
version: 1.5.2
+ '@types/dedent':
+ specifier: ^0.7.2
+ version: 0.7.2
'@types/line-column':
specifier: ^1.0.2
version: 1.0.2
@@ -309,6 +312,9 @@ importers:
'@types/stringify-object':
specifier: ^4.0.5
version: 4.0.5
+ dedent:
+ specifier: ^1.5.3
+ version: 1.5.3
esbuild:
specifier: ^0.25.0
version: 0.25.0
@@ -969,6 +975,9 @@ packages:
'@types/debounce@1.2.0':
resolution: {integrity: sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw==}
+ '@types/dedent@0.7.2':
+ resolution: {integrity: sha512-kRiitIeUg1mPV9yH4VUJ/1uk2XjyANfeL8/7rH1tsjvHeO9PJLBHJIYsFWmAvmGj5u8rj+1TZx7PZzW2qLw3Lw==}
+
'@types/dlv@1.1.4':
resolution: {integrity: sha512-m8KmImw4Jt+4rIgupwfivrWEOnj1LzkmKkqbh075uG13eTQ1ZxHWT6T0vIdSQhLIjQCiR0n0lZdtyDOPO1x2Mw==}
@@ -3227,6 +3236,8 @@ snapshots:
'@types/debounce@1.2.0': {}
+ '@types/dedent@0.7.2': {}
+
'@types/dlv@1.1.4': {}
'@types/estree@1.0.6': {}
From b68a681ac748a4669ad3f4287a40b8c5f9a62c95 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 19 Mar 2025 09:57:07 -0400
Subject: [PATCH 02/93] Update changelog
---
packages/vscode-tailwindcss/CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index ca4255d7..dd3aa562 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,7 +2,7 @@
## Prerelease
-- Nothing yet!
+- Detect classes in JS/TS functions and tagged template literals with the `tailwindCSS.classFunctions` setting ([#1258](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1258))
# 0.14.9
From 0d9bd2e8486a5cd3276fa47bd9a2749537e6a566 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 19 Mar 2025 10:01:32 -0400
Subject: [PATCH 03/93] v4: Show completions after any valid variant (#1263)
Fixes #943
Fixes #1257
This doesn't ensure variants like `not-supports-*` are suggested because
`not-supports:` isn't valid. Only something with a value is e.g.
`not-supports-display`. Making that work (it really should) is a larger
task and requires designing an interactive suggestion API for v4.
---
.../tests/completions/completions.test.js | 70 +++++++++++++++++-
.../src/completionProvider.ts | 27 -------
.../src/util/getVariantsFromClassName.ts | 73 +++++++++----------
.../src/util/v4/design-system.ts | 2 +-
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
5 files changed, 104 insertions(+), 69 deletions(-)
diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js
index 2727fcb0..f5393a40 100644
--- a/packages/tailwindcss-language-server/tests/completions/completions.test.js
+++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js
@@ -1,5 +1,7 @@
-import { test } from 'vitest'
+import { test, expect, describe } from 'vitest'
import { withFixture } from '../common'
+import { css, defineTest } from '../../src/testing'
+import { createClient } from '../utils/client'
function buildCompletion(c) {
return async function completion({
@@ -670,3 +672,69 @@ withFixture('v4/workspaces', (c) => {
})
})
})
+
+defineTest({
+ name: 'v4: Completions show after a variant arbitrary value',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ 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: 23 })
+
+ expect(completion?.items.length).toBe(12289)
+ },
+})
+
+defineTest({
+ name: 'v4: Completions show after an arbitrary variant',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ 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: 22 })
+
+ expect(completion?.items.length).toBe(12289)
+ },
+})
+
+defineTest({
+ name: 'v4: Completions show after a variant with a bare value',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ 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: 31 })
+
+ expect(completion?.items.length).toBe(12289)
+ },
+})
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index fe43bb68..1579fd93 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -189,16 +189,8 @@ export function completionsFromClassList(
}),
)
} else {
- let shouldSortVariants = !semver.gte(state.version, '2.99.0')
let resultingVariants = [...existingVariants, variant.name]
- if (shouldSortVariants) {
- let allVariants = state.variants.map(({ name }) => name)
- resultingVariants = resultingVariants.sort(
- (a, b) => allVariants.indexOf(b) - allVariants.indexOf(a),
- )
- }
-
let selectors: string[] = []
try {
@@ -223,25 +215,6 @@ export function completionsFromClassList(
.map((selector) => addPixelEquivalentsToMediaQuery(selector))
.join(', '),
textEditText: resultingVariants[resultingVariants.length - 1] + sep,
- additionalTextEdits:
- shouldSortVariants && resultingVariants.length > 1
- ? [
- {
- newText:
- resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + sep,
- range: {
- start: {
- ...classListRange.start,
- character: classListRange.end.character - partialClassName.length,
- },
- end: {
- ...replacementRange.start,
- character: replacementRange.start.character,
- },
- },
- },
- ]
- : [],
}),
)
}
diff --git a/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts b/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts
index b58bb29f..c30e729a 100644
--- a/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts
+++ b/packages/tailwindcss-language-service/src/util/getVariantsFromClassName.ts
@@ -1,5 +1,6 @@
import type { State } from './state'
import * as jit from './jit'
+import { segment } from './segment'
export function getVariantsFromClassName(
state: State,
@@ -13,60 +14,52 @@ export function getVariantsFromClassName(
}
return [variant.name]
})
- let variants = new Set
()
- let offset = 0
- let parts = splitAtTopLevelOnly(className, state.separator)
+
+ let parts = segment(className, state.separator)
if (parts.length < 2) {
- return { variants: Array.from(variants), offset }
+ return { variants: [], offset: 0 }
}
+
parts = parts.filter(Boolean)
- for (let part of parts) {
- if (
- allVariants.includes(part) ||
- (state.jit &&
- ((part.includes('[') && part.endsWith(']')) || part.includes('/')) &&
- jit.generateRules(state, [`${part}${state.separator}[color:red]`]).rules.length > 0)
- ) {
- variants.add(part)
- offset += part.length + state.separator.length
- continue
+ function isValidVariant(part: string) {
+ if (allVariants.includes(part)) {
+ return true
}
- break
- }
+ let className = `${part}${state.separator}[color:red]`
- return { variants: Array.from(variants), offset }
-}
+ if (state.v4) {
+ // NOTE: This should never happen
+ if (!state.designSystem) return false
-// https://github.com/tailwindlabs/tailwindcss/blob/a8a2e2a7191fbd4bee044523aecbade5823a8664/src/util/splitAtTopLevelOnly.js
-function splitAtTopLevelOnly(input: string, separator: string): string[] {
- let stack: string[] = []
- let parts: string[] = []
- let lastPos = 0
+ // We don't use `compile()` so there's no overhead from PostCSS
+ let compiled = state.designSystem.candidatesToCss([className])
- for (let idx = 0; idx < input.length; idx++) {
- let char = input[idx]
+ // NOTE: This should never happen
+ if (compiled.length !== 1) return false
- if (stack.length === 0 && char === separator[0]) {
- if (separator.length === 1 || input.slice(idx, idx + separator.length) === separator) {
- parts.push(input.slice(lastPos, idx))
- lastPos = idx + separator.length
- }
+ return compiled[0] !== null
}
- if (char === '(' || char === '[' || char === '{') {
- stack.push(char)
- } else if (
- (char === ')' && stack[stack.length - 1] === '(') ||
- (char === ']' && stack[stack.length - 1] === '[') ||
- (char === '}' && stack[stack.length - 1] === '{')
- ) {
- stack.pop()
+ if (state.jit) {
+ if ((part.includes('[') && part.endsWith(']')) || part.includes('/')) {
+ return jit.generateRules(state, [className]).rules.length > 0
+ }
}
+
+ return false
}
- parts.push(input.slice(lastPos))
+ let offset = 0
+ let variants = new Set()
- return parts
+ for (let part of parts) {
+ if (!isValidVariant(part)) break
+
+ variants.add(part)
+ offset += part.length + state.separator!.length
+ }
+
+ return { variants: Array.from(variants), offset }
}
diff --git a/packages/tailwindcss-language-service/src/util/v4/design-system.ts b/packages/tailwindcss-language-service/src/util/v4/design-system.ts
index cce64d4b..3fb3c401 100644
--- a/packages/tailwindcss-language-service/src/util/v4/design-system.ts
+++ b/packages/tailwindcss-language-service/src/util/v4/design-system.ts
@@ -44,6 +44,6 @@ export interface DesignSystem {
export interface DesignSystem {
dependencies(): Set
- compile(classes: string[]): postcss.Root[]
+ compile(classes: string[]): (postcss.Root | null)[]
toCss(nodes: postcss.Root | postcss.Node[]): string
}
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index dd3aa562..7b9e68fa 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -3,6 +3,7 @@
## Prerelease
- Detect classes in JS/TS functions and tagged template literals with the `tailwindCSS.classFunctions` setting ([#1258](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1258))
+- v4: Make sure completions show after variants using arbitrary and bare values ([#1263](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1263))
# 0.14.9
From 9f9208af592347924461f8a2c8ac26a09ccb14e3 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 19 Mar 2025 10:25:34 -0400
Subject: [PATCH 04/93] =?UTF-8?q?Add=20support=20for=20`@source=20not`=20a?=
=?UTF-8?q?nd=20`@source=20inline(=E2=80=A6)`=20(#1262)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
IntelliSense counterpart of
https://github.com/tailwindlabs/tailwindcss/pull/17147
---
.../tailwindcss-language-server/package.json | 1 +
.../src/projects.ts | 35 +++++-
.../tailwindcss-language-server/src/tw.ts | 13 +++
.../tests/code-lens/source-inline.test.ts | 101 ++++++++++++++++++
.../tests/completions/at-config.test.js | 97 +++++++++++++++++
.../document-links/document-links.test.js | 38 +++++++
.../tests/hover/hover.test.js | 95 ++++++++++++++++
.../tests/utils/client.ts | 26 +++++
.../tailwindcss-language-service/package.json | 1 +
.../src/codeLensProvider.ts | 78 ++++++++++++++
.../src/completionProvider.ts | 13 +++
.../src/completions/file-paths.test.ts | 16 +++
.../src/completions/file-paths.ts | 10 +-
.../getInvalidSourceDiagnostics.ts | 7 +-
.../src/documentLinksProvider.ts | 2 +-
.../src/features.ts | 20 ++--
.../src/hoverProvider.ts | 27 +++--
.../src/util/estimated-class-size.ts | 35 ++++++
.../src/util/format-bytes.ts | 11 ++
.../src/util/state.ts | 5 +
packages/vscode-tailwindcss/CHANGELOG.md | 2 +
packages/vscode-tailwindcss/package.json | 6 ++
pnpm-lock.yaml | 6 ++
23 files changed, 624 insertions(+), 21 deletions(-)
create mode 100644 packages/tailwindcss-language-server/tests/code-lens/source-inline.test.ts
create mode 100644 packages/tailwindcss-language-service/src/codeLensProvider.ts
create mode 100644 packages/tailwindcss-language-service/src/util/estimated-class-size.ts
create mode 100644 packages/tailwindcss-language-service/src/util/format-bytes.ts
diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json
index 5ed431fb..fa0a7e91 100644
--- a/packages/tailwindcss-language-server/package.json
+++ b/packages/tailwindcss-language-server/package.json
@@ -42,6 +42,7 @@
"@tailwindcss/line-clamp": "0.4.2",
"@tailwindcss/oxide": "^4.0.0-alpha.19",
"@tailwindcss/typography": "0.5.7",
+ "@types/braces": "3.0.1",
"@types/color-name": "^1.1.3",
"@types/culori": "^2.1.0",
"@types/debounce": "1.2.0",
diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts
index b55ee078..afc4515f 100644
--- a/packages/tailwindcss-language-server/src/projects.ts
+++ b/packages/tailwindcss-language-server/src/projects.ts
@@ -15,6 +15,8 @@ import type {
Disposable,
DocumentLinkParams,
DocumentLink,
+ CodeLensParams,
+ CodeLens,
} from 'vscode-languageserver/node'
import { FileChangeType } from 'vscode-languageserver/node'
import type { TextDocument } from 'vscode-languageserver-textdocument'
@@ -35,6 +37,7 @@ import stackTrace from 'stack-trace'
import extractClassNames from './lib/extractClassNames'
import { klona } from 'klona/full'
import { doHover } from '@tailwindcss/language-service/src/hoverProvider'
+import { getCodeLens } from '@tailwindcss/language-service/src/codeLensProvider'
import { Resolver } from './resolver'
import {
doComplete,
@@ -110,6 +113,7 @@ export interface ProjectService {
onColorPresentation(params: ColorPresentationParams): Promise
onCodeAction(params: CodeActionParams): Promise
onDocumentLinks(params: DocumentLinkParams): Promise
+ onCodeLens(params: CodeLensParams): Promise
sortClassLists(classLists: string[]): string[]
dependencies(): Iterable
@@ -212,6 +216,7 @@ export async function createProjectService(
let state: State = {
enabled: false,
+ features: [],
completionItemData: {
_projectKey: projectKey,
},
@@ -462,6 +467,14 @@ export async function createProjectService(
// and this should be determined there and passed in instead
let features = supportedFeatures(tailwindcssVersion, tailwindcss)
log(`supported features: ${JSON.stringify(features)}`)
+ state.features = features
+
+ if (params.initializationOptions?.testMode) {
+ state.features = [
+ ...state.features,
+ ...(params.initializationOptions.additionalFeatures ?? []),
+ ]
+ }
if (!features.includes('css-at-theme')) {
tailwindcss = tailwindcss.default ?? tailwindcss
@@ -688,6 +701,15 @@ export async function createProjectService(
state.v4 = true
state.v4Fallback = true
state.jit = true
+ state.features = features
+
+ if (params.initializationOptions?.testMode) {
+ state.features = [
+ ...state.features,
+ ...(params.initializationOptions.additionalFeatures ?? []),
+ ]
+ }
+
state.modules = {
tailwindcss: { version: tailwindcssVersion, module: tailwindcss },
postcss: { version: null, module: null },
@@ -1150,7 +1172,7 @@ export async function createProjectService(
},
tryInit,
async dispose() {
- state = { enabled: false }
+ state = { enabled: false, features: [] }
for (let disposable of disposables) {
;(await disposable).dispose()
}
@@ -1177,6 +1199,17 @@ export async function createProjectService(
return doHover(state, document, params.position)
}, null)
},
+ async onCodeLens(params: CodeLensParams): Promise {
+ return withFallback(async () => {
+ if (!state.enabled) return null
+ let document = documentService.getDocument(params.textDocument.uri)
+ if (!document) return null
+ let settings = await state.editor.getConfiguration(document.uri)
+ if (!settings.tailwindCSS.codeLens) return null
+ if (await isExcluded(state, document)) return null
+ return getCodeLens(state, document)
+ }, null)
+ },
async onCompletion(params: CompletionParams): Promise {
return withFallback(async () => {
if (!state.enabled) return null
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index 35da9386..fc6a87a8 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -19,6 +19,8 @@ import type {
DocumentLink,
InitializeResult,
WorkspaceFolder,
+ CodeLensParams,
+ CodeLens,
} from 'vscode-languageserver/node'
import {
CompletionRequest,
@@ -30,6 +32,7 @@ import {
FileChangeType,
DocumentLinkRequest,
TextDocumentSyncKind,
+ CodeLensRequest,
} from 'vscode-languageserver/node'
import { URI } from 'vscode-uri'
import normalizePath from 'normalize-path'
@@ -757,6 +760,7 @@ export class TW {
this.connection.onDocumentColor(this.onDocumentColor.bind(this))
this.connection.onColorPresentation(this.onColorPresentation.bind(this))
this.connection.onCodeAction(this.onCodeAction.bind(this))
+ this.connection.onCodeLens(this.onCodeLens.bind(this))
this.connection.onDocumentLinks(this.onDocumentLinks.bind(this))
this.connection.onRequest(this.onRequest.bind(this))
}
@@ -809,6 +813,7 @@ export class TW {
capabilities.add(HoverRequest.type, { documentSelector: null })
capabilities.add(DocumentColorRequest.type, { documentSelector: null })
capabilities.add(CodeActionRequest.type, { documentSelector: null })
+ capabilities.add(CodeLensRequest.type, { documentSelector: null })
capabilities.add(DocumentLinkRequest.type, { documentSelector: null })
capabilities.add(CompletionRequest.type, {
@@ -931,6 +936,11 @@ export class TW {
return this.getProject(params.textDocument)?.onCodeAction(params) ?? null
}
+ async onCodeLens(params: CodeLensParams): Promise {
+ await this.init()
+ return this.getProject(params.textDocument)?.onCodeLens(params) ?? null
+ }
+
async onDocumentLinks(params: DocumentLinkParams): Promise {
await this.init()
return this.getProject(params.textDocument)?.onDocumentLinks(params) ?? null
@@ -961,6 +971,9 @@ export class TW {
hoverProvider: true,
colorProvider: true,
codeActionProvider: true,
+ codeLensProvider: {
+ resolveProvider: false,
+ },
documentLinkProvider: {},
completionProvider: {
resolveProvider: true,
diff --git a/packages/tailwindcss-language-server/tests/code-lens/source-inline.test.ts b/packages/tailwindcss-language-server/tests/code-lens/source-inline.test.ts
new file mode 100644
index 00000000..55f6f22e
--- /dev/null
+++ b/packages/tailwindcss-language-server/tests/code-lens/source-inline.test.ts
@@ -0,0 +1,101 @@
+import { expect } from 'vitest'
+import { css, defineTest } from '../../src/testing'
+import { createClient } from '../utils/client'
+
+defineTest({
+ name: 'Code lenses are displayed for @source inline(…)',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ features: ['source-inline'],
+ }),
+ }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'css',
+ text: css`
+ @import 'tailwindcss';
+ @source inline("{,{hover,focus}:}{flex,underline,bg-red-{50,{100..900.100},950}}");
+ `,
+ })
+
+ let lenses = await document.codeLenses()
+
+ expect(lenses).toEqual([
+ {
+ range: {
+ start: { line: 1, character: 15 },
+ end: { line: 1, character: 81 },
+ },
+ command: {
+ title: 'Generates 15 classes',
+ command: '',
+ },
+ },
+ ])
+ },
+})
+
+defineTest({
+ name: 'The user is warned when @source inline(…) generates a lerge amount of CSS',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ features: ['source-inline'],
+ }),
+ }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ lang: 'css',
+ text: css`
+ @import 'tailwindcss';
+ @source inline("{,dark:}{,{sm,md,lg,xl,2xl}:}{,{hover,focus,active}:}{flex,underline,bg-red-{50,{100..900.100},950}{,/{0..100}}}");
+ `,
+ })
+
+ let lenses = await document.codeLenses()
+
+ expect(lenses).toEqual([
+ {
+ range: {
+ start: { line: 1, character: 15 },
+ end: { line: 1, character: 129 },
+ },
+ command: {
+ title: 'Generates 14,784 classes',
+ command: '',
+ },
+ },
+ {
+ range: {
+ start: { line: 1, character: 15 },
+ end: { line: 1, character: 129 },
+ },
+ command: {
+ title: 'At least 3MB of CSS',
+ command: '',
+ },
+ },
+ {
+ range: {
+ start: { line: 1, character: 15 },
+ end: { line: 1, character: 129 },
+ },
+ command: {
+ title: 'This may slow down your bundler/browser',
+ command: '',
+ },
+ },
+ ])
+ },
+})
diff --git a/packages/tailwindcss-language-server/tests/completions/at-config.test.js b/packages/tailwindcss-language-server/tests/completions/at-config.test.js
index 15d99ac6..60ee1495 100644
--- a/packages/tailwindcss-language-server/tests/completions/at-config.test.js
+++ b/packages/tailwindcss-language-server/tests/completions/at-config.test.js
@@ -271,6 +271,51 @@ withFixture('v4/dependencies', (c) => {
})
})
+ test.concurrent('@source not', async ({ expect }) => {
+ let result = await completion({
+ text: '@source not "',
+ lang: 'css',
+ position: {
+ line: 0,
+ character: 13,
+ },
+ })
+
+ expect(result).toEqual({
+ isIncomplete: false,
+ items: [
+ {
+ label: 'index.html',
+ kind: 17,
+ data: expect.anything(),
+ textEdit: {
+ newText: 'index.html',
+ range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } },
+ },
+ },
+ {
+ label: 'sub-dir/',
+ kind: 19,
+ command: { command: 'editor.action.triggerSuggest', title: '' },
+ data: expect.anything(),
+ textEdit: {
+ newText: 'sub-dir/',
+ range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } },
+ },
+ },
+ {
+ label: 'tailwind.config.js',
+ kind: 17,
+ data: expect.anything(),
+ textEdit: {
+ newText: 'tailwind.config.js',
+ range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } },
+ },
+ },
+ ],
+ })
+ })
+
test.concurrent('@source directory', async ({ expect }) => {
let result = await completion({
text: '@source "./sub-dir/',
@@ -297,6 +342,58 @@ withFixture('v4/dependencies', (c) => {
})
})
+ test.concurrent('@source not directory', async ({ expect }) => {
+ let result = await completion({
+ text: '@source not "./sub-dir/',
+ lang: 'css',
+ position: {
+ line: 0,
+ character: 23,
+ },
+ })
+
+ expect(result).toEqual({
+ isIncomplete: false,
+ items: [
+ {
+ label: 'colors.js',
+ kind: 17,
+ data: expect.anything(),
+ textEdit: {
+ newText: 'colors.js',
+ range: { start: { line: 0, character: 23 }, end: { line: 0, character: 23 } },
+ },
+ },
+ ],
+ })
+ })
+
+ test.concurrent('@source inline(…)', async ({ expect }) => {
+ let result = await completion({
+ text: '@source inline("',
+ lang: 'css',
+ position: {
+ line: 0,
+ character: 16,
+ },
+ })
+
+ expect(result).toEqual(null)
+ })
+
+ test.concurrent('@source not inline(…)', async ({ expect }) => {
+ let result = await completion({
+ text: '@source not inline("',
+ lang: 'css',
+ position: {
+ line: 0,
+ character: 20,
+ },
+ })
+
+ expect(result).toEqual(null)
+ })
+
test.concurrent('@import "…" source(…)', async ({ expect }) => {
let result = await completion({
text: '@import "tailwindcss" source("',
diff --git a/packages/tailwindcss-language-server/tests/document-links/document-links.test.js b/packages/tailwindcss-language-server/tests/document-links/document-links.test.js
index 861f74c9..0fc76cb6 100644
--- a/packages/tailwindcss-language-server/tests/document-links/document-links.test.js
+++ b/packages/tailwindcss-language-server/tests/document-links/document-links.test.js
@@ -131,6 +131,44 @@ withFixture('v4/basic', (c) => {
],
})
+ testDocumentLinks('source not: file exists', {
+ text: '@source not "index.html";',
+ lang: 'css',
+ expected: [
+ {
+ target: `file://${path
+ .resolve('./tests/fixtures/v4/basic/index.html')
+ .replace(/@/g, '%40')}`,
+ range: { start: { line: 0, character: 12 }, end: { line: 0, character: 24 } },
+ },
+ ],
+ })
+
+ testDocumentLinks('source not: file does not exist', {
+ text: '@source not "does-not-exist.html";',
+ lang: 'css',
+ expected: [
+ {
+ target: `file://${path
+ .resolve('./tests/fixtures/v4/basic/does-not-exist.html')
+ .replace(/@/g, '%40')}`,
+ range: { start: { line: 0, character: 12 }, end: { line: 0, character: 33 } },
+ },
+ ],
+ })
+
+ testDocumentLinks('@source inline(…)', {
+ text: '@source inline("m-{1,2,3}");',
+ lang: 'css',
+ expected: [],
+ })
+
+ testDocumentLinks('@source not inline(…)', {
+ text: '@source not inline("m-{1,2,3}");',
+ lang: 'css',
+ expected: [],
+ })
+
testDocumentLinks('Directories in source(…) show links', {
text: `
@import "tailwindcss" source("../../");
diff --git a/packages/tailwindcss-language-server/tests/hover/hover.test.js b/packages/tailwindcss-language-server/tests/hover/hover.test.js
index 5c8bcb7f..ac97e414 100644
--- a/packages/tailwindcss-language-server/tests/hover/hover.test.js
+++ b/packages/tailwindcss-language-server/tests/hover/hover.test.js
@@ -293,6 +293,101 @@ withFixture('v4/basic', (c) => {
},
})
+ testHover('css @source not glob expansion', {
+ exact: true,
+ lang: 'css',
+ text: `@source not "../{app,components}/**/*.jsx"`,
+ position: { line: 0, character: 23 },
+ expected: {
+ contents: {
+ kind: 'markdown',
+ value: [
+ '**Expansion**',
+ '```plaintext',
+ '- ../app/**/*.jsx',
+ '- ../components/**/*.jsx',
+ '```',
+ ].join('\n'),
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 42 },
+ },
+ },
+ expectedRange: {
+ start: { line: 2, character: 9 },
+ end: { line: 2, character: 18 },
+ },
+ })
+
+ testHover('css @source inline glob expansion', {
+ exact: true,
+ lang: 'css',
+ text: `@source inline("{hover:,active:,}m-{1,2,3}")`,
+ position: { line: 0, character: 23 },
+ expected: {
+ contents: {
+ kind: 'markdown',
+ value: [
+ '**Expansion**',
+ '```plaintext',
+ '- hover:m-1',
+ '- hover:m-2',
+ '- hover:m-3',
+ '- active:m-1',
+ '- active:m-2',
+ '- active:m-3',
+ '- m-1',
+ '- m-2',
+ '- m-3',
+ '```',
+ ].join('\n'),
+ },
+ range: {
+ start: { line: 0, character: 15 },
+ end: { line: 0, character: 43 },
+ },
+ },
+ expectedRange: {
+ start: { line: 2, character: 9 },
+ end: { line: 2, character: 15 },
+ },
+ })
+
+ testHover('css @source not inline glob expansion', {
+ exact: true,
+ lang: 'css',
+ text: `@source not inline("{hover:,active:,}m-{1,2,3}")`,
+ position: { line: 0, character: 23 },
+ expected: {
+ contents: {
+ kind: 'markdown',
+ value: [
+ '**Expansion**',
+ '```plaintext',
+ '- hover:m-1',
+ '- hover:m-2',
+ '- hover:m-3',
+ '- active:m-1',
+ '- active:m-2',
+ '- active:m-3',
+ '- m-1',
+ '- m-2',
+ '- m-3',
+ '```',
+ ].join('\n'),
+ },
+ range: {
+ start: { line: 0, character: 19 },
+ end: { line: 0, character: 47 },
+ },
+ },
+ expectedRange: {
+ start: { line: 2, character: 9 },
+ end: { line: 2, character: 18 },
+ },
+ })
+
testHover('--theme() works inside @media queries', {
lang: 'tailwindcss',
text: `@media (width>=--theme(--breakpoint-xl)) { .foo { color: red; } }`,
diff --git a/packages/tailwindcss-language-server/tests/utils/client.ts b/packages/tailwindcss-language-server/tests/utils/client.ts
index d6d317bb..3a860230 100644
--- a/packages/tailwindcss-language-server/tests/utils/client.ts
+++ b/packages/tailwindcss-language-server/tests/utils/client.ts
@@ -1,6 +1,8 @@
import type { Settings } from '@tailwindcss/language-service/src/util/state'
import {
ClientCapabilities,
+ CodeLens,
+ CodeLensRequest,
CompletionList,
CompletionParams,
Diagnostic,
@@ -45,6 +47,7 @@ import { clearLanguageBoundariesCache } from '@tailwindcss/language-service/src/
import { DefaultMap } from '../../src/util/default-map'
import { connect, ConnectOptions } from './connection'
import type { DeepPartial } from '@tailwindcss/language-service/src/types'
+import type { Feature } from '@tailwindcss/language-service/src/features'
export interface DocumentDescriptor {
/**
@@ -94,6 +97,11 @@ export interface ClientDocument {
*/
reopen(): Promise
+ /**
+ * Code lenses in the document
+ */
+ codeLenses(): Promise
+
/**
* The diagnostics for the current version of this document
*/
@@ -163,6 +171,14 @@ export interface ClientOptions extends ConnectOptions {
* Settings to provide the server immediately when it starts
*/
settings?: DeepPartial
+
+ /**
+ * Additional features to force-enable
+ *
+ * These should normally be enabled by the server based on the project
+ * and the Tailwind CSS version it detects
+ */
+ features?: Feature[]
}
export interface Client extends ClientWorkspace {
@@ -387,6 +403,7 @@ export async function createClient(opts: ClientOptions): Promise {
workspaceFolders,
initializationOptions: {
testMode: true,
+ additionalFeatures: opts.features,
...opts.options,
},
})
@@ -677,6 +694,14 @@ export async function createClientWorkspace({
return results
}
+ async function codeLenses() {
+ return await conn.sendRequest(CodeLensRequest.type, {
+ textDocument: {
+ uri: uri.toString(),
+ },
+ })
+ }
+
return {
uri,
reopen,
@@ -687,6 +712,7 @@ export async function createClientWorkspace({
symbols,
completions,
diagnostics,
+ codeLenses,
}
}
diff --git a/packages/tailwindcss-language-service/package.json b/packages/tailwindcss-language-service/package.json
index d5fddaaa..e73f6ca3 100644
--- a/packages/tailwindcss-language-service/package.json
+++ b/packages/tailwindcss-language-service/package.json
@@ -40,6 +40,7 @@
"vscode-languageserver-textdocument": "1.0.11"
},
"devDependencies": {
+ "@types/braces": "3.0.1",
"@types/css.escape": "^1.5.2",
"@types/dedent": "^0.7.2",
"@types/line-column": "^1.0.2",
diff --git a/packages/tailwindcss-language-service/src/codeLensProvider.ts b/packages/tailwindcss-language-service/src/codeLensProvider.ts
new file mode 100644
index 00000000..b00df983
--- /dev/null
+++ b/packages/tailwindcss-language-service/src/codeLensProvider.ts
@@ -0,0 +1,78 @@
+import type { Range, TextDocument } from 'vscode-languageserver-textdocument'
+import type { State } from './util/state'
+import type { CodeLens } from 'vscode-languageserver'
+import braces from 'braces'
+import { findAll, indexToPosition } from './util/find'
+import { absoluteRange } from './util/absoluteRange'
+import { formatBytes } from './util/format-bytes'
+import { estimatedClassSize } from './util/estimated-class-size'
+
+export async function getCodeLens(state: State, doc: TextDocument): Promise {
+ if (!state.enabled) return []
+
+ let groups: CodeLens[][] = await Promise.all([
+ //
+ sourceInlineCodeLens(state, doc),
+ ])
+
+ return groups.flat()
+}
+
+const SOURCE_INLINE_PATTERN = /@source(?:\s+not)?\s*inline\((?'[^']+'|"[^"]+")/dg
+async function sourceInlineCodeLens(state: State, doc: TextDocument): Promise {
+ if (!state.features.includes('source-inline')) return []
+
+ let text = doc.getText()
+
+ let countFormatter = new Intl.NumberFormat('en', {
+ maximumFractionDigits: 2,
+ })
+
+ let lenses: CodeLens[] = []
+
+ for (let match of findAll(SOURCE_INLINE_PATTERN, text)) {
+ let glob = match.groups.glob.slice(1, -1)
+
+ // Perform brace expansion
+ let expanded = new Set(braces.expand(glob))
+ if (expanded.size < 2) continue
+
+ let slice: Range = absoluteRange({
+ start: indexToPosition(text, match.indices.groups.glob[0]),
+ end: indexToPosition(text, match.indices.groups.glob[1]),
+ })
+
+ let size = 0
+ for (let className of expanded) {
+ size += estimatedClassSize(className)
+ }
+
+ lenses.push({
+ range: slice,
+ command: {
+ title: `Generates ${countFormatter.format(expanded.size)} classes`,
+ command: '',
+ },
+ })
+
+ if (size >= 1_000_000) {
+ lenses.push({
+ range: slice,
+ command: {
+ title: `At least ${formatBytes(size)} of CSS`,
+ command: '',
+ },
+ })
+
+ lenses.push({
+ range: slice,
+ command: {
+ title: `This may slow down your bundler/browser`,
+ command: '',
+ },
+ })
+ }
+ }
+
+ return lenses
+}
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index 1579fd93..3cdd511a 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -1866,6 +1866,19 @@ function provideCssDirectiveCompletions(
},
})
+ if (state.features.includes('source-not')) {
+ items.push({
+ label: '@source not',
+ documentation: {
+ kind: 'markdown' as typeof MarkupKind.Markdown,
+ value: `Use the \`@source not\` directive to ignore files when scanning.\n\n[Tailwind CSS Documentation](${docsUrl(
+ state.version,
+ 'functions-and-directives/#source',
+ )})`,
+ },
+ })
+ }
+
items.push({
label: '@plugin',
documentation: {
diff --git a/packages/tailwindcss-language-service/src/completions/file-paths.test.ts b/packages/tailwindcss-language-service/src/completions/file-paths.test.ts
index c79fcbdd..ed521392 100644
--- a/packages/tailwindcss-language-service/src/completions/file-paths.test.ts
+++ b/packages/tailwindcss-language-service/src/completions/file-paths.test.ts
@@ -15,6 +15,7 @@ test('Detecting v3 directives that point to files', async () => {
// The following are not supported in v3
await expect(find('@plugin "./')).resolves.toEqual(null)
await expect(find('@source "./')).resolves.toEqual(null)
+ await expect(find('@source not "./')).resolves.toEqual(null)
await expect(find('@import "tailwindcss" source("./')).resolves.toEqual(null)
await expect(find('@tailwind utilities source("./')).resolves.toEqual(null)
})
@@ -42,6 +43,12 @@ test('Detecting v4 directives that point to files', async () => {
suggest: 'source',
})
+ await expect(find('@source not "./')).resolves.toEqual({
+ directive: 'source',
+ partial: './',
+ suggest: 'source',
+ })
+
await expect(find('@import "tailwindcss" source("./')).resolves.toEqual({
directive: 'import',
partial: './',
@@ -54,3 +61,12 @@ test('Detecting v4 directives that point to files', async () => {
suggest: 'directory',
})
})
+
+test('@source inline is ignored', async () => {
+ function find(text: string) {
+ return findFileDirective({ enabled: true, v4: true }, text)
+ }
+
+ await expect(find('@source inline("')).resolves.toEqual(null)
+ await expect(find('@source not inline("')).resolves.toEqual(null)
+})
diff --git a/packages/tailwindcss-language-service/src/completions/file-paths.ts b/packages/tailwindcss-language-service/src/completions/file-paths.ts
index a99325be..acf4f9fa 100644
--- a/packages/tailwindcss-language-service/src/completions/file-paths.ts
+++ b/packages/tailwindcss-language-service/src/completions/file-paths.ts
@@ -1,7 +1,10 @@
import type { State } from '../util/state'
// @config, @plugin, @source
-const PATTERN_CUSTOM_V4 = /@(?config|plugin|source)\s*(?'[^']*|"[^"]*)$/
+// - @source inline("…") is *not* a file directive
+// - @source not inline("…") is *not* a file directive
+const PATTERN_CUSTOM_V4 =
+ /@(?config|plugin|source)(?\s+not)?\s*(?'[^']*|"[^"]*)$/
const PATTERN_CUSTOM_V3 = /@(?config)\s*(?'[^']*|"[^"]*)$/
// @import … source('…')
@@ -26,6 +29,7 @@ export async function findFileDirective(state: State, text: string): Promise 0
let directive = match.groups.directive
let partial = match.groups.partial?.slice(1) ?? '' // remove leading quote
@@ -40,6 +44,7 @@ export async function findFileDirective(state: State, text: string): Promise 0
+ if (isNot) return null
+
let directive = match.groups.directive
let partial = match.groups.partial.slice(1) // remove leading quote
diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidSourceDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidSourceDiagnostics.ts
index 2ac52a08..24fdcd9c 100644
--- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidSourceDiagnostics.ts
+++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidSourceDiagnostics.ts
@@ -14,7 +14,7 @@ const PATTERN_UTIL_SOURCE =
// @source …
const PATTERN_AT_SOURCE =
- /(?:\s|^)@(?source)\s*(?'[^']*'?|"[^"]*"?|[a-z]*|\)|;)/dg
+ /(?:\s|^)@(?source)\s*(?not)?\s*(?'[^']*'?|"[^"]*"?|[a-z]*(?:\([^)]+\))?|\)|;)/dg
const HAS_DRIVE_LETTER = /^[A-Z]:/
@@ -135,6 +135,11 @@ export function getInvalidSourceDiagnostics(
})
}
+ // `@source inline(…)` is fine
+ else if (directive === 'source' && source.startsWith('inline(')) {
+ //
+ }
+
// - `@import "tailwindcss" source(no)`
// - `@tailwind utilities source('')`
else if (directive === 'source' && source !== 'none' && !isQuoted) {
diff --git a/packages/tailwindcss-language-service/src/documentLinksProvider.ts b/packages/tailwindcss-language-service/src/documentLinksProvider.ts
index 76e39ed7..ea2e7bd9 100644
--- a/packages/tailwindcss-language-service/src/documentLinksProvider.ts
+++ b/packages/tailwindcss-language-service/src/documentLinksProvider.ts
@@ -18,7 +18,7 @@ export function getDocumentLinks(
if (state.v4) {
patterns.push(
/@plugin\s*(?'[^']+'|"[^"]+")/g,
- /@source\s*(?'[^']+'|"[^"]+")/g,
+ /@source(?:\s+not)?\s*(?'[^']+'|"[^"]+")/g,
/@import\s*('[^']*'|"[^"]*")\s*(layer\([^)]+\)\s*)?source\((?'[^']*'?|"[^"]*"?)/g,
/@reference\s*('[^']*'|"[^"]*")\s*source\((?'[^']*'?|"[^"]*"?)/g,
/@tailwind\s*utilities\s*source\((?'[^']*'?|"[^"]*"?)/g,
diff --git a/packages/tailwindcss-language-service/src/features.ts b/packages/tailwindcss-language-service/src/features.ts
index 60181bf7..6aa9236e 100644
--- a/packages/tailwindcss-language-service/src/features.ts
+++ b/packages/tailwindcss-language-service/src/features.ts
@@ -15,6 +15,8 @@ export type Feature =
| 'jit'
| 'separator:root'
| 'separator:options'
+ | 'source-not'
+ | 'source-inline'
/**
* Determine a list of features that are supported by the given Tailwind CSS version
@@ -39,15 +41,21 @@ export function supportedFeatures(version: string, mod?: unknown): Feature[] {
}
if (isInsidersV4) {
- return ['css-at-theme', 'layer:base', 'content-list']
+ return ['css-at-theme', 'layer:base', 'content-list', 'source-inline', 'source-not']
}
- if (!isInsidersV3 && semver.gte(version, '4.0.0-alpha.1')) {
- return ['css-at-theme', 'layer:base', 'content-list']
- }
+ if (!isInsidersV3) {
+ if (semver.gte(version, '4.1.0')) {
+ return ['css-at-theme', 'layer:base', 'content-list', 'source-inline', 'source-not']
+ }
- if (!isInsidersV3 && version.startsWith('0.0.0-oxide')) {
- return ['css-at-theme', 'layer:base', 'content-list']
+ if (semver.gte(version, '4.0.0-alpha.1')) {
+ return ['css-at-theme', 'layer:base', 'content-list']
+ }
+
+ if (version.startsWith('0.0.0-oxide')) {
+ return ['css-at-theme', 'layer:base', 'content-list']
+ }
}
if (semver.gte(version, '0.99.0')) {
diff --git a/packages/tailwindcss-language-service/src/hoverProvider.ts b/packages/tailwindcss-language-service/src/hoverProvider.ts
index eb5a7d1c..1db68bed 100644
--- a/packages/tailwindcss-language-service/src/hoverProvider.ts
+++ b/packages/tailwindcss-language-service/src/hoverProvider.ts
@@ -168,19 +168,24 @@ async function provideSourceGlobHover(
let text = getTextWithoutComments(document, 'css', range)
- let pattern = /@source\s*(?'[^']+'|"[^"]+")/dg
+ let patterns = [
+ /@source(?:\s+not)?\s*(?'[^']+'|"[^"]+")/dg,
+ /@source(?:\s+not)?\s*inline\((?'[^']+'|"[^"]+")/dg,
+ ]
- for (let match of findAll(pattern, text)) {
- let path = match.groups.path.slice(1, -1)
+ let matches = patterns.flatMap((pattern) => findAll(pattern, text))
- // Ignore paths that don't need brace expansion
- if (!path.includes('{') || !path.includes('}')) continue
+ for (let match of matches) {
+ let glob = match.groups.glob.slice(1, -1)
+
+ // Ignore globs that don't need brace expansion
+ if (!glob.includes('{') || !glob.includes('}')) continue
- // Ignore paths that don't contain the current position
+ // Ignore glob that don't contain the current position
let slice: Range = absoluteRange(
{
- start: indexToPosition(text, match.indices.groups.path[0]),
- end: indexToPosition(text, match.indices.groups.path[1]),
+ start: indexToPosition(text, match.indices.groups.glob[0]),
+ end: indexToPosition(text, match.indices.groups.glob[1]),
},
range,
)
@@ -188,8 +193,8 @@ async function provideSourceGlobHover(
if (!isWithinRange(position, slice)) continue
// Perform brace expansion
- let paths = new Set(braces.expand(path))
- if (paths.size < 2) continue
+ let expanded = new Set(braces.expand(glob))
+ if (expanded.size < 2) continue
return {
range: slice,
@@ -197,7 +202,7 @@ async function provideSourceGlobHover(
//
'**Expansion**',
'```plaintext',
- ...Array.from(paths, (path) => `- ${path}`),
+ ...Array.from(expanded, (entry) => `- ${entry}`),
'```',
]),
}
diff --git a/packages/tailwindcss-language-service/src/util/estimated-class-size.ts b/packages/tailwindcss-language-service/src/util/estimated-class-size.ts
new file mode 100644
index 00000000..57dc4235
--- /dev/null
+++ b/packages/tailwindcss-language-service/src/util/estimated-class-size.ts
@@ -0,0 +1,35 @@
+import { segment } from './segment'
+
+/**
+ * Calculates the approximate size of a generated class
+ *
+ * This is meant to be a lower bound, as the actual size of a class can vary
+ * depending on the actual CSS properties and values, configured theme, etc…
+ */
+export function estimatedClassSize(className: string) {
+ let size = 0
+
+ // We estimate the size using the following structure which gives a reasonable
+ // lower bound on the size of the generated CSS:
+ //
+ // .class-name {
+ // &:variant-1 {
+ // &:variant-2 {
+ // …
+ // }
+ // }
+ // }
+
+ // Class name
+ size += 1 + className.length + 3
+ size += 2
+
+ // Variants + nesting
+ for (let [depth, variantName] of segment(className, ':').entries()) {
+ size += (depth + 1) * 2 + 2 + variantName.length + 3
+ size += (depth + 1) * 2 + 2
+ }
+
+ // ~1.95x is a rough growth factor due to the actual properties being present
+ return size * 1.95
+}
diff --git a/packages/tailwindcss-language-service/src/util/format-bytes.ts b/packages/tailwindcss-language-service/src/util/format-bytes.ts
new file mode 100644
index 00000000..a7b0b050
--- /dev/null
+++ b/packages/tailwindcss-language-service/src/util/format-bytes.ts
@@ -0,0 +1,11 @@
+const UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte', 'petabyte']
+
+export function formatBytes(n: number) {
+ let i = n == 0 ? 0 : Math.floor(Math.log(n) / Math.log(1000))
+ return new Intl.NumberFormat('en', {
+ notation: 'compact',
+ style: 'unit',
+ unit: UNITS[i],
+ unitDisplay: 'narrow',
+ }).format(n / 1000 ** i)
+}
diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts
index 3bdb1bc4..119e5f59 100644
--- a/packages/tailwindcss-language-service/src/util/state.ts
+++ b/packages/tailwindcss-language-service/src/util/state.ts
@@ -4,6 +4,7 @@ import type { Postcss } from 'postcss'
import type { KeywordColor } from './color'
import type * as culori from 'culori'
import type { DesignSystem } from './v4'
+import type { Feature } from '../features'
export type ClassNamesTree = {
[key: string]: ClassNamesTree
@@ -49,6 +50,7 @@ export type TailwindCssSettings = {
classFunctions: string[]
suggestions: boolean
hovers: boolean
+ codeLens: boolean
codeActions: boolean
validate: boolean
showPixelEquivalents: boolean
@@ -141,6 +143,7 @@ export interface State {
classListContainsMetadata?: boolean
pluginVersions?: string
completionItemData?: Record
+ features: Feature[]
// postcssPlugins?: { before: any[]; after: any[] }
}
@@ -185,6 +188,7 @@ export function getDefaultTailwindSettings(): Settings {
classAttributes: ['class', 'className', 'ngClass', 'class:list'],
classFunctions: [],
codeActions: true,
+ codeLens: true,
hovers: true,
suggestions: true,
validate: true,
@@ -221,6 +225,7 @@ export function createState(
): State {
return {
enabled: true,
+ features: [],
...partial,
editor: {
get connection(): Connection {
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 7b9e68fa..faf20065 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -4,6 +4,8 @@
- Detect classes in JS/TS functions and tagged template literals with the `tailwindCSS.classFunctions` setting ([#1258](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1258))
- v4: Make sure completions show after variants using arbitrary and bare values ([#1263](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1263))
+- v4: Add support for upcoming `@source not` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
+- v4: Add support for upcoming `@source inline(…)` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
# 0.14.9
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 036ca936..876a661b 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -210,6 +210,12 @@
"markdownDescription": "Enable code actions.",
"scope": "language-overridable"
},
+ "tailwindCSS.codeLens": {
+ "type": "boolean",
+ "default": true,
+ "markdownDescription": "Enable code lens.",
+ "scope": "language-overridable"
+ },
"tailwindCSS.colorDecorators": {
"type": "boolean",
"default": true,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2378a376..67d58759 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -56,6 +56,9 @@ importers:
'@tailwindcss/typography':
specifier: 0.5.7
version: 0.5.7(tailwindcss@3.4.17)
+ '@types/braces':
+ specifier: 3.0.1
+ version: 3.0.1
'@types/color-name':
specifier: ^1.1.3
version: 1.1.4
@@ -297,6 +300,9 @@ importers:
specifier: 1.0.11
version: 1.0.11
devDependencies:
+ '@types/braces':
+ specifier: 3.0.1
+ version: 3.0.1
'@types/css.escape':
specifier: ^1.5.2
version: 1.5.2
From 182600d55606e81ea600e57be15580cf32b7fe73 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 19 Mar 2025 10:47:17 -0400
Subject: [PATCH 05/93] Ensure `classFunctions` completions work inside
`
+ `,
+ })
+
+ // let classes = clsx('')
+ // ^
+ let completion = await document.completions({ line: 1, character: 22 })
+
+ expect(completion?.items.length).toBe(12289)
+ },
+})
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index 3cdd511a..62c54594 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -22,7 +22,7 @@ import isObject from './util/isObject'
import { braceLevel, parenLevel } from './util/braceLevel'
import * as emmetHelper from 'vscode-emmet-helper-bundled'
import { isValidLocationForEmmetAbbreviation } from './util/isValidLocationForEmmetAbbreviation'
-import { isJsDoc, isJsxContext } from './util/js'
+import { isJsContext, isJsDoc, isJsxContext } from './util/js'
import { naturalExpand } from './util/naturalExpand'
import * as semver from './util/semver'
import { getTextWithoutComments } from './util/doc'
@@ -986,7 +986,11 @@ async function provideClassNameCompletions(
return provideAtApplyCompletions(state, document, position, context)
}
- if (isHtmlContext(state, document, position) || isJsxContext(state, document, position)) {
+ if (
+ isHtmlContext(state, document, position) ||
+ isJsContext(state, document, position) ||
+ isJsxContext(state, document, position)
+ ) {
return provideClassAttributeCompletions(state, document, position, context)
}
diff --git a/packages/tailwindcss-language-service/src/util/js.ts b/packages/tailwindcss-language-service/src/util/js.ts
index 1197d1e6..0c405a8d 100644
--- a/packages/tailwindcss-language-service/src/util/js.ts
+++ b/packages/tailwindcss-language-service/src/util/js.ts
@@ -12,6 +12,19 @@ export function isJsDoc(state: State, doc: TextDocument): boolean {
return [...jsLanguages, ...userJsLanguages].indexOf(doc.languageId) !== -1
}
+export function isJsContext(state: State, doc: TextDocument, position: Position): boolean {
+ let str = doc.getText({
+ start: { line: 0, character: 0 },
+ end: position,
+ })
+
+ let boundaries = getLanguageBoundaries(state, doc, str)
+
+ return boundaries
+ ? ['js', 'ts', 'jsx', 'tsx'].includes(boundaries[boundaries.length - 1].type)
+ : false
+}
+
export function isJsxContext(state: State, doc: TextDocument, position: Position): boolean {
let str = doc.getText({
start: { line: 0, character: 0 },
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index faf20065..468cdd08 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,7 +2,7 @@
## Prerelease
-- Detect classes in JS/TS functions and tagged template literals with the `tailwindCSS.classFunctions` setting ([#1258](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1258))
+- Detect classes in JS/TS functions and tagged template literals with the `tailwindCSS.classFunctions` setting ([#1258](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1258), [#1272](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1272))
- v4: Make sure completions show after variants using arbitrary and bare values ([#1263](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1263))
- v4: Add support for upcoming `@source not` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
- v4: Add support for upcoming `@source inline(…)` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
From 2f41b839a3856efd1ebe10b656e7b4704cbd57d8 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 19 Mar 2025 11:48:21 -0400
Subject: [PATCH 06/93] LSP: Refresh internal caches when settings are updated
(#1273)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We use the pull model (`workspace/configuration`) to get settings for a
document and we cache these internally so we don't repeat these calls
multiple times for a given request. We're set up to listen for
configuration refresh notifications and update the project settings when
we get them.
Unfortunately, we didn't actually _register_ for these notifications, so
we never got them. This meant that if you changed the settings for an
already opened file or workspace folder, the language server would not
react to these changes. This PR fixes this by registering for
configuration change notifications and now open files with color
decorators, completions, etc… should react to changes in the settings as
needed.
If settings are updated and our langauge server doesn't react to or
handle these changes, it is definitely a bug. Hopefully this will squash
all of those particular ones but… we'll see. 😅
---
packages/tailwindcss-language-server/src/projects.ts | 8 +++-----
packages/tailwindcss-language-server/src/tw.ts | 6 ++++++
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
3 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts
index afc4515f..a8012920 100644
--- a/packages/tailwindcss-language-server/src/projects.ts
+++ b/packages/tailwindcss-language-server/src/projects.ts
@@ -1181,11 +1181,9 @@ export async function createProjectService(
if (state.enabled) {
refreshDiagnostics()
}
- if (settings.editor?.colorDecorators) {
- updateCapabilities()
- } else {
- connection.sendNotification('@/tailwindCSS/clearColors')
- }
+
+ updateCapabilities()
+ connection.sendNotification('@/tailwindCSS/clearColors')
},
onFileEvents,
async onHover(params: TextDocumentPositionParams): Promise {
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index fc6a87a8..48d7b5f7 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -33,6 +33,7 @@ import {
DocumentLinkRequest,
TextDocumentSyncKind,
CodeLensRequest,
+ DidChangeConfigurationNotification,
} from 'vscode-languageserver/node'
import { URI } from 'vscode-uri'
import normalizePath from 'normalize-path'
@@ -799,6 +800,7 @@ export class TW {
private updateCapabilities() {
if (!supportsDynamicRegistration(this.initializeParams)) {
+ this.connection.client.register(DidChangeConfigurationNotification.type, undefined)
return
}
@@ -810,12 +812,16 @@ export class TW {
let capabilities = BulkRegistration.create()
+ // TODO: We should *not* be re-registering these capabilities
+ // IDEA: These should probably be static registrations up front
capabilities.add(HoverRequest.type, { documentSelector: null })
capabilities.add(DocumentColorRequest.type, { documentSelector: null })
capabilities.add(CodeActionRequest.type, { documentSelector: null })
capabilities.add(CodeLensRequest.type, { documentSelector: null })
capabilities.add(DocumentLinkRequest.type, { documentSelector: null })
+ capabilities.add(DidChangeConfigurationNotification.type, undefined)
+ // TODO: Only re-register this if trigger characters change
capabilities.add(CompletionRequest.type, {
documentSelector: null,
resolveProvider: true,
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 468cdd08..fa2d07c3 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -6,6 +6,7 @@
- v4: Make sure completions show after variants using arbitrary and bare values ([#1263](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1263))
- v4: Add support for upcoming `@source not` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
- v4: Add support for upcoming `@source inline(…)` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
+- LSP: Refresh internal caches when settings are updated ([#1273](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1273))
# 0.14.9
From 747884f34b31c797db0915d1719276eae35431f1 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 20 Mar 2025 12:37:21 -0400
Subject: [PATCH 07/93] Ignore regex literals when analyzing language
boundaries (#1275)
Fixes #929
---
.../tests/env/v4.test.js | 48 +++++
.../src/util/doc.ts | 150 ++++++++++++++
.../src/util/find.test.ts | 61 +-----
.../src/util/language-boundaries.test.ts | 191 ++++++++++++++++++
.../src/util/test-utils.ts | 65 ++++++
5 files changed, 455 insertions(+), 60 deletions(-)
create mode 100644 packages/tailwindcss-language-service/src/util/language-boundaries.test.ts
create mode 100644 packages/tailwindcss-language-service/src/util/test-utils.ts
diff --git a/packages/tailwindcss-language-server/tests/env/v4.test.js b/packages/tailwindcss-language-server/tests/env/v4.test.js
index 76de7f15..3a7780af 100644
--- a/packages/tailwindcss-language-server/tests/env/v4.test.js
+++ b/packages/tailwindcss-language-server/tests/env/v4.test.js
@@ -791,3 +791,51 @@ defineTest({
})
},
})
+
+defineTest({
+ options: { only: true },
+ name: 'regex literals do not break language boundaries',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let doc = await client.open({
+ lang: 'javascriptreact',
+ text: js`
+ export default function Page() {
+ let styles = "str".match(/
+
+
+ `,
+ })
+
+ let boundaries = getLanguageBoundaries(file.state, file.doc)
+
+ expect(boundaries).toEqual([
+ {
+ type: 'html',
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 1, character: 2 },
+ },
+ },
+ {
+ type: 'css',
+ range: {
+ start: { line: 1, character: 2 },
+ end: { line: 5, character: 2 },
+ },
+ },
+ {
+ type: 'html',
+ range: {
+ start: { line: 5, character: 2 },
+ end: { line: 7, character: 6 },
+ },
+ },
+ ])
+})
+
+test('script tags in HTML are treated as a separate boundary', ({ expect }) => {
+ let file = createDocument({
+ name: 'file.html',
+ lang: 'html',
+ content: html`
+
+ `,
+ })
+
+ let boundaries = getLanguageBoundaries(file.state, file.doc)
+
+ expect(boundaries).toEqual([
+ {
+ type: 'html',
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 1, character: 2 },
+ },
+ },
+ {
+ type: 'js',
+ range: {
+ start: { line: 1, character: 2 },
+ end: { line: 5, character: 2 },
+ },
+ },
+ {
+ type: 'html',
+ range: {
+ start: { line: 5, character: 2 },
+ end: { line: 7, character: 6 },
+ },
+ },
+ ])
+})
+
+test('Vue files detect
,
+
+
+
+
+
+ Some documentation
+
+ `,
+ })
+
+ let boundaries = getLanguageBoundaries(file.state, file.doc)
+
+ expect(boundaries).toEqual([
+ {
+ type: 'none',
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 0, character: 0 },
+ },
+ },
+ {
+ type: 'js',
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 2, character: 0 },
+ },
+ },
+ {
+ type: 'none',
+ range: {
+ start: { line: 2, character: 0 },
+ end: { line: 3, character: 0 },
+ },
+ },
+ {
+ type: 'html',
+ range: {
+ start: { line: 3, character: 0 },
+ end: { line: 5, character: 0 },
+ },
+ },
+ {
+ type: 'none',
+ range: {
+ start: { line: 5, character: 0 },
+ end: { line: 6, character: 0 },
+ },
+ },
+ {
+ type: 'css',
+ range: {
+ start: { line: 6, character: 0 },
+ end: { line: 10, character: 0 },
+ },
+ },
+ {
+ type: 'none',
+ range: {
+ start: { line: 10, character: 0 },
+ end: { line: 13, character: 16 },
+ },
+ },
+ ])
+})
diff --git a/packages/tailwindcss-language-service/src/util/test-utils.ts b/packages/tailwindcss-language-service/src/util/test-utils.ts
new file mode 100644
index 00000000..088dade5
--- /dev/null
+++ b/packages/tailwindcss-language-service/src/util/test-utils.ts
@@ -0,0 +1,65 @@
+import { createState, getDefaultTailwindSettings, Settings } from './state'
+import { TextDocument } from 'vscode-languageserver-textdocument'
+import type { DeepPartial } from '../types'
+import dedent from 'dedent'
+
+export const js = dedent
+export const jsx = dedent
+export const ts = dedent
+export const tsx = dedent
+export const css = dedent
+export const html = dedent
+
+export function createDocument({
+ name,
+ lang,
+ content,
+ settings,
+}: {
+ name: string
+ lang: string
+ content: string | string[]
+ settings?: DeepPartial
+}) {
+ let doc = TextDocument.create(
+ `file://${name}`,
+ lang,
+ 1,
+ typeof content === 'string' ? content : content.join('\n'),
+ )
+ let defaults = getDefaultTailwindSettings()
+ settings ??= {}
+ let state = createState({
+ editor: {
+ getConfiguration: async () => ({
+ ...defaults,
+ ...settings,
+ tailwindCSS: {
+ ...defaults.tailwindCSS,
+ ...settings.tailwindCSS,
+ lint: {
+ ...defaults.tailwindCSS.lint,
+ ...(settings.tailwindCSS?.lint ?? {}),
+ },
+ experimental: {
+ ...defaults.tailwindCSS.experimental,
+ ...(settings.tailwindCSS?.experimental ?? {}),
+ },
+ files: {
+ ...defaults.tailwindCSS.files,
+ ...(settings.tailwindCSS?.files ?? {}),
+ },
+ },
+ editor: {
+ ...defaults.editor,
+ ...settings.editor,
+ },
+ }),
+ },
+ })
+
+ return {
+ doc,
+ state,
+ }
+}
From f91a3bceb7a720669af3e5ab2db4e8e0f659d3cc Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 20 Mar 2025 12:43:27 -0400
Subject: [PATCH 08/93] Improve error message when a workspace folder is
inaccessible (#1276)
On Linux when using a LSP client that does not support watching files on
behalf of the server, we'll use Parcel Watcher (if possible). If we
start the watcher with a non-existent or inaccessible directory, it will
throw an error with a very unhelpful message: "Bad file descriptor".
The best thing we can do is an initial check for access to the directory
and log a more helpful error message if it fails:
See
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/884#issuecomment-1927660975
---------
Co-authored-by: Robin Malfait
---
.../tailwindcss-language-server/src/tw.ts | 22 ++++++++++++++++++-
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index 48d7b5f7..d5cfba20 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -38,6 +38,7 @@ import {
import { URI } from 'vscode-uri'
import normalizePath from 'normalize-path'
import * as path from 'node:path'
+import * as fs from 'node:fs/promises'
import type * as chokidar from 'chokidar'
import picomatch from 'picomatch'
import * as parcel from './watcher/index.js'
@@ -174,6 +175,26 @@ export class TW {
}
private async _initFolder(baseUri: URI): Promise {
+ // NOTE: We do this check because on Linux when using an LSP client that does
+ // not support watching files on behalf of the server, we'll use Parcel
+ // Watcher (if possible). If we start the watcher with a non-existent or
+ // inaccessible directory, it will throw an error with a very unhelpful
+ // message: "Bad file descriptor"
+ //
+ // The best thing we can do is an initial check for access to the directory
+ // and log a more helpful error message if it fails.
+ let base = baseUri.fsPath
+
+ try {
+ await fs.access(base, fs.constants.F_OK | fs.constants.R_OK)
+ } catch (err) {
+ console.error(
+ `Unable to access the workspace folder [${base}]. This may happen if the directory does not exist or the current user does not have the necessary permissions to access it.`,
+ )
+ console.error(err)
+ return
+ }
+
let initUserLanguages = this.initializeParams.initializationOptions?.userLanguages ?? {}
if (Object.keys(initUserLanguages).length > 0) {
@@ -182,7 +203,6 @@ export class TW {
)
}
- let base = baseUri.fsPath
let workspaceFolders: Array = []
let globalSettings = await this.settingsCache.get()
let ignore = globalSettings.tailwindCSS.files.exclude
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index fa2d07c3..32733ec8 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -7,6 +7,7 @@
- v4: Add support for upcoming `@source not` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
- v4: Add support for upcoming `@source inline(…)` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
- LSP: Refresh internal caches when settings are updated ([#1273](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1273))
+- LSP: Improve error message when a workspace folder does not exist or is inaccesible to the current user ([#1276](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1276))
# 0.14.9
From 741925942f35c3c220527bcc58fb4d2d025da3f7 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 20 Mar 2025 15:09:57 -0400
Subject: [PATCH 09/93] =?UTF-8?q?v4:=20Show=20theme=20key=20completions=20?=
=?UTF-8?q?in=20`var(=E2=80=A6)`=20(#1274)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Closes #1209
---
.../tests/completions/completions.test.js | 51 +++++++++++++++++
.../src/completionProvider.ts | 56 +++++++++++++++----
.../src/util/rewriting/lookup.ts | 2 +-
.../src/util/v4/design-system.ts | 2 +-
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
5 files changed, 99 insertions(+), 13 deletions(-)
diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js
index 15c220bb..024213cb 100644
--- a/packages/tailwindcss-language-server/tests/completions/completions.test.js
+++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js
@@ -2,6 +2,7 @@ import { test, expect } from 'vitest'
import { withFixture } from '../common'
import { css, defineTest, html, js } from '../../src/testing'
import { createClient } from '../utils/client'
+import { CompletionItemKind } from 'vscode-languageserver'
function buildCompletion(c) {
return async function completion({
@@ -798,3 +799,53 @@ defineTest({
expect(completion?.items.length).toBe(12289)
},
})
+
+defineTest({
+ name: 'v4: Theme key completions show in var(…)',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+
+ @theme {
+ --color-custom: #000;
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx'],
+ },
+ },
+ lang: 'css',
+ text: css`
+ .foo {
+ color: var();
+ }
+ `,
+ })
+
+ // color: var();
+ // ^
+ let completion = await document.completions({ line: 1, character: 13 })
+
+ expect(completion).toEqual({
+ isIncomplete: false,
+ items: expect.arrayContaining([
+ // From the default theme
+ expect.objectContaining({ label: '--font-sans' }),
+
+ // From the `@theme` block in the CSS file
+ expect.objectContaining({
+ label: '--color-custom',
+
+ // And it's shown as a color
+ kind: CompletionItemKind.Color,
+ documentation: '#000000',
+ }),
+ ]),
+ })
+ },
+})
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index 62c54594..978f118c 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -1014,7 +1014,7 @@ function provideCssHelperCompletions(
const match = text
.substr(0, text.length - 1) // don't include that extra character from earlier
- .match(/[\s:;/*(){}](?config|theme|--theme)\(\s*['"]?(?[^)'"]*)$/)
+ .match(/[\s:;/*(){}](?config|theme|--theme|var)\(\s*['"]?(?[^)'"]*)$/d)
if (match === null) {
return null
@@ -1040,17 +1040,15 @@ function provideCssHelperCompletions(
end: position,
}
- if (state.v4 && match.groups.helper === '--theme') {
- // List known theme keys
- let validThemeKeys = resolveKnownThemeKeys(state.designSystem)
+ if (
+ state.v4 &&
+ (match.groups.helper === '--theme' ||
+ match.groups.helper === 'theme' ||
+ match.groups.helper === 'var')
+ ) {
+ let items: CompletionItem[] = themeKeyCompletions(state)
- let items: CompletionItem[] = validThemeKeys.map((themeKey, index) => {
- return {
- label: themeKey,
- sortText: naturalExpand(index, validThemeKeys.length),
- kind: 9,
- }
- })
+ editRange.start.character = match.indices.groups.helper[1] + 1
return withDefaults(
{ isIncomplete: false, items },
@@ -1065,6 +1063,8 @@ function provideCssHelperCompletions(
)
}
+ if (match.groups.helper === 'var') return null
+
let base = match.groups.helper === 'config' ? state.config : dlv(state.config, 'theme', {})
let parts = path.split(/([\[\].]+)/)
let keys = parts.filter((_, i) => i % 2 === 0)
@@ -2486,3 +2486,37 @@ async function knownUtilityFunctionArguments(state: State, fn: UtilityFn): Promi
return args
}
+
+export function themeKeyCompletions(state: State): CompletionItem[] {
+ if (!state.v4) return null
+ if (!state.designSystem) return null
+
+ let knownThemeKeys = resolveKnownThemeKeys(state.designSystem)
+
+ return knownThemeKeys.map((themeKey, index) => {
+ let value = state.designSystem.resolveThemeValue(themeKey, true)
+ let documentation: string | undefined
+
+ let color = getColorFromValue(value)
+ if (color !== null) {
+ if (typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
+ documentation = formatColor(color)
+ }
+
+ return {
+ label: themeKey,
+ kind: CompletionItemKind.Color,
+ sortText: naturalExpand(index, knownThemeKeys.length),
+ detail: value,
+ documentation,
+ }
+ }
+
+ return {
+ label: themeKey,
+ kind: CompletionItemKind.Variable,
+ sortText: naturalExpand(index, knownThemeKeys.length),
+ detail: value,
+ }
+ })
+}
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts b/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts
index ae8e70e7..2dbb7146 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts
@@ -8,5 +8,5 @@ export function resolveVariableValue(design: DesignSystem, name: string) {
name = `--${name.slice(prefix.length + 3)}`
}
- return design.resolveThemeValue?.(name) ?? null
+ return design.resolveThemeValue?.(name, true) ?? null
}
diff --git a/packages/tailwindcss-language-service/src/util/v4/design-system.ts b/packages/tailwindcss-language-service/src/util/v4/design-system.ts
index 3fb3c401..20b7ff32 100644
--- a/packages/tailwindcss-language-service/src/util/v4/design-system.ts
+++ b/packages/tailwindcss-language-service/src/util/v4/design-system.ts
@@ -39,7 +39,7 @@ export interface DesignSystem {
getVariants(): VariantEntry[]
// Optional because it did not exist in earlier v4 alpha versions
- resolveThemeValue?(path: string): string | undefined
+ resolveThemeValue?(path: string, forceInline?: boolean): string | undefined
}
export interface DesignSystem {
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 32733ec8..7b4d62bb 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -8,6 +8,7 @@
- v4: Add support for upcoming `@source inline(…)` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
- LSP: Refresh internal caches when settings are updated ([#1273](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1273))
- LSP: Improve error message when a workspace folder does not exist or is inaccesible to the current user ([#1276](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1276))
+- v4: Show theme key completions in `var(…)` in CSS ([#1274](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1274))
# 0.14.9
From fd34756a9642e4969ca07dde9680bef0184961d0 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 20 Mar 2025 14:29:24 -0400
Subject: [PATCH 10/93] 0.14.10
---
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 fa0a7e91..f04ee81c 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.9",
+ "version": "0.14.10",
"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 e73f6ca3..6082fc86 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.9",
+ "version": "0.14.10",
"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 7b4d62bb..1e8a1970 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,6 +2,10 @@
## Prerelease
+- Nothing yet!
+
+# 0.14.10
+
- Detect classes in JS/TS functions and tagged template literals with the `tailwindCSS.classFunctions` setting ([#1258](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1258), [#1272](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1272))
- v4: Make sure completions show after variants using arbitrary and bare values ([#1263](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1263))
- v4: Add support for upcoming `@source not` feature ([#1262](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1262))
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 876a661b..2747fd44 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "vscode-tailwindcss",
- "version": "0.14.9",
+ "version": "0.14.10",
"displayName": "Tailwind CSS IntelliSense",
"description": "Intelligent Tailwind CSS tooling for VS Code",
"author": "Brad Cornes ",
From 43f90004a6b3bc785221c17d84923e020875efc4 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 20 Mar 2025 15:20:52 -0400
Subject: [PATCH 11/93] Remove `only: true`
oops
---
packages/tailwindcss-language-server/tests/env/v4.test.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/tailwindcss-language-server/tests/env/v4.test.js b/packages/tailwindcss-language-server/tests/env/v4.test.js
index 3a7780af..36639f55 100644
--- a/packages/tailwindcss-language-server/tests/env/v4.test.js
+++ b/packages/tailwindcss-language-server/tests/env/v4.test.js
@@ -793,7 +793,6 @@ defineTest({
})
defineTest({
- options: { only: true },
name: 'regex literals do not break language boundaries',
fs: {
'app.css': css`
From 20890ea2ce0ed37436b457c9e6eb76ef340ea251 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 20 Mar 2025 18:52:04 -0400
Subject: [PATCH 12/93] Discard function matches that appear after the current
cursor in completions (#1278)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Look, it's not a fancy shiny new feature without a bug 😅
When we discover function matches via `tailwindCSS.classFunctions` we
were implicitly breaking some assumptions used by the completions code:
- No matches for class attributes or functions that appear _after_ the
cursor position are found (we weren't discarding these function matches
but we need to)
- The *last* match is always the one that corresponds to the current
cursor position (we collect function matches separately so the order was
wrong)
This PR fixes this by doing two things:
- Discarding function matches that appear after the current cursor
position
- Sorting the list of attribute and function matches so the last one in
the list is always the latest one in the document (up to the cursor)
---
.../tests/completions/completions.test.js | 58 +++++++++++++++++++
.../src/completionProvider.ts | 13 +++--
packages/vscode-tailwindcss/CHANGELOG.md | 2 +-
3 files changed, 68 insertions(+), 5 deletions(-)
diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js
index 024213cb..96f8188a 100644
--- a/packages/tailwindcss-language-server/tests/completions/completions.test.js
+++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js
@@ -849,3 +849,61 @@ defineTest({
})
},
})
+
+defineTest({
+ name: 'v4: class function completions mixed with class attribute completions work',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let document = await client.open({
+ settings: {
+ tailwindCSS: {
+ classAttributes: ['className'],
+ classFunctions: ['cn', 'cva'],
+ },
+ },
+ lang: 'javascriptreact',
+ text: js`
+ let x = cva("")
+
+ export function Button() {
+ return
+ }
+
+ export function Button2() {
+ return
+ }
+
+ let y = cva("")
+ `,
+ })
+
+ // let x = cva("");
+ // ^
+ let completionA = await document.completions({ line: 0, character: 13 })
+
+ expect(completionA?.items.length).toBe(12289)
+
+ // return ;
+ // ^
+ let completionB = await document.completions({ line: 3, character: 30 })
+
+ expect(completionB?.items.length).toBe(12289)
+
+ // return ;
+ // ^
+ let completionC = await document.completions({ line: 7, character: 30 })
+
+ expect(completionC?.items.length).toBe(12289)
+
+ // let y = cva("");
+ // ^
+ let completionD = await document.completions({ line: 10, character: 13 })
+
+ expect(completionD?.items.length).toBe(12289)
+ },
+})
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index 978f118c..5c0c5552 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -703,8 +703,9 @@ async function provideClassAttributeCompletions(
position: Position,
context?: CompletionContext,
): Promise {
+ let current = document.offsetAt(position)
let range: Range = {
- start: document.positionAt(Math.max(0, document.offsetAt(position) - SEARCH_RANGE)),
+ start: document.positionAt(Math.max(0, current - SEARCH_RANGE)),
end: position,
}
@@ -734,13 +735,17 @@ async function provideClassAttributeCompletions(
let offset = document.offsetAt(boundary.range.start)
let fnMatches = matchClassFunctions(str, settings.classFunctions)
- fnMatches.forEach((match) => {
+ for (let match of fnMatches) {
if (match.index) match.index += offset
- })
+ if (match.index > current) continue
- matches.push(...fnMatches)
+ matches.push(match)
+ }
}
+ // Make sure matches are sorted by index
+ matches.sort((a, b) => a.index - b.index)
+
if (matches.length === 0) {
return null
}
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 1e8a1970..9c949db3 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,7 +2,7 @@
## Prerelease
-- Nothing yet!
+- Fix completions not showing for some class attributes when a class function exists in the document ([#1278](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1278))
# 0.14.10
From e85877419954148a8e3efd76ab85bded0ac34d5a Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 20 Mar 2025 18:54:06 -0400
Subject: [PATCH 13/93] 0.14.11
---
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 f04ee81c..f43e88df 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.10",
+ "version": "0.14.11",
"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 6082fc86..2fc8c0b4 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.10",
+ "version": "0.14.11",
"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 9c949db3..9b0498db 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,6 +2,10 @@
## Prerelease
+- Nothing yet!
+
+# 0.14.11
+
- Fix completions not showing for some class attributes when a class function exists in the document ([#1278](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1278))
# 0.14.10
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 2747fd44..038c9133 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "vscode-tailwindcss",
- "version": "0.14.10",
+ "version": "0.14.11",
"displayName": "Tailwind CSS IntelliSense",
"description": "Intelligent Tailwind CSS tooling for VS Code",
"author": "Brad Cornes ",
From 3650ed46f3841331d319d93c6a151f603fc86eb8 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Sat, 22 Mar 2025 13:55:09 -0400
Subject: [PATCH 14/93] Bump Vitest
---
.../tailwindcss-language-server/package.json | 2 +-
.../tailwindcss-language-service/package.json | 2 +-
pnpm-lock.yaml | 454 ++++++------------
3 files changed, 148 insertions(+), 310 deletions(-)
diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json
index f43e88df..63089551 100644
--- a/packages/tailwindcss-language-server/package.json
+++ b/packages/tailwindcss-language-server/package.json
@@ -89,7 +89,7 @@
"tsconfig-paths": "^4.2.0",
"typescript": "5.3.3",
"vite-tsconfig-paths": "^4.3.1",
- "vitest": "^1.6.1",
+ "vitest": "^3.0.9",
"vscode-css-languageservice": "6.2.9",
"vscode-jsonrpc": "8.2.0",
"vscode-languageclient": "8.1.0",
diff --git a/packages/tailwindcss-language-service/package.json b/packages/tailwindcss-language-service/package.json
index 2fc8c0b4..40ddba77 100644
--- a/packages/tailwindcss-language-service/package.json
+++ b/packages/tailwindcss-language-service/package.json
@@ -52,6 +52,6 @@
"minimist": "^1.2.8",
"tslib": "2.2.0",
"typescript": "^5.3.3",
- "vitest": "^1.6.1"
+ "vitest": "^3.0.9"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 67d58759..6367f5ae 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -198,8 +198,8 @@ importers:
specifier: ^4.3.1
version: 4.3.2(typescript@5.3.3)(vite@5.4.14(@types/node@18.19.43))
vitest:
- specifier: ^1.6.1
- version: 1.6.1(@types/node@18.19.43)
+ specifier: ^3.0.9
+ version: 3.0.9(@types/node@18.19.43)
vscode-css-languageservice:
specifier: 6.2.9
version: 6.2.9
@@ -337,8 +337,8 @@ importers:
specifier: ^5.3.3
version: 5.3.3
vitest:
- specifier: ^1.6.1
- version: 1.6.1(@types/node@18.19.43)
+ specifier: ^3.0.9
+ version: 3.0.9(@types/node@18.19.43)
packages/vscode-tailwindcss:
devDependencies:
@@ -725,10 +725,6 @@ packages:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
- '@jest/schemas@29.6.3':
- resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
'@jridgewell/gen-mapping@0.3.5':
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@@ -874,9 +870,6 @@ packages:
cpu: [x64]
os: [win32]
- '@sinclair/typebox@0.27.8':
- resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
-
'@tailwindcss/aspect-ratio@0.4.2':
resolution: {integrity: sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==}
peerDependencies:
@@ -1042,20 +1035,34 @@ packages:
'@types/ws@8.5.12':
resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==}
- '@vitest/expect@1.6.1':
- resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==}
+ '@vitest/expect@3.0.9':
+ resolution: {integrity: sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig==}
+
+ '@vitest/mocker@3.0.9':
+ resolution: {integrity: sha512-ryERPIBOnvevAkTq+L1lD+DTFBRcjueL9lOUfXsLfwP92h4e+Heb+PjiqS3/OURWPtywfafK0kj++yDFjWUmrA==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0 || ^6.0.0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@3.0.9':
+ resolution: {integrity: sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==}
- '@vitest/runner@1.6.1':
- resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==}
+ '@vitest/runner@3.0.9':
+ resolution: {integrity: sha512-NX9oUXgF9HPfJSwl8tUZCMP1oGx2+Sf+ru6d05QjzQz4OwWg0psEzwY6VexP2tTHWdOkhKHUIZH+fS6nA7jfOw==}
- '@vitest/snapshot@1.6.1':
- resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==}
+ '@vitest/snapshot@3.0.9':
+ resolution: {integrity: sha512-AiLUiuZ0FuA+/8i19mTYd+re5jqjEc2jZbgJ2up0VY0Ddyyxg/uUtBDpIFAy4uzKaQxOW8gMgBdAJJ2ydhu39A==}
- '@vitest/spy@1.6.1':
- resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==}
+ '@vitest/spy@3.0.9':
+ resolution: {integrity: sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ==}
- '@vitest/utils@1.6.1':
- resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==}
+ '@vitest/utils@3.0.9':
+ resolution: {integrity: sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==}
'@vscode/l10n@0.0.16':
resolution: {integrity: sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg==}
@@ -1068,15 +1075,6 @@ packages:
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
- acorn-walk@8.3.4:
- resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
- engines: {node: '>=0.4.0'}
-
- acorn@8.14.0:
- resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -1093,10 +1091,6 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
- ansi-styles@5.2.0:
- resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
- engines: {node: '>=10'}
-
ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
@@ -1125,8 +1119,9 @@ packages:
asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
- assertion-error@1.1.0:
- resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
azure-devops-node-api@11.2.0:
resolution: {integrity: sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==}
@@ -1201,9 +1196,9 @@ packages:
caniuse-lite@1.0.30001649:
resolution: {integrity: sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==}
- chai@4.5.0:
- resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
- engines: {node: '>=4'}
+ chai@5.2.0:
+ resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
+ engines: {node: '>=12'}
chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
@@ -1213,8 +1208,9 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
- check-error@1.0.3:
- resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
+ check-error@2.1.1:
+ resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
+ engines: {node: '>= 16'}
cheerio-select@2.1.0:
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
@@ -1262,9 +1258,6 @@ packages:
engines: {node: ^12.20.0 || ^14.13.0 || >=16.0.0}
hasBin: true
- confbox@0.1.8:
- resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
-
cosmiconfig@7.1.0:
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
engines: {node: '>=10'}
@@ -1353,8 +1346,8 @@ packages:
babel-plugin-macros:
optional: true
- deep-eql@4.1.4:
- resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==}
+ deep-eql@5.0.2:
+ resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
deep-extend@0.6.0:
@@ -1383,10 +1376,6 @@ packages:
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
- diff-sequences@29.6.3:
- resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
@@ -1447,6 +1436,9 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
+ es-module-lexer@1.6.0:
+ resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==}
+
esbuild-node-externals@1.14.0:
resolution: {integrity: sha512-jMWnTlCII3cLEjR5+u0JRSTJuP+MgbjEHKfwSIAI41NgLQ0ZjfzjchlbEn0r7v2u5gCBMSEYvYlkO7GDG8gG3A==}
engines: {node: '>=12'}
@@ -1474,14 +1466,14 @@ packages:
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
- execa@8.0.1:
- resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
- engines: {node: '>=16.17'}
-
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
+ expect-type@1.2.0:
+ 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'}
@@ -1533,9 +1525,6 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
- get-func-name@2.0.2:
- resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
-
get-intrinsic@1.2.4:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
@@ -1543,10 +1532,6 @@ packages:
get-own-enumerable-property-symbols@3.0.2:
resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
- get-stream@8.0.1:
- resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
- engines: {node: '>=16'}
-
github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
@@ -1621,10 +1606,6 @@ packages:
htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
- human-signals@5.0.0:
- resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
- engines: {node: '>=16.17.0'}
-
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@@ -1695,10 +1676,6 @@ packages:
resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
engines: {node: '>=0.10.0'}
- is-stream@3.0.0:
- resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
@@ -1728,9 +1705,6 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
- js-tokens@9.0.1:
- resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
-
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
@@ -1778,10 +1752,6 @@ packages:
linkify-it@3.0.3:
resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==}
- local-pkg@0.5.1:
- resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
- engines: {node: '>=14'}
-
locate-path@3.0.0:
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
engines: {node: '>=6'}
@@ -1802,8 +1772,8 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
- loupe@2.3.7:
- resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
+ loupe@3.1.3:
+ resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
lru-cache@10.0.1:
resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==}
@@ -1835,9 +1805,6 @@ packages:
resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- merge-stream@2.0.0:
- resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
-
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -1855,10 +1822,6 @@ packages:
engines: {node: '>=4'}
hasBin: true
- mimic-fn@4.0.0:
- resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
- engines: {node: '>=12'}
-
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@@ -1900,9 +1863,6 @@ packages:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
- mlly@1.7.4:
- resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==}
-
moo@0.5.1:
resolution: {integrity: sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==}
@@ -1995,10 +1955,6 @@ packages:
resolution: {integrity: sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==}
engines: {node: ^16.14.0 || >=18.0.0}
- npm-run-path@5.3.0:
- resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
@@ -2017,10 +1973,6 @@ packages:
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
- onetime@6.0.0:
- resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
- engines: {node: '>=12'}
-
os-homedir@1.0.2:
resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==}
engines: {node: '>=0.10.0'}
@@ -2041,10 +1993,6 @@ packages:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
- p-limit@5.0.0:
- resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
- engines: {node: '>=18'}
-
p-locate@3.0.0:
resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
engines: {node: '>=6'}
@@ -2094,10 +2042,6 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
- path-key@4.0.0:
- resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
- engines: {node: '>=12'}
-
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
@@ -2109,14 +2053,12 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
- pathe@1.1.2:
- resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
- pathe@2.0.2:
- resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==}
-
- pathval@1.1.1:
- resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
+ pathval@2.0.0:
+ resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
+ engines: {node: '>= 14.16'}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
@@ -2143,9 +2085,6 @@ packages:
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
engines: {node: '>= 6'}
- pkg-types@1.3.1:
- resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
-
pkg-up@3.1.0:
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
engines: {node: '>=8'}
@@ -2223,10 +2162,6 @@ packages:
engines: {node: '>=14'}
hasBin: true
- pretty-format@29.7.0:
- resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
proc-log@3.0.0:
resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -2261,9 +2196,6 @@ packages:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
- react-is@18.3.1:
- resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
-
read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
@@ -2468,10 +2400,6 @@ packages:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
- strip-final-newline@3.0.0:
- resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
- engines: {node: '>=12'}
-
strip-indent@4.0.0:
resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==}
engines: {node: '>=12'}
@@ -2480,9 +2408,6 @@ packages:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
- strip-literal@2.1.1:
- resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
-
sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -2533,12 +2458,19 @@ packages:
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
- tinypool@0.8.4:
- resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinypool@1.0.2:
+ resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+
+ tinyrainbow@2.0.0:
+ resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
engines: {node: '>=14.0.0'}
- tinyspy@2.2.1:
- resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
+ tinyspy@3.0.2:
+ resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
engines: {node: '>=14.0.0'}
tmp-cache@1.1.0:
@@ -2598,10 +2530,6 @@ packages:
resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}
- type-detect@4.1.0:
- resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
- engines: {node: '>=4'}
-
type-fest@1.4.0:
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
engines: {node: '>=10'}
@@ -2617,9 +2545,6 @@ packages:
uc.micro@1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
- ufo@1.5.4:
- resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
-
underscore@1.13.7:
resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==}
@@ -2651,9 +2576,9 @@ packages:
resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
- vite-node@1.6.1:
- resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==}
- engines: {node: ^18.0.0 || >=20.0.0}
+ vite-node@3.0.9:
+ resolution: {integrity: sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
vite-tsconfig-paths@4.3.2:
@@ -2695,20 +2620,23 @@ packages:
terser:
optional: true
- vitest@1.6.1:
- resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==}
- engines: {node: ^18.0.0 || >=20.0.0}
+ vitest@3.0.9:
+ resolution: {integrity: sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
- '@types/node': ^18.0.0 || >=20.0.0
- '@vitest/browser': 1.6.1
- '@vitest/ui': 1.6.1
+ '@types/debug': ^4.1.12
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ '@vitest/browser': 3.0.9
+ '@vitest/ui': 3.0.9
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
+ '@types/debug':
+ optional: true
'@types/node':
optional: true
'@vitest/browser':
@@ -2845,10 +2773,6 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- yocto-queue@1.1.1:
- resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==}
- engines: {node: '>=12.20'}
-
snapshots:
'@alloc/quick-lru@5.2.0': {}
@@ -3035,10 +2959,6 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
- '@jest/schemas@29.6.3':
- dependencies:
- '@sinclair/typebox': 0.27.8
-
'@jridgewell/gen-mapping@0.3.5':
dependencies:
'@jridgewell/set-array': 1.2.1
@@ -3162,8 +3082,6 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.34.3':
optional: true
- '@sinclair/typebox@0.27.8': {}
-
'@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.17)':
dependencies:
tailwindcss: 3.4.17
@@ -3292,34 +3210,45 @@ snapshots:
dependencies:
'@types/node': 18.19.43
- '@vitest/expect@1.6.1':
+ '@vitest/expect@3.0.9':
+ dependencies:
+ '@vitest/spy': 3.0.9
+ '@vitest/utils': 3.0.9
+ chai: 5.2.0
+ tinyrainbow: 2.0.0
+
+ '@vitest/mocker@3.0.9(vite@5.4.14(@types/node@18.19.43))':
+ dependencies:
+ '@vitest/spy': 3.0.9
+ estree-walker: 3.0.3
+ magic-string: 0.30.17
+ optionalDependencies:
+ vite: 5.4.14(@types/node@18.19.43)
+
+ '@vitest/pretty-format@3.0.9':
dependencies:
- '@vitest/spy': 1.6.1
- '@vitest/utils': 1.6.1
- chai: 4.5.0
+ tinyrainbow: 2.0.0
- '@vitest/runner@1.6.1':
+ '@vitest/runner@3.0.9':
dependencies:
- '@vitest/utils': 1.6.1
- p-limit: 5.0.0
- pathe: 1.1.2
+ '@vitest/utils': 3.0.9
+ pathe: 2.0.3
- '@vitest/snapshot@1.6.1':
+ '@vitest/snapshot@3.0.9':
dependencies:
+ '@vitest/pretty-format': 3.0.9
magic-string: 0.30.17
- pathe: 1.1.2
- pretty-format: 29.7.0
+ pathe: 2.0.3
- '@vitest/spy@1.6.1':
+ '@vitest/spy@3.0.9':
dependencies:
- tinyspy: 2.2.1
+ tinyspy: 3.0.2
- '@vitest/utils@1.6.1':
+ '@vitest/utils@3.0.9':
dependencies:
- diff-sequences: 29.6.3
- estree-walker: 3.0.3
- loupe: 2.3.7
- pretty-format: 29.7.0
+ '@vitest/pretty-format': 3.0.9
+ loupe: 3.1.3
+ tinyrainbow: 2.0.0
'@vscode/l10n@0.0.16': {}
@@ -3350,12 +3279,6 @@ snapshots:
abbrev@1.1.1: {}
- acorn-walk@8.3.4:
- dependencies:
- acorn: 8.14.0
-
- acorn@8.14.0: {}
-
ansi-regex@5.0.1: {}
ansi-regex@6.0.1: {}
@@ -3368,8 +3291,6 @@ snapshots:
dependencies:
color-convert: 2.0.1
- ansi-styles@5.2.0: {}
-
ansi-styles@6.2.1: {}
any-promise@1.3.0: {}
@@ -3389,7 +3310,7 @@ snapshots:
asap@2.0.6: {}
- assertion-error@1.1.0: {}
+ assertion-error@2.0.1: {}
azure-devops-node-api@11.2.0:
dependencies:
@@ -3474,15 +3395,13 @@ snapshots:
caniuse-lite@1.0.30001649: {}
- chai@4.5.0:
+ chai@5.2.0:
dependencies:
- assertion-error: 1.1.0
- check-error: 1.0.3
- deep-eql: 4.1.4
- get-func-name: 2.0.2
- loupe: 2.3.7
- pathval: 1.1.1
- type-detect: 4.1.0
+ assertion-error: 2.0.1
+ check-error: 2.1.1
+ deep-eql: 5.0.2
+ loupe: 3.1.3
+ pathval: 2.0.0
chalk@2.4.2:
dependencies:
@@ -3495,9 +3414,7 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
- check-error@1.0.3:
- dependencies:
- get-func-name: 2.0.2
+ check-error@2.1.1: {}
cheerio-select@2.1.0:
dependencies:
@@ -3568,8 +3485,6 @@ snapshots:
tree-kill: 1.2.2
yargs: 16.2.0
- confbox@0.1.8: {}
-
cosmiconfig@7.1.0:
dependencies:
'@types/parse-json': 4.0.2
@@ -3636,9 +3551,7 @@ snapshots:
dedent@1.5.3: {}
- deep-eql@4.1.4:
- dependencies:
- type-detect: 4.1.0
+ deep-eql@5.0.2: {}
deep-extend@0.6.0:
optional: true
@@ -3663,8 +3576,6 @@ snapshots:
didyoumean@1.2.2: {}
- diff-sequences@29.6.3: {}
-
dlv@1.1.3: {}
dom-serializer@2.0.0:
@@ -3721,6 +3632,8 @@ snapshots:
es-errors@1.3.0: {}
+ es-module-lexer@1.6.0: {}
+
esbuild-node-externals@1.14.0(esbuild@0.25.0):
dependencies:
esbuild: 0.25.0
@@ -3789,21 +3702,11 @@ snapshots:
dependencies:
'@types/estree': 1.0.6
- execa@8.0.1:
- dependencies:
- cross-spawn: 7.0.6
- get-stream: 8.0.1
- human-signals: 5.0.0
- is-stream: 3.0.0
- merge-stream: 2.0.0
- npm-run-path: 5.3.0
- onetime: 6.0.0
- signal-exit: 4.1.0
- strip-final-newline: 3.0.0
-
expand-template@2.0.3:
optional: true
+ expect-type@1.2.0: {}
+
fast-glob@3.2.4:
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -3861,8 +3764,6 @@ snapshots:
get-caller-file@2.0.5: {}
- get-func-name@2.0.2: {}
-
get-intrinsic@1.2.4:
dependencies:
es-errors: 1.3.0
@@ -3873,8 +3774,6 @@ snapshots:
get-own-enumerable-property-symbols@3.0.2: {}
- get-stream@8.0.1: {}
-
github-from-package@0.0.0:
optional: true
@@ -3950,8 +3849,6 @@ snapshots:
domutils: 3.1.0
entities: 4.5.0
- human-signals@5.0.0: {}
-
ieee754@1.2.1:
optional: true
@@ -4008,8 +3905,6 @@ snapshots:
is-regexp@1.0.0: {}
- is-stream@3.0.0: {}
-
isarray@1.0.0: {}
isexe@2.0.0: {}
@@ -4032,8 +3927,6 @@ snapshots:
js-tokens@4.0.0: {}
- js-tokens@9.0.1: {}
-
json-parse-even-better-errors@2.3.1: {}
json-parse-even-better-errors@3.0.0: {}
@@ -4082,11 +3975,6 @@ snapshots:
dependencies:
uc.micro: 1.0.6
- local-pkg@0.5.1:
- dependencies:
- mlly: 1.7.4
- pkg-types: 1.3.1
-
locate-path@3.0.0:
dependencies:
p-locate: 3.0.0
@@ -4104,9 +3992,7 @@ snapshots:
lodash@4.17.21: {}
- loupe@2.3.7:
- dependencies:
- get-func-name: 2.0.2
+ loupe@3.1.3: {}
lru-cache@10.0.1: {}
@@ -4147,8 +4033,6 @@ snapshots:
type-fest: 1.4.0
yargs-parser: 20.2.9
- merge-stream@2.0.0: {}
-
merge2@1.4.1: {}
micromatch@4.0.7:
@@ -4163,8 +4047,6 @@ snapshots:
mime@1.6.0: {}
- mimic-fn@4.0.0: {}
-
mimic-response@3.1.0:
optional: true
@@ -4201,13 +4083,6 @@ snapshots:
dependencies:
minimist: 1.2.8
- mlly@1.7.4:
- dependencies:
- acorn: 8.14.0
- pathe: 2.0.2
- pkg-types: 1.3.1
- ufo: 1.5.4
-
moo@0.5.1: {}
move-file-cli@3.0.0:
@@ -4302,10 +4177,6 @@ snapshots:
npm-package-arg: 11.0.1
semver: 7.7.1
- npm-run-path@5.3.0:
- dependencies:
- path-key: 4.0.0
-
nth-check@2.1.1:
dependencies:
boolbase: 1.0.0
@@ -4320,10 +4191,6 @@ snapshots:
dependencies:
wrappy: 1.0.2
- onetime@6.0.0:
- dependencies:
- mimic-fn: 4.0.0
-
os-homedir@1.0.2: {}
os-tmpdir@1.0.2: {}
@@ -4341,10 +4208,6 @@ snapshots:
dependencies:
yocto-queue: 0.1.0
- p-limit@5.0.0:
- dependencies:
- yocto-queue: 1.1.1
-
p-locate@3.0.0:
dependencies:
p-limit: 2.3.0
@@ -4389,8 +4252,6 @@ snapshots:
path-key@3.1.1: {}
- path-key@4.0.0: {}
-
path-parse@1.0.7: {}
path-scurry@1.10.1:
@@ -4400,11 +4261,9 @@ snapshots:
path-type@4.0.0: {}
- pathe@1.1.2: {}
-
- pathe@2.0.2: {}
+ pathe@2.0.3: {}
- pathval@1.1.1: {}
+ pathval@2.0.0: {}
pend@1.2.0: {}
@@ -4420,12 +4279,6 @@ snapshots:
pirates@4.0.6: {}
- pkg-types@1.3.1:
- dependencies:
- confbox: 0.1.8
- mlly: 1.7.4
- pathe: 2.0.2
-
pkg-up@3.1.0:
dependencies:
find-up: 3.0.0
@@ -4514,12 +4367,6 @@ snapshots:
prettier@3.2.5: {}
- pretty-format@29.7.0:
- dependencies:
- '@jest/schemas': 29.6.3
- ansi-styles: 5.2.0
- react-is: 18.3.1
-
proc-log@3.0.0: {}
promise-inflight@1.0.1: {}
@@ -4551,8 +4398,6 @@ snapshots:
strip-json-comments: 2.0.1
optional: true
- react-is@18.3.1: {}
-
read-cache@1.0.0:
dependencies:
pify: 2.3.0
@@ -4797,8 +4642,6 @@ snapshots:
strip-bom@3.0.0: {}
- strip-final-newline@3.0.0: {}
-
strip-indent@4.0.0:
dependencies:
min-indent: 1.0.1
@@ -4806,10 +4649,6 @@ snapshots:
strip-json-comments@2.0.1:
optional: true
- strip-literal@2.1.1:
- dependencies:
- js-tokens: 9.0.1
-
sucrase@3.35.0:
dependencies:
'@jridgewell/gen-mapping': 0.3.5
@@ -4892,9 +4731,13 @@ snapshots:
tinybench@2.9.0: {}
- tinypool@0.8.4: {}
+ tinyexec@0.3.2: {}
- tinyspy@2.2.1: {}
+ tinypool@1.0.2: {}
+
+ tinyrainbow@2.0.0: {}
+
+ tinyspy@3.0.2: {}
tmp-cache@1.1.0: {}
@@ -4935,8 +4778,6 @@ snapshots:
tunnel@0.0.6: {}
- type-detect@4.1.0: {}
-
type-fest@1.4.0: {}
typed-rest-client@1.8.11:
@@ -4949,8 +4790,6 @@ snapshots:
uc.micro@1.0.6: {}
- ufo@1.5.4: {}
-
underscore@1.13.7: {}
undici-types@5.26.5: {}
@@ -4978,12 +4817,12 @@ snapshots:
dependencies:
builtins: 5.0.1
- vite-node@1.6.1(@types/node@18.19.43):
+ vite-node@3.0.9(@types/node@18.19.43):
dependencies:
cac: 6.7.14
debug: 4.4.0
- pathe: 1.1.2
- picocolors: 1.1.1
+ es-module-lexer: 1.6.0
+ pathe: 2.0.3
vite: 5.4.14(@types/node@18.19.43)
transitivePeerDependencies:
- '@types/node'
@@ -5016,33 +4855,34 @@ snapshots:
'@types/node': 18.19.43
fsevents: 2.3.3
- vitest@1.6.1(@types/node@18.19.43):
+ vitest@3.0.9(@types/node@18.19.43):
dependencies:
- '@vitest/expect': 1.6.1
- '@vitest/runner': 1.6.1
- '@vitest/snapshot': 1.6.1
- '@vitest/spy': 1.6.1
- '@vitest/utils': 1.6.1
- acorn-walk: 8.3.4
- chai: 4.5.0
+ '@vitest/expect': 3.0.9
+ '@vitest/mocker': 3.0.9(vite@5.4.14(@types/node@18.19.43))
+ '@vitest/pretty-format': 3.0.9
+ '@vitest/runner': 3.0.9
+ '@vitest/snapshot': 3.0.9
+ '@vitest/spy': 3.0.9
+ '@vitest/utils': 3.0.9
+ chai: 5.2.0
debug: 4.4.0
- execa: 8.0.1
- local-pkg: 0.5.1
+ expect-type: 1.2.0
magic-string: 0.30.17
- pathe: 1.1.2
- picocolors: 1.1.1
+ pathe: 2.0.3
std-env: 3.8.0
- strip-literal: 2.1.1
tinybench: 2.9.0
- tinypool: 0.8.4
+ tinyexec: 0.3.2
+ tinypool: 1.0.2
+ tinyrainbow: 2.0.0
vite: 5.4.14(@types/node@18.19.43)
- vite-node: 1.6.1(@types/node@18.19.43)
+ vite-node: 3.0.9(@types/node@18.19.43)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 18.19.43
transitivePeerDependencies:
- less
- lightningcss
+ - msw
- sass
- sass-embedded
- stylus
@@ -5172,5 +5012,3 @@ snapshots:
buffer-crc32: 0.2.13
yocto-queue@0.1.0: {}
-
- yocto-queue@1.1.1: {}
From d909c7716a7aabdf29d99b8f924429ecc67a06bc Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Sat, 22 Mar 2025 12:51:45 -0400
Subject: [PATCH 15/93] Bump versions
---
.../v4/auto-content-split/package-lock.json | 104 +++++++++---------
.../v4/auto-content-split/package.json | 4 +-
.../v4/auto-content/package-lock.json | 104 +++++++++---------
.../fixtures/v4/auto-content/package.json | 4 +-
.../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/custom-source/package-lock.json | 8 +-
.../fixtures/v4/custom-source/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 +-
24 files changed, 158 insertions(+), 158 deletions(-)
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package-lock.json
index 50cb1adf..b829f613 100644
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package-lock.json
+++ b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package-lock.json
@@ -5,36 +5,36 @@
"packages": {
"": {
"dependencies": {
- "@tailwindcss/oxide": "^4.0.0-beta.10",
- "tailwindcss": "^4.0.0-beta.10"
+ "@tailwindcss/oxide": "^4.0.15",
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/@tailwindcss/oxide": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.0-beta.10.tgz",
- "integrity": "sha512-r6tfUMqtkdX2Bwuhury6/wHfrWK2R8aRHMAVcppCKr1/SE2uIlPCGcOzkuybWlRU9OwjHk1t+dVgEcC0igSZ7g==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.15.tgz",
+ "integrity": "sha512-e0uHrKfPu7JJGMfjwVNyt5M0u+OP8kUmhACwIRlM+JNBuReDVQ63yAD1NWe5DwJtdaHjugNBil76j+ks3zlk6g==",
"license": "MIT",
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.0.0-beta.10",
- "@tailwindcss/oxide-darwin-arm64": "4.0.0-beta.10",
- "@tailwindcss/oxide-darwin-x64": "4.0.0-beta.10",
- "@tailwindcss/oxide-freebsd-x64": "4.0.0-beta.10",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.0-beta.10",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.0.0-beta.10",
- "@tailwindcss/oxide-linux-arm64-musl": "4.0.0-beta.10",
- "@tailwindcss/oxide-linux-x64-gnu": "4.0.0-beta.10",
- "@tailwindcss/oxide-linux-x64-musl": "4.0.0-beta.10",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.0.0-beta.10",
- "@tailwindcss/oxide-win32-x64-msvc": "4.0.0-beta.10"
+ "@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"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.0-beta.10.tgz",
- "integrity": "sha512-wQ3NcFTToQlippJqxNsPtUjkzldcL+RciGvW5tDRGq7yXt/giND292+joshNp55Bi51rXRLXfAU19PV0JP9IGQ==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.15.tgz",
+ "integrity": "sha512-EBuyfSKkom7N+CB3A+7c0m4+qzKuiN0WCvzPvj5ZoRu4NlQadg/mthc1tl5k9b5ffRGsbDvP4k21azU4VwVk3Q==",
"cpu": [
"arm64"
],
@@ -48,9 +48,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.0-beta.10.tgz",
- "integrity": "sha512-g65kSY1n0AdhhWXchTqmyv+jblDMM+eXM2SJaBidcGicz3wYm0a548i0lVIn3Lt8f72akhftz9YKHaL3riq5AA==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.15.tgz",
+ "integrity": "sha512-ObVAnEpLepMhV9VoO0JSit66jiN5C4YCqW3TflsE9boo2Z7FIjV80RFbgeL2opBhtxbaNEDa6D0/hq/EP03kgQ==",
"cpu": [
"arm64"
],
@@ -64,9 +64,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.0-beta.10.tgz",
- "integrity": "sha512-edQRV1hucPU5pIh1j4pXcqnwdxjme0i4P2tMqBvSTM9UTGQmvxn6/QM2oos7htn3dfgqIkvMBb0y6u1mnBLwKw==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.15.tgz",
+ "integrity": "sha512-IElwoFhUinOr9MyKmGTPNi1Rwdh68JReFgYWibPWTGuevkHkLWKEflZc2jtI5lWZ5U9JjUnUfnY43I4fEXrc4g==",
"cpu": [
"x64"
],
@@ -80,9 +80,9 @@
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.0-beta.10.tgz",
- "integrity": "sha512-okok/IcX9jh85T9mzLOYGr8996KNm+ZU0t1mwibWvW36jBM0oIfq3TVw3xI3M3OkuYj1XpALfcO4Kr8ZZrt2cw==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.15.tgz",
+ "integrity": "sha512-6BLLqyx7SIYRBOnTZ8wgfXANLJV5TQd3PevRJZp0vn42eO58A2LykRKdvL1qyPfdpmEVtF+uVOEZ4QTMqDRAWA==",
"cpu": [
"x64"
],
@@ -96,9 +96,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.0-beta.10.tgz",
- "integrity": "sha512-gfjDqO/3fWkKPd2Lv3rj0vW2CMHRmWH50qFJ29vAkhtteg7oPB/NU1Z4K5YkSpul0Pgcfixv6YfPQpzBkrM7Nw==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.15.tgz",
+ "integrity": "sha512-Zy63EVqO9241Pfg6G0IlRIWyY5vNcWrL5dd2WAKVJZRQVeolXEf1KfjkyeAAlErDj72cnyXObEZjMoPEKHpdNw==",
"cpu": [
"arm"
],
@@ -112,9 +112,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.0-beta.10.tgz",
- "integrity": "sha512-/LgdJ+XBZK5ZgyvxiMEJTrMj36GMEhRGTtuHGJmxJDcvv4VFbCzDmNN8H/YC9dtCF0OEOfsCCQxEeKTfUYIuJg==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.15.tgz",
+ "integrity": "sha512-2NemGQeaTbtIp1Z2wyerbVEJZTkAWhMDOhhR5z/zJ75yMNf8yLnE+sAlyf6yGDNr+1RqvWrRhhCFt7i0CIxe4Q==",
"cpu": [
"arm64"
],
@@ -128,9 +128,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.0-beta.10.tgz",
- "integrity": "sha512-pIoXoN6BZ3gDDDBBW2mZtLLNMTB3UW1F/uRGMTuPUYl8cERUlXOYek4v2veCMwzTNf5l4FrgwPm8G5rsx9qNiw==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.15.tgz",
+ "integrity": "sha512-342GVnhH/6PkVgKtEzvNVuQ4D+Q7B7qplvuH20Cfz9qEtydG6IQczTZ5IT4JPlh931MG1NUCVxg+CIorr1WJyw==",
"cpu": [
"arm64"
],
@@ -144,9 +144,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.0-beta.10.tgz",
- "integrity": "sha512-mVXCIesysMgzU7XerH2VWN6R9LEdr+MUZ1LAvUx1wDcjawMPGWbzjyVWzbRNgWxQLPi71YuFT9hU3Srewfb6dw==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.15.tgz",
+ "integrity": "sha512-g76GxlKH124RuGqacCEFc2nbzRl7bBrlC8qDQMiUABkiifDRHOIUjgKbLNG4RuR9hQAD/MKsqZ7A8L08zsoBrw==",
"cpu": [
"x64"
],
@@ -160,9 +160,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.0-beta.10.tgz",
- "integrity": "sha512-TPszoPUQbXTy76kgi38ohCpT9SFMjxHaLKO3z6SUZis2+HURl7H6Z8rS/PlZwNCawv5Dg+UKsYfh1UwvfepXmQ==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.15.tgz",
+ "integrity": "sha512-Gg/Y1XrKEvKpq6WeNt2h8rMIKOBj/W3mNa5NMvkQgMC7iO0+UNLrYmt6zgZufht66HozNpn+tJMbbkZ5a3LczA==",
"cpu": [
"x64"
],
@@ -176,9 +176,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.0-beta.10.tgz",
- "integrity": "sha512-Gq4bQm1QsaMIX95/zkpnI3J+8CMAgZSWLIYeAq2ehom9AG/Qe9AaT7dDzIzI68uovqwScnKWiJJLo1SfHveoRA==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.15.tgz",
+ "integrity": "sha512-7QtSSJwYZ7ZK1phVgcNZpuf7c7gaCj8Wb0xjliligT5qCGCp79OV2n3SJummVZdw4fbTNKUOYMO7m1GinppZyA==",
"cpu": [
"arm64"
],
@@ -192,9 +192,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.0-beta.10.tgz",
- "integrity": "sha512-rzKGE0TvLyADYaAq50oYVGtFXZ9QZEZfeaEqrRdUkK4HMNr5kv66bv7k46JN9260/K8cHp60ISPwLObHloi3zA==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.15.tgz",
+ "integrity": "sha512-JQ5H+5MLhOjpgNp6KomouE0ZuKmk3hO5h7/ClMNAQ8gZI2zkli3IH8ZqLbd2DVfXDbdxN2xvooIEeIlkIoSCqw==",
"cpu": [
"x64"
],
@@ -208,9 +208,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"license": "MIT"
}
}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package.json
index 8f5d1bbd..908f1d2f 100644
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package.json
+++ b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package.json
@@ -1,6 +1,6 @@
{
"dependencies": {
- "tailwindcss": "^4.0.0-beta.10",
- "@tailwindcss/oxide": "^4.0.0-beta.10"
+ "tailwindcss": "^4.0.15",
+ "@tailwindcss/oxide": "^4.0.15"
}
}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package-lock.json
index 7b41a366..6b32300e 100644
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package-lock.json
+++ b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package-lock.json
@@ -5,36 +5,36 @@
"packages": {
"": {
"dependencies": {
- "@tailwindcss/oxide": "^4.0.0-beta.10",
- "tailwindcss": "^4.0.0-beta.10"
+ "@tailwindcss/oxide": "^4.0.15",
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/@tailwindcss/oxide": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.0-beta.10.tgz",
- "integrity": "sha512-r6tfUMqtkdX2Bwuhury6/wHfrWK2R8aRHMAVcppCKr1/SE2uIlPCGcOzkuybWlRU9OwjHk1t+dVgEcC0igSZ7g==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.15.tgz",
+ "integrity": "sha512-e0uHrKfPu7JJGMfjwVNyt5M0u+OP8kUmhACwIRlM+JNBuReDVQ63yAD1NWe5DwJtdaHjugNBil76j+ks3zlk6g==",
"license": "MIT",
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.0.0-beta.10",
- "@tailwindcss/oxide-darwin-arm64": "4.0.0-beta.10",
- "@tailwindcss/oxide-darwin-x64": "4.0.0-beta.10",
- "@tailwindcss/oxide-freebsd-x64": "4.0.0-beta.10",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.0-beta.10",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.0.0-beta.10",
- "@tailwindcss/oxide-linux-arm64-musl": "4.0.0-beta.10",
- "@tailwindcss/oxide-linux-x64-gnu": "4.0.0-beta.10",
- "@tailwindcss/oxide-linux-x64-musl": "4.0.0-beta.10",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.0.0-beta.10",
- "@tailwindcss/oxide-win32-x64-msvc": "4.0.0-beta.10"
+ "@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"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.0-beta.10.tgz",
- "integrity": "sha512-wQ3NcFTToQlippJqxNsPtUjkzldcL+RciGvW5tDRGq7yXt/giND292+joshNp55Bi51rXRLXfAU19PV0JP9IGQ==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.15.tgz",
+ "integrity": "sha512-EBuyfSKkom7N+CB3A+7c0m4+qzKuiN0WCvzPvj5ZoRu4NlQadg/mthc1tl5k9b5ffRGsbDvP4k21azU4VwVk3Q==",
"cpu": [
"arm64"
],
@@ -48,9 +48,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.0-beta.10.tgz",
- "integrity": "sha512-g65kSY1n0AdhhWXchTqmyv+jblDMM+eXM2SJaBidcGicz3wYm0a548i0lVIn3Lt8f72akhftz9YKHaL3riq5AA==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.15.tgz",
+ "integrity": "sha512-ObVAnEpLepMhV9VoO0JSit66jiN5C4YCqW3TflsE9boo2Z7FIjV80RFbgeL2opBhtxbaNEDa6D0/hq/EP03kgQ==",
"cpu": [
"arm64"
],
@@ -64,9 +64,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.0-beta.10.tgz",
- "integrity": "sha512-edQRV1hucPU5pIh1j4pXcqnwdxjme0i4P2tMqBvSTM9UTGQmvxn6/QM2oos7htn3dfgqIkvMBb0y6u1mnBLwKw==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.15.tgz",
+ "integrity": "sha512-IElwoFhUinOr9MyKmGTPNi1Rwdh68JReFgYWibPWTGuevkHkLWKEflZc2jtI5lWZ5U9JjUnUfnY43I4fEXrc4g==",
"cpu": [
"x64"
],
@@ -80,9 +80,9 @@
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.0-beta.10.tgz",
- "integrity": "sha512-okok/IcX9jh85T9mzLOYGr8996KNm+ZU0t1mwibWvW36jBM0oIfq3TVw3xI3M3OkuYj1XpALfcO4Kr8ZZrt2cw==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.15.tgz",
+ "integrity": "sha512-6BLLqyx7SIYRBOnTZ8wgfXANLJV5TQd3PevRJZp0vn42eO58A2LykRKdvL1qyPfdpmEVtF+uVOEZ4QTMqDRAWA==",
"cpu": [
"x64"
],
@@ -96,9 +96,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.0-beta.10.tgz",
- "integrity": "sha512-gfjDqO/3fWkKPd2Lv3rj0vW2CMHRmWH50qFJ29vAkhtteg7oPB/NU1Z4K5YkSpul0Pgcfixv6YfPQpzBkrM7Nw==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.15.tgz",
+ "integrity": "sha512-Zy63EVqO9241Pfg6G0IlRIWyY5vNcWrL5dd2WAKVJZRQVeolXEf1KfjkyeAAlErDj72cnyXObEZjMoPEKHpdNw==",
"cpu": [
"arm"
],
@@ -112,9 +112,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.0-beta.10.tgz",
- "integrity": "sha512-/LgdJ+XBZK5ZgyvxiMEJTrMj36GMEhRGTtuHGJmxJDcvv4VFbCzDmNN8H/YC9dtCF0OEOfsCCQxEeKTfUYIuJg==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.15.tgz",
+ "integrity": "sha512-2NemGQeaTbtIp1Z2wyerbVEJZTkAWhMDOhhR5z/zJ75yMNf8yLnE+sAlyf6yGDNr+1RqvWrRhhCFt7i0CIxe4Q==",
"cpu": [
"arm64"
],
@@ -128,9 +128,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.0-beta.10.tgz",
- "integrity": "sha512-pIoXoN6BZ3gDDDBBW2mZtLLNMTB3UW1F/uRGMTuPUYl8cERUlXOYek4v2veCMwzTNf5l4FrgwPm8G5rsx9qNiw==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.15.tgz",
+ "integrity": "sha512-342GVnhH/6PkVgKtEzvNVuQ4D+Q7B7qplvuH20Cfz9qEtydG6IQczTZ5IT4JPlh931MG1NUCVxg+CIorr1WJyw==",
"cpu": [
"arm64"
],
@@ -144,9 +144,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.0-beta.10.tgz",
- "integrity": "sha512-mVXCIesysMgzU7XerH2VWN6R9LEdr+MUZ1LAvUx1wDcjawMPGWbzjyVWzbRNgWxQLPi71YuFT9hU3Srewfb6dw==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.15.tgz",
+ "integrity": "sha512-g76GxlKH124RuGqacCEFc2nbzRl7bBrlC8qDQMiUABkiifDRHOIUjgKbLNG4RuR9hQAD/MKsqZ7A8L08zsoBrw==",
"cpu": [
"x64"
],
@@ -160,9 +160,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.0-beta.10.tgz",
- "integrity": "sha512-TPszoPUQbXTy76kgi38ohCpT9SFMjxHaLKO3z6SUZis2+HURl7H6Z8rS/PlZwNCawv5Dg+UKsYfh1UwvfepXmQ==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.15.tgz",
+ "integrity": "sha512-Gg/Y1XrKEvKpq6WeNt2h8rMIKOBj/W3mNa5NMvkQgMC7iO0+UNLrYmt6zgZufht66HozNpn+tJMbbkZ5a3LczA==",
"cpu": [
"x64"
],
@@ -176,9 +176,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.0-beta.10.tgz",
- "integrity": "sha512-Gq4bQm1QsaMIX95/zkpnI3J+8CMAgZSWLIYeAq2ehom9AG/Qe9AaT7dDzIzI68uovqwScnKWiJJLo1SfHveoRA==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.15.tgz",
+ "integrity": "sha512-7QtSSJwYZ7ZK1phVgcNZpuf7c7gaCj8Wb0xjliligT5qCGCp79OV2n3SJummVZdw4fbTNKUOYMO7m1GinppZyA==",
"cpu": [
"arm64"
],
@@ -192,9 +192,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.0-beta.10.tgz",
- "integrity": "sha512-rzKGE0TvLyADYaAq50oYVGtFXZ9QZEZfeaEqrRdUkK4HMNr5kv66bv7k46JN9260/K8cHp60ISPwLObHloi3zA==",
+ "version": "4.0.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.15.tgz",
+ "integrity": "sha512-JQ5H+5MLhOjpgNp6KomouE0ZuKmk3hO5h7/ClMNAQ8gZI2zkli3IH8ZqLbd2DVfXDbdxN2xvooIEeIlkIoSCqw==",
"cpu": [
"x64"
],
@@ -208,9 +208,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"license": "MIT"
}
}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package.json
index 8f5d1bbd..908f1d2f 100644
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package.json
+++ b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package.json
@@ -1,6 +1,6 @@
{
"dependencies": {
- "tailwindcss": "^4.0.0-beta.10",
- "@tailwindcss/oxide": "^4.0.0-beta.10"
+ "tailwindcss": "^4.0.15",
+ "@tailwindcss/oxide": "^4.0.15"
}
}
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 ce144e37..6275c3dc 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"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 90a7d003..b6cb53b1 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
}
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 81dca7a7..5089dc65 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"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 90a7d003..b6cb53b1 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package-lock.json
index 2f24caae..ce004f1d 100644
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package-lock.json
+++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package-lock.json
@@ -5,13 +5,13 @@
"packages": {
"": {
"dependencies": {
- "tailwindcss": "^4.0.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"license": "MIT"
}
}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package.json
index 90a7d003..b6cb53b1 100644
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package.json
+++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package.json
@@ -1,5 +1,5 @@
{
"dependencies": {
- "tailwindcss": "^4.0.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
}
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 a08734a7..555ee660 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"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 90a7d003..b6cb53b1 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
}
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 4a96d074..24d978d8 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"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 90a7d003..b6cb53b1 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
}
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 0ad59a4e..ed4d2d9a 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"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 90a7d003..b6cb53b1 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
}
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 5a7fd4c6..c4664645 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"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 90a7d003..b6cb53b1 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
}
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 f73ee08a..651bf7c9 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"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 90a7d003..b6cb53b1 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
}
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 b2ac9b4f..1e5486ae 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"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 90a7d003..b6cb53b1 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
}
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 76d48561..b622b7b7 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
},
"node_modules/@private/admin": {
@@ -32,9 +32,9 @@
"link": true
},
"node_modules/tailwindcss": {
- "version": "4.0.0-beta.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.10.tgz",
- "integrity": "sha512-QphgB58oviLYkSWu74+5S2FU8sREus9wBEzWWi5iSxnBrgzfVFp7pIeAPbQ7gq1WXnbllCipsTjK3yT+Do4y0Q==",
+ "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==",
"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 fac98d06..aa5e54db 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.0-beta.10"
+ "tailwindcss": "^4.0.15"
}
}
From 7593e824b2186dd3e4241a26729cd2638e0d40b9 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Sat, 22 Mar 2025 12:51:50 -0400
Subject: [PATCH 16/93] Update tests
---
.../tests/completions/completions.test.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js
index 96f8188a..dbe9a352 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(12314)
- expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(304)
+ expect(result.items.length).toBe(13172)
+ expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(317)
expect(result).toEqual({
isIncomplete: false,
items: expect.arrayContaining([
From 405733ba59e1d2d51220f1e5d2a710def1ecef02 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Sat, 22 Mar 2025 13:28:17 -0400
Subject: [PATCH 17/93] Refactor
---
.../tailwindcss-language-server/src/testing/index.ts | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/packages/tailwindcss-language-server/src/testing/index.ts b/packages/tailwindcss-language-server/src/testing/index.ts
index 21296fc7..a0516d9a 100644
--- a/packages/tailwindcss-language-server/src/testing/index.ts
+++ b/packages/tailwindcss-language-server/src/testing/index.ts
@@ -9,9 +9,14 @@ export interface TestUtils {
root: string
}
+export interface StorageSymlink {
+ [IS_A_SYMLINK]: true
+ filepath: string
+}
+
export interface Storage {
/** A list of files and their content */
- [filePath: string]: string | Uint8Array | { [IS_A_SYMLINK]: true; filepath: string }
+ [filePath: string]: string | Uint8Array | StorageSymlink
}
export interface TestConfig {
@@ -70,7 +75,7 @@ async function setup(config: TestConfig): Promise {
}
const IS_A_SYMLINK = Symbol('is-a-symlink')
-export const symlinkTo = function (filepath: string) {
+export function symlinkTo(filepath: string): StorageSymlink {
return {
[IS_A_SYMLINK]: true as const,
filepath,
From 765ebce4c8a719a115f935352eb5090fef91e776 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Sat, 22 Mar 2025 14:18:50 -0400
Subject: [PATCH 18/93] Fix content detection in later Oxide versions (#1280)
This should improve project selection for files in a workspace with
multiple projects. Though probably only a tiny bit.
---
.../tailwindcss-language-server/package.json | 2 +-
.../tailwindcss-language-server/src/oxide.ts | 4 +-
.../src/project-locator.test.ts | 45 ++++----
packages/vscode-tailwindcss/CHANGELOG.md | 2 +-
pnpm-lock.yaml | 100 ++++++++++--------
5 files changed, 77 insertions(+), 76 deletions(-)
diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json
index 63089551..d31549e7 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.0-alpha.19",
+ "@tailwindcss/oxide": "^4.0.15",
"@tailwindcss/typography": "0.5.7",
"@types/braces": "3.0.1",
"@types/color-name": "^1.1.3",
diff --git a/packages/tailwindcss-language-server/src/oxide.ts b/packages/tailwindcss-language-server/src/oxide.ts
index bb8700ff..5dad7553 100644
--- a/packages/tailwindcss-language-server/src/oxide.ts
+++ b/packages/tailwindcss-language-server/src/oxide.ts
@@ -68,7 +68,7 @@ async function loadOxideAtPath(id: string): Promise {
let oxide = await import(id)
// This is a much older, unsupported version of Oxide before v4.0.0-alpha.1
- if (!oxide.scanDir) return null
+ if (!oxide.scanDir && !oxide.Scanner) return null
return oxide
}
@@ -101,7 +101,7 @@ interface ScanResult {
* For example, the `sources` option is ignored before v4.0.0-alpha.19.
*/
export async function scan(options: ScanOptions): Promise {
- const oxide = await loadOxideAtPath(options.oxidePath)
+ let oxide = await loadOxideAtPath(options.oxidePath)
if (!oxide) return null
// V1
diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts
index 6414a099..f350dba1 100644
--- a/packages/tailwindcss-language-server/src/project-locator.test.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.test.ts
@@ -9,7 +9,7 @@ import { css, defineTest, js, json, scss, Storage, TestUtils } from './testing'
let settings: Settings = {
tailwindCSS: {
files: {
- exclude: [],
+ exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'],
},
},
} as any
@@ -114,10 +114,7 @@ testFixture('v4/workspaces', [
{
config: 'packages/admin/app.css',
selectors: [
- '{URL}/node_modules/tailwindcss/**',
- '{URL}/node_modules/tailwindcss/index.css',
- '{URL}/node_modules/tailwindcss/theme.css',
- '{URL}/node_modules/tailwindcss/utilities.css',
+ '{URL}/packages/admin/*',
'{URL}/packages/admin/**',
'{URL}/packages/admin/app.css',
'{URL}/packages/admin/package.json',
@@ -126,15 +123,12 @@ testFixture('v4/workspaces', [
{
config: 'packages/web/app.css',
selectors: [
- '{URL}/node_modules/tailwindcss/**',
- '{URL}/node_modules/tailwindcss/index.css',
- '{URL}/node_modules/tailwindcss/theme.css',
- '{URL}/node_modules/tailwindcss/utilities.css',
'{URL}/packages/style-export/**',
'{URL}/packages/style-export/lib.css',
'{URL}/packages/style-export/theme.css',
'{URL}/packages/style-main-field/**',
'{URL}/packages/style-main-field/lib.css',
+ '{URL}/packages/web/*',
'{URL}/packages/web/**',
'{URL}/packages/web/app.css',
'{URL}/packages/web/package.json',
@@ -147,28 +141,24 @@ testFixture('v4/auto-content', [
{
config: 'src/app.css',
content: [
+ '{URL}/*',
'{URL}/package.json',
'{URL}/src/index.html',
'{URL}/src/components/example.html',
- '{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}',
+ '{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}',
],
},
])
testFixture('v4/auto-content-split', [
- //
- {
- // TODO: This should _probably_ not be present
- config: 'node_modules/tailwindcss/index.css',
- content: [],
- },
{
config: 'src/app.css',
content: [
+ '{URL}/*',
'{URL}/package.json',
'{URL}/src/index.html',
'{URL}/src/components/example.html',
- '{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}',
+ '{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}',
],
},
])
@@ -178,23 +168,23 @@ testFixture('v4/custom-source', [
{
config: 'admin/app.css',
content: [
- '{URL}/admin/**/*.{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}',
- '{URL}/admin/**/*.bin',
+ '{URL}/*',
'{URL}/admin/foo.bin',
+ '{URL}/admin/{**/*.bin,**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}}',
'{URL}/package.json',
'{URL}/shared.html',
- '{URL}/web/**/*.{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}',
+ '{URL}/web/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
],
},
{
config: 'web/app.css',
content: [
- '{URL}/admin/**/*.{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}',
- '{URL}/web/*.bin',
- '{URL}/web/bar.bin',
+ '{URL}/*',
+ '{URL}/admin/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
'{URL}/package.json',
'{URL}/shared.html',
- '{URL}/web/**/*.{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}',
+ '{URL}/web/bar.bin',
+ '{URL}/web/{**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue},*.bin}',
],
},
])
@@ -203,7 +193,7 @@ testFixture('v4/missing-files', [
//
{
config: 'app.css',
- content: ['{URL}/package.json'],
+ content: ['{URL}/*', '{URL}/package.json'],
},
])
@@ -212,8 +202,9 @@ testFixture('v4/path-mappings', [
{
config: 'app.css',
content: [
+ '{URL}/*',
'{URL}/package.json',
- '{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}',
+ '{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/a/my-config.ts',
'{URL}/src/a/my-plugin.ts',
'{URL}/tsconfig.json',
@@ -225,7 +216,7 @@ testFixture('v4/invalid-import-order', [
//
{
config: 'tailwind.css',
- content: ['{URL}/package.json'],
+ content: ['{URL}/*', '{URL}/package.json'],
},
])
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 9b0498db..0ec6c3a4 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,7 +2,7 @@
## Prerelease
-- Nothing yet!
+- Fix content detection when using v4.0+ ([#1280](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1280))
# 0.14.11
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6367f5ae..ab264604 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.0-alpha.19
- version: 4.0.0-alpha.19
+ specifier: ^4.0.15
+ version: 4.0.15
'@tailwindcss/typography':
specifier: 0.5.7
version: 0.5.7(tailwindcss@3.4.17)
@@ -890,68 +890,74 @@ packages:
peerDependencies:
tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1'
- '@tailwindcss/oxide-android-arm64@4.0.0-alpha.19':
- resolution: {integrity: sha512-NhpXem1j7g0uSGyLucmMj0VVQMeUrWc6kR/Ymnri3tpw2eaykgFYwLfdnI7jdJRxUxa/nNJip9yBJ3diZXl60w==}
+ '@tailwindcss/oxide-android-arm64@4.0.15':
+ resolution: {integrity: sha512-EBuyfSKkom7N+CB3A+7c0m4+qzKuiN0WCvzPvj5ZoRu4NlQadg/mthc1tl5k9b5ffRGsbDvP4k21azU4VwVk3Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
- '@tailwindcss/oxide-darwin-arm64@4.0.0-alpha.19':
- resolution: {integrity: sha512-KCdalT+huX2cW9snNmPr+B66V91cSzIobBCXVgYCPCh0NZF4ueKu+X+kQN2gFxurDUm/D+aKW/0rQUIsUmFpdQ==}
+ '@tailwindcss/oxide-darwin-arm64@4.0.15':
+ resolution: {integrity: sha512-ObVAnEpLepMhV9VoO0JSit66jiN5C4YCqW3TflsE9boo2Z7FIjV80RFbgeL2opBhtxbaNEDa6D0/hq/EP03kgQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
- '@tailwindcss/oxide-darwin-x64@4.0.0-alpha.19':
- resolution: {integrity: sha512-ETOWA08loUmOVTEa3zhRhY8HyqdGtR9DNhXdrRZBi67ZwCAmA+jg5B+mZaYeQJ6CjETx07BnhcGmmxGz3/6c8w==}
+ '@tailwindcss/oxide-darwin-x64@4.0.15':
+ resolution: {integrity: sha512-IElwoFhUinOr9MyKmGTPNi1Rwdh68JReFgYWibPWTGuevkHkLWKEflZc2jtI5lWZ5U9JjUnUfnY43I4fEXrc4g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
- '@tailwindcss/oxide-freebsd-x64@4.0.0-alpha.19':
- resolution: {integrity: sha512-uB0rYLpqPnmyqtYSKHu1AtnHeerNcVH+de0sIufGCBDFGYNxmW8feCKNZwo6r7U/Fzg+AF9BOjwvdvd4yLfQ8g==}
+ '@tailwindcss/oxide-freebsd-x64@4.0.15':
+ resolution: {integrity: sha512-6BLLqyx7SIYRBOnTZ8wgfXANLJV5TQd3PevRJZp0vn42eO58A2LykRKdvL1qyPfdpmEVtF+uVOEZ4QTMqDRAWA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-alpha.19':
- resolution: {integrity: sha512-hIfm6DNh18rkz2PFRsQANINH0tpso6/vaU8p0Qw7rgYqqrxJTRpyLVsnvx3ahMOplJrDT6Z+Nfak8udnZN2C/Q==}
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.15':
+ resolution: {integrity: sha512-Zy63EVqO9241Pfg6G0IlRIWyY5vNcWrL5dd2WAKVJZRQVeolXEf1KfjkyeAAlErDj72cnyXObEZjMoPEKHpdNw==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
- '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-alpha.19':
- resolution: {integrity: sha512-n1Hr+8Hup2GLmeonQy9ydZxMBCs0FR1rcv4K7AHip+6PbD0se8k9LBIZac3OguFNj2hTehiadaiRb18rsVUw0g==}
+ '@tailwindcss/oxide-linux-arm64-gnu@4.0.15':
+ resolution: {integrity: sha512-2NemGQeaTbtIp1Z2wyerbVEJZTkAWhMDOhhR5z/zJ75yMNf8yLnE+sAlyf6yGDNr+1RqvWrRhhCFt7i0CIxe4Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@tailwindcss/oxide-linux-arm64-musl@4.0.0-alpha.19':
- resolution: {integrity: sha512-KhwthLZh9Js3t5URkuRURw45iU3rSh9vhuHRaV4KQT10ZFiXQMUlFfMKJyxRMcgC2fcL5vsiqwjOaMwp7Y8vsQ==}
+ '@tailwindcss/oxide-linux-arm64-musl@4.0.15':
+ resolution: {integrity: sha512-342GVnhH/6PkVgKtEzvNVuQ4D+Q7B7qplvuH20Cfz9qEtydG6IQczTZ5IT4JPlh931MG1NUCVxg+CIorr1WJyw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@tailwindcss/oxide-linux-x64-gnu@4.0.0-alpha.19':
- resolution: {integrity: sha512-qqEuULSiczyZkdWVzwkiiFyOYqx5RR2De75iwYREzXUuHRHval1ep2qO7tvZdgt37t2vgjoQwaPA6zO+JGUa+Q==}
+ '@tailwindcss/oxide-linux-x64-gnu@4.0.15':
+ resolution: {integrity: sha512-g76GxlKH124RuGqacCEFc2nbzRl7bBrlC8qDQMiUABkiifDRHOIUjgKbLNG4RuR9hQAD/MKsqZ7A8L08zsoBrw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@tailwindcss/oxide-linux-x64-musl@4.0.0-alpha.19':
- resolution: {integrity: sha512-JKTYCiNz83sYl2FgKJk3dL11FS4dAj7Rgmuz3TVANmeMYTBtwmFPthoH0qAmF+hjPJgT5Ne7lSwplfuHJAD3MQ==}
+ '@tailwindcss/oxide-linux-x64-musl@4.0.15':
+ resolution: {integrity: sha512-Gg/Y1XrKEvKpq6WeNt2h8rMIKOBj/W3mNa5NMvkQgMC7iO0+UNLrYmt6zgZufht66HozNpn+tJMbbkZ5a3LczA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@tailwindcss/oxide-win32-x64-msvc@4.0.0-alpha.19':
- resolution: {integrity: sha512-D43tji14+i/GhJn5YZX8c2FXFmAqlI6DDGX8caUM35dga/uT+sJUfLJ84YigJyvAdSF1gBVdm7MvhYRcVYHOwg==}
+ '@tailwindcss/oxide-win32-arm64-msvc@4.0.15':
+ resolution: {integrity: sha512-7QtSSJwYZ7ZK1phVgcNZpuf7c7gaCj8Wb0xjliligT5qCGCp79OV2n3SJummVZdw4fbTNKUOYMO7m1GinppZyA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.0.15':
+ resolution: {integrity: sha512-JQ5H+5MLhOjpgNp6KomouE0ZuKmk3hO5h7/ClMNAQ8gZI2zkli3IH8ZqLbd2DVfXDbdxN2xvooIEeIlkIoSCqw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
- '@tailwindcss/oxide@4.0.0-alpha.19':
- resolution: {integrity: sha512-DdkrVz/MKPoe9v7W3c0+SEFKRDIPMSsxgN7gPC+xeTnTL4BGoT5b1EiVGFuXWEyLbDmWztuN6z75Yuze2BwvMQ==}
+ '@tailwindcss/oxide@4.0.15':
+ resolution: {integrity: sha512-e0uHrKfPu7JJGMfjwVNyt5M0u+OP8kUmhACwIRlM+JNBuReDVQ63yAD1NWe5DwJtdaHjugNBil76j+ks3zlk6g==}
engines: {node: '>= 10'}
'@tailwindcss/typography@0.5.7':
@@ -3099,48 +3105,52 @@ snapshots:
dependencies:
tailwindcss: 3.4.17
- '@tailwindcss/oxide-android-arm64@4.0.0-alpha.19':
+ '@tailwindcss/oxide-android-arm64@4.0.15':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.0.15':
optional: true
- '@tailwindcss/oxide-darwin-arm64@4.0.0-alpha.19':
+ '@tailwindcss/oxide-darwin-x64@4.0.15':
optional: true
- '@tailwindcss/oxide-darwin-x64@4.0.0-alpha.19':
+ '@tailwindcss/oxide-freebsd-x64@4.0.15':
optional: true
- '@tailwindcss/oxide-freebsd-x64@4.0.0-alpha.19':
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.15':
optional: true
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-alpha.19':
+ '@tailwindcss/oxide-linux-arm64-gnu@4.0.15':
optional: true
- '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-alpha.19':
+ '@tailwindcss/oxide-linux-arm64-musl@4.0.15':
optional: true
- '@tailwindcss/oxide-linux-arm64-musl@4.0.0-alpha.19':
+ '@tailwindcss/oxide-linux-x64-gnu@4.0.15':
optional: true
- '@tailwindcss/oxide-linux-x64-gnu@4.0.0-alpha.19':
+ '@tailwindcss/oxide-linux-x64-musl@4.0.15':
optional: true
- '@tailwindcss/oxide-linux-x64-musl@4.0.0-alpha.19':
+ '@tailwindcss/oxide-win32-arm64-msvc@4.0.15':
optional: true
- '@tailwindcss/oxide-win32-x64-msvc@4.0.0-alpha.19':
+ '@tailwindcss/oxide-win32-x64-msvc@4.0.15':
optional: true
- '@tailwindcss/oxide@4.0.0-alpha.19':
+ '@tailwindcss/oxide@4.0.15':
optionalDependencies:
- '@tailwindcss/oxide-android-arm64': 4.0.0-alpha.19
- '@tailwindcss/oxide-darwin-arm64': 4.0.0-alpha.19
- '@tailwindcss/oxide-darwin-x64': 4.0.0-alpha.19
- '@tailwindcss/oxide-freebsd-x64': 4.0.0-alpha.19
- '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.0-alpha.19
- '@tailwindcss/oxide-linux-arm64-gnu': 4.0.0-alpha.19
- '@tailwindcss/oxide-linux-arm64-musl': 4.0.0-alpha.19
- '@tailwindcss/oxide-linux-x64-gnu': 4.0.0-alpha.19
- '@tailwindcss/oxide-linux-x64-musl': 4.0.0-alpha.19
- '@tailwindcss/oxide-win32-x64-msvc': 4.0.0-alpha.19
+ '@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/typography@0.5.7(tailwindcss@3.4.17)':
dependencies:
From f5bcefc6dc8348ac3b964d0ea0bb321b20e08c2a Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Sat, 22 Mar 2025 14:19:44 -0400
Subject: [PATCH 19/93] Ensure file exclusions always work on Windows (#1281)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Sometimes exclusions weren't processed correctly on Windows. This cause
additional results to show up in file path completions in things like
`@config`, `@plugim`, `@source`, etc…
---
.../tailwindcss-language-server/src/util/isExcluded.ts | 10 +++++++++-
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/packages/tailwindcss-language-server/src/util/isExcluded.ts b/packages/tailwindcss-language-server/src/util/isExcluded.ts
index beb4115a..df998e7f 100644
--- a/packages/tailwindcss-language-server/src/util/isExcluded.ts
+++ b/packages/tailwindcss-language-server/src/util/isExcluded.ts
@@ -3,6 +3,7 @@ import * as path from 'node:path'
import type { TextDocument } from 'vscode-languageserver-textdocument'
import type { State } from '@tailwindcss/language-service/src/util/state'
import { getFileFsPath } from './uri'
+import { normalizePath, normalizeDriveLetter } from '../utils'
export default async function isExcluded(
state: State,
@@ -11,8 +12,15 @@ export default async function isExcluded(
): Promise {
let settings = await state.editor.getConfiguration(document.uri)
+ file = normalizePath(file)
+ file = normalizeDriveLetter(file)
+
for (let pattern of settings.tailwindCSS.files.exclude) {
- if (picomatch(path.join(state.editor.folder, pattern))(file)) {
+ pattern = path.join(state.editor.folder, pattern)
+ pattern = normalizePath(pattern)
+ pattern = normalizeDriveLetter(pattern)
+
+ if (picomatch(pattern)(file)) {
return true
}
}
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 0ec6c3a4..de81de55 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -3,6 +3,7 @@
## Prerelease
- Fix content detection when using v4.0+ ([#1280](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1280))
+- Ensure file exclusions always work on Windows ([#1281](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1281))
# 0.14.11
From 8b6eafa51a6669bb9f689c1da65eba4739da1b3c Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Sat, 22 Mar 2025 14:28:10 -0400
Subject: [PATCH 20/93] Add CI testing matrix (#989)
This is starting point for setting up CI on PRs (they already run before
publishing pre-release or release versions).
This is also intended to test multiple Node versions _and_ Windows.
---
.github/workflows/ci.yml | 32 +++++++++++++
.../src/project-locator.test.ts | 13 +++--
.../src/testing/index.ts | 22 +++++++--
.../document-links/document-links.test.js | 47 ++++++-------------
.../tests/env/v4.test.js | 2 +-
5 files changed, 75 insertions(+), 41 deletions(-)
create mode 100644 .github/workflows/ci.yml
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..f64cfa03
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,32 @@
+name: Run Tests
+on:
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ tests:
+ strategy:
+ matrix:
+ node: [18, 20, 22, 23]
+ os: [ubuntu-latest, macos-latest, windows-latest]
+
+ runs-on: ${{ matrix.os }}
+ name: Run Tests - Node v${{ matrix.node }} / ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ cache: 'pnpm'
+ node-version: ${{ matrix.node-version }}
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Run tests
+ run: |
+ cd packages/tailwindcss-language-server &&
+ pnpm run build &&
+ pnpm run test
diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts
index f350dba1..52af6446 100644
--- a/packages/tailwindcss-language-server/src/project-locator.test.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.test.ts
@@ -5,6 +5,7 @@ import { URL, fileURLToPath } from 'url'
import { Settings } from '@tailwindcss/language-service/src/util/state'
import { createResolver } from './resolver'
import { css, defineTest, js, json, scss, Storage, TestUtils } from './testing'
+import { normalizePath } from './utils'
let settings: Settings = {
tailwindCSS: {
@@ -29,12 +30,14 @@ function testFixture(fixture: string, details: any[]) {
let detail = details[i]
- let configPath = path.relative(fixturePath, project.config.path)
+ let configPath = path.posix.relative(normalizePath(fixturePath), project.config.path)
expect(configPath).toEqual(detail?.config)
if (detail?.content) {
- let expected = detail?.content.map((path) => path.replace('{URL}', fixturePath)).sort()
+ let expected = detail?.content
+ .map((path) => path.replace('{URL}', normalizePath(fixturePath)))
+ .sort()
let actual = project.documentSelector
.filter((selector) => selector.priority === 1 /** content */)
@@ -45,7 +48,9 @@ function testFixture(fixture: string, details: any[]) {
}
if (detail?.selectors) {
- let expected = detail?.selectors.map((path) => path.replace('{URL}', fixturePath)).sort()
+ let expected = detail?.selectors
+ .map((path) => path.replace('{URL}', normalizePath(fixturePath)))
+ .sort()
let actual = project.documentSelector.map((selector) => selector.pattern).sort()
@@ -364,7 +369,7 @@ async function prepare({ root }: TestUtils) {
} as Settings
function adjustPath(filepath: string) {
- filepath = filepath.replace(root, '{URL}')
+ filepath = filepath.replace(normalizePath(root), '{URL}')
if (filepath.startsWith('{URL}/')) {
filepath = filepath.slice(5)
diff --git a/packages/tailwindcss-language-server/src/testing/index.ts b/packages/tailwindcss-language-server/src/testing/index.ts
index a0516d9a..91a07b81 100644
--- a/packages/tailwindcss-language-server/src/testing/index.ts
+++ b/packages/tailwindcss-language-server/src/testing/index.ts
@@ -1,4 +1,5 @@
import { onTestFinished, test, TestOptions } from 'vitest'
+import * as os from 'node:os'
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import * as proc from 'node:child_process'
@@ -12,6 +13,7 @@ export interface TestUtils {
export interface StorageSymlink {
[IS_A_SYMLINK]: true
filepath: string
+ type: 'file' | 'dir' | undefined
}
export interface Storage {
@@ -56,7 +58,13 @@ async function setup(config: TestConfig): Promise {
onTestFinished(async (result) => {
// Once done, move all the files to a new location
- await fs.rename(baseDir, doneDir)
+ try {
+ await fs.rename(baseDir, doneDir)
+ } catch {
+ // If it fails it doesn't really matter. It only fails on Windows and then
+ // only randomly so whatever
+ console.error('Failed to move test files to done directory')
+ }
if (result.state === 'fail') return
@@ -75,10 +83,11 @@ async function setup(config: TestConfig): Promise {
}
const IS_A_SYMLINK = Symbol('is-a-symlink')
-export function symlinkTo(filepath: string): StorageSymlink {
+export function symlinkTo(filepath: string, type?: 'file' | 'dir'): StorageSymlink {
return {
[IS_A_SYMLINK]: true as const,
filepath,
+ type,
}
}
@@ -93,7 +102,14 @@ async function prepareFileSystem(base: string, storage: Storage) {
if (typeof content === 'object' && IS_A_SYMLINK in content) {
let target = path.resolve(base, content.filepath)
- await fs.symlink(target, fullPath)
+
+ let type: string = content.type
+
+ if (os.platform() === 'win32' && content.type === 'dir') {
+ type = 'junction'
+ }
+
+ await fs.symlink(target, fullPath, type)
continue
}
diff --git a/packages/tailwindcss-language-server/tests/document-links/document-links.test.js b/packages/tailwindcss-language-server/tests/document-links/document-links.test.js
index 0fc76cb6..f187cc46 100644
--- a/packages/tailwindcss-language-server/tests/document-links/document-links.test.js
+++ b/packages/tailwindcss-language-server/tests/document-links/document-links.test.js
@@ -1,6 +1,7 @@
import { test } from 'vitest'
-import { withFixture } from '../common'
import * as path from 'path'
+import { URI } from 'vscode-uri'
+import { withFixture } from '../common'
withFixture('basic', (c) => {
async function testDocumentLinks(name, { text, lang, expected }) {
@@ -19,9 +20,7 @@ withFixture('basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/basic/tailwind.config.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/basic/tailwind.config.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 28 } },
},
],
@@ -32,9 +31,7 @@ withFixture('basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/basic/does-not-exist.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/basic/does-not-exist.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 27 } },
},
],
@@ -58,9 +55,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/tailwind.config.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/tailwind.config.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 28 } },
},
],
@@ -71,9 +66,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/does-not-exist.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/does-not-exist.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 27 } },
},
],
@@ -84,9 +77,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/plugin.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/plugin.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 19 } },
},
],
@@ -97,9 +88,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/does-not-exist.js')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/does-not-exist.js')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 27 } },
},
],
@@ -110,9 +99,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/index.html')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/index.html')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 20 } },
},
],
@@ -123,9 +110,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/does-not-exist.html')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/does-not-exist.html')).toString(),
range: { start: { line: 0, character: 8 }, end: { line: 0, character: 29 } },
},
],
@@ -136,9 +121,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/index.html')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/index.html')).toString(),
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 24 } },
},
],
@@ -149,9 +132,7 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path
- .resolve('./tests/fixtures/v4/basic/does-not-exist.html')
- .replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures/v4/basic/does-not-exist.html')).toString(),
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 33 } },
},
],
@@ -177,11 +158,11 @@ withFixture('v4/basic', (c) => {
lang: 'css',
expected: [
{
- target: `file://${path.resolve('./tests/fixtures').replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures')).toString(),
range: { start: { line: 1, character: 35 }, end: { line: 1, character: 43 } },
},
{
- target: `file://${path.resolve('./tests/fixtures').replace(/@/g, '%40')}`,
+ target: URI.file(path.resolve('./tests/fixtures')).toString(),
range: { start: { line: 2, character: 33 }, end: { line: 2, character: 41 } },
},
],
diff --git a/packages/tailwindcss-language-server/tests/env/v4.test.js b/packages/tailwindcss-language-server/tests/env/v4.test.js
index 36639f55..f57a45dc 100644
--- a/packages/tailwindcss-language-server/tests/env/v4.test.js
+++ b/packages/tailwindcss-language-server/tests/env/v4.test.js
@@ -737,7 +737,7 @@ defineTest({
presets: [require('some-pkg/config/tailwind.config.js').default]
}
`,
- 'packages/some-pkg': symlinkTo('packages/some-pkg#c3f1e'),
+ 'packages/some-pkg': symlinkTo('packages/some-pkg#c3f1e', 'dir'),
'packages/some-pkg#c3f1e/package.json': json`
{
"name": "some-pkg",
From 45f024a2a238c82231090bbabe9cda47ec12598b Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 27 Mar 2025 13:00:59 -0400
Subject: [PATCH 21/93] Update bundled CSS language service (#1286)
Fixes #1282
---
.github/workflows/ci.yml | 1 +
.../tailwindcss-language-server/package.json | 4 +--
.../tests/env/v4.test.js | 5 +++
.../tailwindcss-language-service/package.json | 2 +-
pnpm-lock.yaml | 36 +++++++++----------
5 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f64cfa03..af79a008 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,6 +7,7 @@ on:
jobs:
tests:
strategy:
+ fail-fast: false
matrix:
node: [18, 20, 22, 23]
os: [ubuntu-latest, macos-latest, windows-latest]
diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json
index d31549e7..8059ce0d 100644
--- a/packages/tailwindcss-language-server/package.json
+++ b/packages/tailwindcss-language-server/package.json
@@ -90,12 +90,12 @@
"typescript": "5.3.3",
"vite-tsconfig-paths": "^4.3.1",
"vitest": "^3.0.9",
- "vscode-css-languageservice": "6.2.9",
+ "vscode-css-languageservice": "6.3.3",
"vscode-jsonrpc": "8.2.0",
"vscode-languageclient": "8.1.0",
"vscode-languageserver": "8.1.0",
"vscode-languageserver-protocol": "^3.17.5",
- "vscode-languageserver-textdocument": "1.0.11",
+ "vscode-languageserver-textdocument": "1.0.12",
"vscode-uri": "3.0.2"
},
"engines": {
diff --git a/packages/tailwindcss-language-server/tests/env/v4.test.js b/packages/tailwindcss-language-server/tests/env/v4.test.js
index f57a45dc..2a6af3da 100644
--- a/packages/tailwindcss-language-server/tests/env/v4.test.js
+++ b/packages/tailwindcss-language-server/tests/env/v4.test.js
@@ -650,6 +650,11 @@ defineTest({
})
defineTest({
+ // This test sometimes takes a really long time on Windows because… Windows.
+ options: {
+ timeout: 30_000,
+ },
+
name: 'v3: Presets with a `#` in the name are loadable',
fs: {
'package.json': json`
diff --git a/packages/tailwindcss-language-service/package.json b/packages/tailwindcss-language-service/package.json
index 40ddba77..c77a08c7 100644
--- a/packages/tailwindcss-language-service/package.json
+++ b/packages/tailwindcss-language-service/package.json
@@ -37,7 +37,7 @@
"tmp-cache": "1.1.0",
"vscode-emmet-helper-bundled": "0.0.1",
"vscode-languageserver": "8.1.0",
- "vscode-languageserver-textdocument": "1.0.11"
+ "vscode-languageserver-textdocument": "1.0.12"
},
"devDependencies": {
"@types/braces": "3.0.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ab264604..071263e7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -201,8 +201,8 @@ importers:
specifier: ^3.0.9
version: 3.0.9(@types/node@18.19.43)
vscode-css-languageservice:
- specifier: 6.2.9
- version: 6.2.9
+ specifier: 6.3.3
+ version: 6.3.3
vscode-jsonrpc:
specifier: 8.2.0
version: 8.2.0
@@ -216,8 +216,8 @@ importers:
specifier: ^3.17.5
version: 3.17.5
vscode-languageserver-textdocument:
- specifier: 1.0.11
- version: 1.0.11
+ specifier: 1.0.12
+ version: 1.0.12
vscode-uri:
specifier: 3.0.2
version: 3.0.2
@@ -297,8 +297,8 @@ importers:
specifier: 8.1.0
version: 8.1.0
vscode-languageserver-textdocument:
- specifier: 1.0.11
- version: 1.0.11
+ specifier: 1.0.12
+ version: 1.0.12
devDependencies:
'@types/braces':
specifier: 3.0.1
@@ -1070,8 +1070,8 @@ packages:
'@vitest/utils@3.0.9':
resolution: {integrity: sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==}
- '@vscode/l10n@0.0.16':
- resolution: {integrity: sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg==}
+ '@vscode/l10n@0.0.18':
+ resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==}
'@vscode/vsce@2.21.1':
resolution: {integrity: sha512-f45/aT+HTubfCU2oC7IaWnH9NjOWp668ML002QiFObFRVUCoLtcwepp9mmql/ArFUy+HCHp54Xrq4koTcOD6TA==}
@@ -2654,8 +2654,8 @@ packages:
jsdom:
optional: true
- vscode-css-languageservice@6.2.9:
- resolution: {integrity: sha512-9MsOvAi+VycKomQ7KEq4o/hLtjHHrtRLLl8lM9nMcH8cxfNI7/6jVXmsV/7pdbDWu9L3DZhsspN1eMXZwiOymw==}
+ vscode-css-languageservice@6.3.3:
+ resolution: {integrity: sha512-xXa+ftMPv6JxRgzkvPwZuDCafIdwDW3kyijGcfij1a2qBVScr2qli6MfgJzYm/AMYdbHq9I/4hdpKV0Thim2EA==}
vscode-emmet-helper-bundled@0.0.1:
resolution: {integrity: sha512-EhZ0Wt8MbdrKF3NUMfaUDhFPTdRnl1tyqYS7KOcNtsSNTV285IV+XPDtNQyw5rwYsULEfb6n+fK1DRufJQlPYw==}
@@ -2689,8 +2689,8 @@ packages:
vscode-languageserver-protocol@3.17.5:
resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
- vscode-languageserver-textdocument@1.0.11:
- resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==}
+ vscode-languageserver-textdocument@1.0.12:
+ resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==}
vscode-languageserver-types@3.17.2:
resolution: {integrity: sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==}
@@ -3260,7 +3260,7 @@ snapshots:
loupe: 3.1.3
tinyrainbow: 2.0.0
- '@vscode/l10n@0.0.16': {}
+ '@vscode/l10n@0.0.18': {}
'@vscode/vsce@2.21.1':
dependencies:
@@ -4900,11 +4900,11 @@ snapshots:
- supports-color
- terser
- vscode-css-languageservice@6.2.9:
+ vscode-css-languageservice@6.3.3:
dependencies:
- '@vscode/l10n': 0.0.16
- vscode-languageserver-textdocument: 1.0.11
- vscode-languageserver-types: 3.17.3
+ '@vscode/l10n': 0.0.18
+ vscode-languageserver-textdocument: 1.0.12
+ vscode-languageserver-types: 3.17.5
vscode-uri: 3.0.8
vscode-emmet-helper-bundled@0.0.1: {}
@@ -4942,7 +4942,7 @@ snapshots:
vscode-jsonrpc: 8.2.0
vscode-languageserver-types: 3.17.5
- vscode-languageserver-textdocument@1.0.11: {}
+ vscode-languageserver-textdocument@1.0.12: {}
vscode-languageserver-types@3.17.2: {}
From a8418d39a46845accdfcce4014aca9c843c0a0a1 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 27 Mar 2025 13:30:08 -0400
Subject: [PATCH 22/93] Fix test timeout
Looks like I added this to the wrong test
---
.../tailwindcss-language-server/tests/env/v4.test.js | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/tailwindcss-language-server/tests/env/v4.test.js b/packages/tailwindcss-language-server/tests/env/v4.test.js
index 2a6af3da..01710786 100644
--- a/packages/tailwindcss-language-server/tests/env/v4.test.js
+++ b/packages/tailwindcss-language-server/tests/env/v4.test.js
@@ -650,11 +650,6 @@ defineTest({
})
defineTest({
- // This test sometimes takes a really long time on Windows because… Windows.
- options: {
- timeout: 30_000,
- },
-
name: 'v3: Presets with a `#` in the name are loadable',
fs: {
'package.json': json`
@@ -712,6 +707,11 @@ defineTest({
})
defineTest({
+ // This test sometimes takes a really long time on Windows because… Windows.
+ options: {
+ timeout: 30_000,
+ },
+
// This test *always* passes inside Vitest because our custom version of
// `Module._resolveFilename` is not called. Our custom implementation is
// using enhanced-resolve under the hood which is affected by the `#`
From c6a602ababe4a693f7248c90b53d14a3b1d22694 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 27 Mar 2025 14:44:31 -0400
Subject: [PATCH 23/93] Make Windows tests more stable
Windows just takes way too long sometimes
---
.../tests/env/v4.test.js | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/packages/tailwindcss-language-server/tests/env/v4.test.js b/packages/tailwindcss-language-server/tests/env/v4.test.js
index 01710786..632eb1a2 100644
--- a/packages/tailwindcss-language-server/tests/env/v4.test.js
+++ b/packages/tailwindcss-language-server/tests/env/v4.test.js
@@ -605,6 +605,12 @@ defineTest({
})
defineTest({
+ // This test sometimes takes a really long time on Windows because… Windows.
+ options: {
+ retry: 3,
+ timeout: 30_000,
+ },
+
name: 'Plugins with a `#` in the name are loadable',
fs: {
'app.css': css`
@@ -650,6 +656,12 @@ defineTest({
})
defineTest({
+ // This test sometimes takes a really long time on Windows because… Windows.
+ options: {
+ retry: 3,
+ timeout: 30_000,
+ },
+
name: 'v3: Presets with a `#` in the name are loadable',
fs: {
'package.json': json`
@@ -709,6 +721,7 @@ defineTest({
defineTest({
// This test sometimes takes a really long time on Windows because… Windows.
options: {
+ retry: 3,
timeout: 30_000,
},
From 5f74714b61b4f14838400761deeb50e6f1eccdee Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 27 Mar 2025 15:10:24 -0400
Subject: [PATCH 24/93] Prep for new Oxide API in v4.1 (#1284)
---
.../tailwindcss-language-server/src/oxide.ts | 87 +++++--
.../src/project-locator.test.ts | 167 ++++++++++----
.../src/project-locator.ts | 7 +-
.../v4/auto-content-split/package-lock.json | 217 ------------------
.../v4/auto-content-split/package.json | 6 -
.../v4/auto-content-split/src/app.css | 3 -
.../src/components/example.html | 1 -
.../v4/auto-content-split/src/index.html | 1 -
.../v4/auto-content/package-lock.json | 217 ------------------
.../fixtures/v4/auto-content/package.json | 6 -
.../fixtures/v4/auto-content/src/app.css | 1 -
.../auto-content/src/components/example.html | 1 -
.../fixtures/v4/auto-content/src/index.html | 1 -
.../fixtures/v4/custom-source/admin/app.css | 2 -
.../fixtures/v4/custom-source/admin/foo.bin | 1 -
.../fixtures/v4/custom-source/admin/tw.css | 2 -
.../fixtures/v4/custom-source/admin/ui.css | 3 -
.../v4/custom-source/package-lock.json | 18 --
.../fixtures/v4/custom-source/package.json | 5 -
.../fixtures/v4/custom-source/shared.html | 1 -
.../fixtures/v4/custom-source/web/app.css | 2 -
.../fixtures/v4/custom-source/web/bar.bin | 1 -
22 files changed, 191 insertions(+), 559 deletions(-)
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package-lock.json
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package.json
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/app.css
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/components/example.html
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/index.html
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package-lock.json
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package.json
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/app.css
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/components/example.html
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/index.html
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/app.css
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/foo.bin
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/tw.css
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/ui.css
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package-lock.json
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package.json
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/shared.html
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/web/app.css
delete mode 100644 packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/web/bar.bin
diff --git a/packages/tailwindcss-language-server/src/oxide.ts b/packages/tailwindcss-language-server/src/oxide.ts
index 5dad7553..4dd529df 100644
--- a/packages/tailwindcss-language-server/src/oxide.ts
+++ b/packages/tailwindcss-language-server/src/oxide.ts
@@ -36,8 +36,8 @@ declare namespace OxideV2 {
}
}
-// This covers the Oxide API from v4.0.0-alpha.20+
-declare namespace OxideV3 {
+// This covers the Oxide API from v4.0.0-alpha.30+
+declare namespace OxideV3And4 {
interface GlobEntry {
base: string
pattern: string
@@ -58,10 +58,37 @@ declare namespace OxideV3 {
}
}
+// This covers the Oxide API from v4.1.0+
+declare namespace OxideV5 {
+ interface GlobEntry {
+ base: string
+ pattern: string
+ }
+
+ interface SourceEntry {
+ base: string
+ pattern: string
+ negated: boolean
+ }
+
+ interface ScannerOptions {
+ sources: Array
+ }
+
+ interface ScannerConstructor {
+ new (options: ScannerOptions): Scanner
+ }
+
+ interface Scanner {
+ get files(): Array
+ get globs(): Array
+ }
+}
+
interface Oxide {
scanDir?(options: OxideV1.ScanOptions): OxideV1.ScanResult
scanDir?(options: OxideV2.ScanOptions): OxideV2.ScanResult
- Scanner?: OxideV3.ScannerConstructor
+ Scanner?: OxideV3And4.ScannerConstructor | OxideV5.ScannerConstructor
}
async function loadOxideAtPath(id: string): Promise {
@@ -78,11 +105,17 @@ interface GlobEntry {
pattern: string
}
+interface SourceEntry {
+ base: string
+ pattern: string
+ negated: boolean
+}
+
interface ScanOptions {
oxidePath: string
oxideVersion: string
basePath: string
- sources: Array
+ sources: Array
}
interface ScanResult {
@@ -118,38 +151,58 @@ export async function scan(options: ScanOptions): Promise {
}
// V2
- if (lte(options.oxideVersion, '4.0.0-alpha.19')) {
+ else if (lte(options.oxideVersion, '4.0.0-alpha.19')) {
let result = oxide.scanDir({
base: options.basePath,
- sources: options.sources,
+ sources: options.sources.map((g) => ({ base: g.base, pattern: g.pattern })),
})
return {
files: result.files,
- globs: result.globs,
+ globs: result.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
}
}
// V3
- if (lte(options.oxideVersion, '4.0.0-alpha.30')) {
- let scanner = new oxide.Scanner({
+ else if (lte(options.oxideVersion, '4.0.0-alpha.30')) {
+ let scanner = new (oxide.Scanner as OxideV3And4.ScannerConstructor)({
detectSources: { base: options.basePath },
- sources: options.sources,
+ sources: options.sources.map((g) => ({ base: g.base, pattern: g.pattern })),
})
return {
files: scanner.files,
- globs: scanner.globs,
+ globs: scanner.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
}
}
// V4
- let scanner = new oxide.Scanner({
- sources: [{ base: options.basePath, pattern: '**/*' }, ...options.sources],
- })
+ else if (lte(options.oxideVersion, '4.0.9999')) {
+ let scanner = new (oxide.Scanner as OxideV3And4.ScannerConstructor)({
+ sources: [
+ { base: options.basePath, pattern: '**/*' },
+ ...options.sources.map((g) => ({ base: g.base, pattern: g.pattern })),
+ ],
+ })
- return {
- files: scanner.files,
- globs: scanner.globs,
+ return {
+ files: scanner.files,
+ globs: scanner.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
+ }
+ }
+
+ // V5
+ else {
+ let scanner = new (oxide.Scanner as OxideV5.ScannerConstructor)({
+ sources: [
+ { base: options.basePath, pattern: '**/*', negated: false },
+ ...options.sources.map((g) => ({ base: g.base, pattern: g.pattern, negated: g.negated })),
+ ],
+ })
+
+ return {
+ files: scanner.files,
+ globs: scanner.globs.map((g) => ({ base: g.base, pattern: g.pattern })),
+ }
}
}
diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts
index 52af6446..ae3598e5 100644
--- a/packages/tailwindcss-language-server/src/project-locator.test.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.test.ts
@@ -4,7 +4,7 @@ import { ProjectLocator } from './project-locator'
import { URL, fileURLToPath } from 'url'
import { Settings } from '@tailwindcss/language-service/src/util/state'
import { createResolver } from './resolver'
-import { css, defineTest, js, json, scss, Storage, TestUtils } from './testing'
+import { css, defineTest, html, js, json, scss, Storage, symlinkTo, TestUtils } from './testing'
import { normalizePath } from './utils'
let settings: Settings = {
@@ -141,58 +141,129 @@ testFixture('v4/workspaces', [
},
])
-testFixture('v4/auto-content', [
- //
- {
- config: 'src/app.css',
- content: [
- '{URL}/*',
- '{URL}/package.json',
- '{URL}/src/index.html',
- '{URL}/src/components/example.html',
- '{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}',
- ],
+testLocator({
+ name: 'automatic content detection with Oxide',
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "^4.0.15",
+ "@tailwindcss/oxide": "^4.0.15"
+ }
+ }
+ `,
+ 'src/index.html': html`Test
`,
+ 'src/app.css': css`
+ @import 'tailwindcss';
+ `,
+ 'src/components/example.html': html`Test
`,
},
-])
+ expected: [
+ {
+ config: '/src/app.css',
+ 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/components/example.html',
+ '/src/index.html',
+ ],
+ },
+ ],
+})
-testFixture('v4/auto-content-split', [
- {
- config: 'src/app.css',
- content: [
- '{URL}/*',
- '{URL}/package.json',
- '{URL}/src/index.html',
- '{URL}/src/components/example.html',
- '{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}',
- ],
+testLocator({
+ name: 'automatic content detection with Oxide using split config',
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "^4.0.15",
+ "@tailwindcss/oxide": "^4.0.15"
+ }
+ }
+ `,
+ 'src/index.html': html`Test
`,
+ 'src/app.css': css`
+ @import 'tailwindcss/preflight' layer(base);
+ @import 'tailwindcss/theme' layer(theme);
+ @import 'tailwindcss/utilities' layer(utilities);
+ `,
+ 'src/components/example.html': html`Test
`,
},
-])
+ expected: [
+ {
+ config: '/src/app.css',
+ 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/components/example.html',
+ '/src/index.html',
+ ],
+ },
+ ],
+})
-testFixture('v4/custom-source', [
- //
- {
- config: 'admin/app.css',
- content: [
- '{URL}/*',
- '{URL}/admin/foo.bin',
- '{URL}/admin/{**/*.bin,**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}}',
- '{URL}/package.json',
- '{URL}/shared.html',
- '{URL}/web/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
- ],
- },
- {
- config: 'web/app.css',
- content: [
- '{URL}/*',
- '{URL}/admin/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}',
- '{URL}/package.json',
- '{URL}/shared.html',
- '{URL}/web/bar.bin',
- '{URL}/web/{**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue},*.bin}',
- ],
+testLocator({
+ name: 'automatic content detection with custom sources',
+ fs: {
+ 'package.json': json`
+ {
+ "dependencies": {
+ "tailwindcss": "^4.0.15",
+ "@tailwindcss/oxide": "^4.0.15"
+ }
+ }
+ `,
+ 'admin/app.css': css`
+ @import './tw.css';
+ @import './ui.css';
+ `,
+ 'admin/tw.css': css`
+ @import 'tailwindcss';
+ @source './**/*.bin';
+ `,
+ 'admin/ui.css': css`
+ @theme {
+ --color-potato: #907a70;
+ }
+ `,
+ 'admin/foo.bin': html`Admin
`,
+
+ 'web/app.css': css`
+ @import 'tailwindcss';
+ @source './*.bin';
+ `,
+ 'web/bar.bin': html`Web
`,
+
+ 'shared.html': html`I belong to no one!
`,
},
-])
+ expected: [
+ {
+ config: '/admin/app.css',
+ 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}}',
+ '/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}',
+ ],
+ },
+ {
+ 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}',
+ '/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}',
+ ],
+ },
+ ],
+})
testFixture('v4/missing-files', [
//
diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts
index 460b1423..c75ae1de 100644
--- a/packages/tailwindcss-language-server/src/project-locator.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.ts
@@ -627,12 +627,9 @@ async function* detectContentFiles(
resolver: Resolver,
): AsyncIterable {
try {
- let oxidePath = await resolver.resolveJsId('@tailwindcss/oxide', path.dirname(base))
+ let oxidePath = await resolver.resolveJsId('@tailwindcss/oxide', base)
oxidePath = pathToFileURL(oxidePath).href
- let oxidePackageJsonPath = await resolver.resolveJsId(
- '@tailwindcss/oxide/package.json',
- path.dirname(base),
- )
+ let oxidePackageJsonPath = await resolver.resolveJsId('@tailwindcss/oxide/package.json', base)
let oxidePackageJson = JSON.parse(await fs.readFile(oxidePackageJsonPath, 'utf8'))
let result = await oxide.scan({
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package-lock.json
deleted file mode 100644
index b829f613..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package-lock.json
+++ /dev/null
@@ -1,217 +0,0 @@
-{
- "name": "auto-content-split",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "dependencies": {
- "@tailwindcss/oxide": "^4.0.15",
- "tailwindcss": "^4.0.15"
- }
- },
- "node_modules/@tailwindcss/oxide": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.15.tgz",
- "integrity": "sha512-e0uHrKfPu7JJGMfjwVNyt5M0u+OP8kUmhACwIRlM+JNBuReDVQ63yAD1NWe5DwJtdaHjugNBil76j+ks3zlk6g==",
- "license": "MIT",
- "engines": {
- "node": ">= 10"
- },
- "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"
- }
- },
- "node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.15.tgz",
- "integrity": "sha512-EBuyfSKkom7N+CB3A+7c0m4+qzKuiN0WCvzPvj5ZoRu4NlQadg/mthc1tl5k9b5ffRGsbDvP4k21azU4VwVk3Q==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.15.tgz",
- "integrity": "sha512-ObVAnEpLepMhV9VoO0JSit66jiN5C4YCqW3TflsE9boo2Z7FIjV80RFbgeL2opBhtxbaNEDa6D0/hq/EP03kgQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.15.tgz",
- "integrity": "sha512-IElwoFhUinOr9MyKmGTPNi1Rwdh68JReFgYWibPWTGuevkHkLWKEflZc2jtI5lWZ5U9JjUnUfnY43I4fEXrc4g==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.15.tgz",
- "integrity": "sha512-6BLLqyx7SIYRBOnTZ8wgfXANLJV5TQd3PevRJZp0vn42eO58A2LykRKdvL1qyPfdpmEVtF+uVOEZ4QTMqDRAWA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.15.tgz",
- "integrity": "sha512-Zy63EVqO9241Pfg6G0IlRIWyY5vNcWrL5dd2WAKVJZRQVeolXEf1KfjkyeAAlErDj72cnyXObEZjMoPEKHpdNw==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.15.tgz",
- "integrity": "sha512-2NemGQeaTbtIp1Z2wyerbVEJZTkAWhMDOhhR5z/zJ75yMNf8yLnE+sAlyf6yGDNr+1RqvWrRhhCFt7i0CIxe4Q==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.15.tgz",
- "integrity": "sha512-342GVnhH/6PkVgKtEzvNVuQ4D+Q7B7qplvuH20Cfz9qEtydG6IQczTZ5IT4JPlh931MG1NUCVxg+CIorr1WJyw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.15.tgz",
- "integrity": "sha512-g76GxlKH124RuGqacCEFc2nbzRl7bBrlC8qDQMiUABkiifDRHOIUjgKbLNG4RuR9hQAD/MKsqZ7A8L08zsoBrw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.15.tgz",
- "integrity": "sha512-Gg/Y1XrKEvKpq6WeNt2h8rMIKOBj/W3mNa5NMvkQgMC7iO0+UNLrYmt6zgZufht66HozNpn+tJMbbkZ5a3LczA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.15.tgz",
- "integrity": "sha512-7QtSSJwYZ7ZK1phVgcNZpuf7c7gaCj8Wb0xjliligT5qCGCp79OV2n3SJummVZdw4fbTNKUOYMO7m1GinppZyA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.15.tgz",
- "integrity": "sha512-JQ5H+5MLhOjpgNp6KomouE0ZuKmk3hO5h7/ClMNAQ8gZI2zkli3IH8ZqLbd2DVfXDbdxN2xvooIEeIlkIoSCqw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "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==",
- "license": "MIT"
- }
- }
-}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package.json
deleted file mode 100644
index 908f1d2f..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "dependencies": {
- "tailwindcss": "^4.0.15",
- "@tailwindcss/oxide": "^4.0.15"
- }
-}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/app.css b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/app.css
deleted file mode 100644
index 7b4dcc31..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/app.css
+++ /dev/null
@@ -1,3 +0,0 @@
-@import "tailwindcss/preflight" layer(base);
-@import "tailwindcss/theme" layer(theme);
-@import "tailwindcss/utilities" layer(utilities);
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/components/example.html b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/components/example.html
deleted file mode 100644
index 788b45c9..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/components/example.html
+++ /dev/null
@@ -1 +0,0 @@
-Test
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/index.html b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/index.html
deleted file mode 100644
index a108a449..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content-split/src/index.html
+++ /dev/null
@@ -1 +0,0 @@
-Test
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package-lock.json
deleted file mode 100644
index 6b32300e..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package-lock.json
+++ /dev/null
@@ -1,217 +0,0 @@
-{
- "name": "auto-content",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "dependencies": {
- "@tailwindcss/oxide": "^4.0.15",
- "tailwindcss": "^4.0.15"
- }
- },
- "node_modules/@tailwindcss/oxide": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.15.tgz",
- "integrity": "sha512-e0uHrKfPu7JJGMfjwVNyt5M0u+OP8kUmhACwIRlM+JNBuReDVQ63yAD1NWe5DwJtdaHjugNBil76j+ks3zlk6g==",
- "license": "MIT",
- "engines": {
- "node": ">= 10"
- },
- "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"
- }
- },
- "node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.15.tgz",
- "integrity": "sha512-EBuyfSKkom7N+CB3A+7c0m4+qzKuiN0WCvzPvj5ZoRu4NlQadg/mthc1tl5k9b5ffRGsbDvP4k21azU4VwVk3Q==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.15.tgz",
- "integrity": "sha512-ObVAnEpLepMhV9VoO0JSit66jiN5C4YCqW3TflsE9boo2Z7FIjV80RFbgeL2opBhtxbaNEDa6D0/hq/EP03kgQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.15.tgz",
- "integrity": "sha512-IElwoFhUinOr9MyKmGTPNi1Rwdh68JReFgYWibPWTGuevkHkLWKEflZc2jtI5lWZ5U9JjUnUfnY43I4fEXrc4g==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.15.tgz",
- "integrity": "sha512-6BLLqyx7SIYRBOnTZ8wgfXANLJV5TQd3PevRJZp0vn42eO58A2LykRKdvL1qyPfdpmEVtF+uVOEZ4QTMqDRAWA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.15.tgz",
- "integrity": "sha512-Zy63EVqO9241Pfg6G0IlRIWyY5vNcWrL5dd2WAKVJZRQVeolXEf1KfjkyeAAlErDj72cnyXObEZjMoPEKHpdNw==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.15.tgz",
- "integrity": "sha512-2NemGQeaTbtIp1Z2wyerbVEJZTkAWhMDOhhR5z/zJ75yMNf8yLnE+sAlyf6yGDNr+1RqvWrRhhCFt7i0CIxe4Q==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.15.tgz",
- "integrity": "sha512-342GVnhH/6PkVgKtEzvNVuQ4D+Q7B7qplvuH20Cfz9qEtydG6IQczTZ5IT4JPlh931MG1NUCVxg+CIorr1WJyw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.15.tgz",
- "integrity": "sha512-g76GxlKH124RuGqacCEFc2nbzRl7bBrlC8qDQMiUABkiifDRHOIUjgKbLNG4RuR9hQAD/MKsqZ7A8L08zsoBrw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.15.tgz",
- "integrity": "sha512-Gg/Y1XrKEvKpq6WeNt2h8rMIKOBj/W3mNa5NMvkQgMC7iO0+UNLrYmt6zgZufht66HozNpn+tJMbbkZ5a3LczA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.15.tgz",
- "integrity": "sha512-7QtSSJwYZ7ZK1phVgcNZpuf7c7gaCj8Wb0xjliligT5qCGCp79OV2n3SJummVZdw4fbTNKUOYMO7m1GinppZyA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.0.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.15.tgz",
- "integrity": "sha512-JQ5H+5MLhOjpgNp6KomouE0ZuKmk3hO5h7/ClMNAQ8gZI2zkli3IH8ZqLbd2DVfXDbdxN2xvooIEeIlkIoSCqw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "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==",
- "license": "MIT"
- }
- }
-}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package.json
deleted file mode 100644
index 908f1d2f..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "dependencies": {
- "tailwindcss": "^4.0.15",
- "@tailwindcss/oxide": "^4.0.15"
- }
-}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/app.css b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/app.css
deleted file mode 100644
index d4b50785..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/app.css
+++ /dev/null
@@ -1 +0,0 @@
-@import 'tailwindcss';
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/components/example.html b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/components/example.html
deleted file mode 100644
index 788b45c9..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/components/example.html
+++ /dev/null
@@ -1 +0,0 @@
-Test
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/index.html b/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/index.html
deleted file mode 100644
index a108a449..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/auto-content/src/index.html
+++ /dev/null
@@ -1 +0,0 @@
-Test
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/app.css b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/app.css
deleted file mode 100644
index 41dcb5f8..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/app.css
+++ /dev/null
@@ -1,2 +0,0 @@
-@import './tw.css';
-@import './ui.css';
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/foo.bin b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/foo.bin
deleted file mode 100644
index ec56c434..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/foo.bin
+++ /dev/null
@@ -1 +0,0 @@
-Admin
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/tw.css b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/tw.css
deleted file mode 100644
index 9c0a7919..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/tw.css
+++ /dev/null
@@ -1,2 +0,0 @@
-@import 'tailwindcss';
-@source './**/*.bin';
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/ui.css b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/ui.css
deleted file mode 100644
index 24ea64d2..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/ui.css
+++ /dev/null
@@ -1,3 +0,0 @@
-@theme {
- --color-potato: #907a70;
-}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package-lock.json b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package-lock.json
deleted file mode 100644
index ce004f1d..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package-lock.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "custom-source",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "dependencies": {
- "tailwindcss": "^4.0.15"
- }
- },
- "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==",
- "license": "MIT"
- }
- }
-}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package.json b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package.json
deleted file mode 100644
index b6cb53b1..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "dependencies": {
- "tailwindcss": "^4.0.15"
- }
-}
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/shared.html b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/shared.html
deleted file mode 100644
index 49b293bf..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/shared.html
+++ /dev/null
@@ -1 +0,0 @@
-I belong to no one!
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/web/app.css b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/web/app.css
deleted file mode 100644
index 9357eb02..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/web/app.css
+++ /dev/null
@@ -1,2 +0,0 @@
-@import 'tailwindcss';
-@source './*.bin';
diff --git a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/web/bar.bin b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/web/bar.bin
deleted file mode 100644
index 56ee61a2..00000000
--- a/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/web/bar.bin
+++ /dev/null
@@ -1 +0,0 @@
-Web
From 91970d58ef27f7e260ce4ceea10a9475bdc96919 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 27 Mar 2025 15:44:27 -0400
Subject: [PATCH 25/93] Handle negated sources during project discovery (#1288)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
I forgot to add support for reading `@source not` in #1284
Also realized I forgot to add the `negated` field for `@source` globs.
I'd already written the test — just not tested it with the new version
like I did the others 🤦♂️
---
.../src/css/extract-source-directives.ts | 13 +++++--
.../src/project-locator.test.ts | 34 +++++++++++++++++++
.../src/project-locator.ts | 14 +++++---
3 files changed, 55 insertions(+), 6 deletions(-)
diff --git a/packages/tailwindcss-language-server/src/css/extract-source-directives.ts b/packages/tailwindcss-language-server/src/css/extract-source-directives.ts
index 9de33a9b..a97e3559 100644
--- a/packages/tailwindcss-language-server/src/css/extract-source-directives.ts
+++ b/packages/tailwindcss-language-server/src/css/extract-source-directives.ts
@@ -1,12 +1,21 @@
import type { Plugin } from 'postcss'
+import type { SourcePattern } from '../project-locator'
-export function extractSourceDirectives(sources: string[]): Plugin {
+export function extractSourceDirectives(sources: SourcePattern[]): Plugin {
return {
postcssPlugin: 'extract-at-rules',
AtRule: {
source: ({ params }) => {
+ let negated = /^not\s+/.test(params)
+
+ if (negated) params = params.slice(4).trimStart()
+
if (params[0] !== '"' && params[0] !== "'") return
- sources.push(params.slice(1, -1))
+
+ sources.push({
+ pattern: params.slice(1, -1),
+ negated,
+ })
},
},
}
diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts
index ae3598e5..429f0b27 100644
--- a/packages/tailwindcss-language-server/src/project-locator.test.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.test.ts
@@ -265,6 +265,40 @@ testLocator({
],
})
+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"
+ }
+ }
+ `,
+ 'src/app.css': css`
+ @import 'tailwindcss';
+ @source './**/*.html';
+ @source not './ignored.html';
+ `,
+ 'src/index.html': html``,
+ 'src/ignored.html': html``,
+ },
+ expected: [
+ {
+ config: '/src/app.css',
+ content: [
+ '/*',
+ '/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}}',
+ ],
+ },
+ ],
+})
+
testFixture('v4/missing-files', [
//
{
diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts
index c75ae1de..c04738bf 100644
--- a/packages/tailwindcss-language-server/src/project-locator.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.ts
@@ -623,7 +623,7 @@ async function* contentSelectorsFromCssConfig(
async function* detectContentFiles(
base: string,
inputFile: string,
- sources: string[],
+ sources: SourcePattern[],
resolver: Resolver,
): AsyncIterable {
try {
@@ -636,9 +636,10 @@ async function* detectContentFiles(
oxidePath,
oxideVersion: oxidePackageJson.version,
basePath: base,
- sources: sources.map((pattern) => ({
+ sources: sources.map((source) => ({
base: path.dirname(inputFile),
- pattern,
+ pattern: source.pattern,
+ negated: source.negated,
})),
})
@@ -672,11 +673,16 @@ type ConfigEntry = {
content: ContentItem[]
}
+export interface SourcePattern {
+ pattern: string
+ negated: boolean
+}
+
class FileEntry {
content: string | null
deps: FileEntry[] = []
realpath: string | null
- sources: string[] = []
+ sources: SourcePattern[] = []
meta: TailwindStylesheet | null = null
constructor(
From 285d70d3b2aac6f9251f9eb85a48f8e3c0948eae Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 27 Mar 2025 15:45:54 -0400
Subject: [PATCH 26/93] Update changelog
---
packages/vscode-tailwindcss/CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index de81de55..9a2ce434 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -4,6 +4,8 @@
- Fix content detection when using v4.0+ ([#1280](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1280))
- Ensure file exclusions always work on Windows ([#1281](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1281))
+- Prep for new Oxide API in v4.1 ([#1284](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1284))
+- Handle negated sources during project discovery in v4.1 ([#1288](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1288))
# 0.14.11
From 3542626c74049e200b4ce9ed035a89864da95a17 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Fri, 28 Mar 2025 16:37:10 -0400
Subject: [PATCH 27/93] 0.14.12
---
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 8059ce0d..dd812e3c 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.11",
+ "version": "0.14.12",
"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 c77a08c7..48fae16e 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.11",
+ "version": "0.14.12",
"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 9a2ce434..31a8ca66 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,6 +2,10 @@
## Prerelease
+- Nothing yet!
+
+# 0.14.12
+
- Fix content detection when using v4.0+ ([#1280](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1280))
- Ensure file exclusions always work on Windows ([#1281](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1281))
- Prep for new Oxide API in v4.1 ([#1284](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1284))
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 038c9133..26af65d3 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "vscode-tailwindcss",
- "version": "0.14.11",
+ "version": "0.14.12",
"displayName": "Tailwind CSS IntelliSense",
"description": "Intelligent Tailwind CSS tooling for VS Code",
"author": "Brad Cornes ",
From 6b306ba89b75a26bd420c61582242bb8251515df Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 1 Apr 2025 10:22:18 -0400
Subject: [PATCH 28/93] =?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:
after:
---
.../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 a43910b2..73e967fc 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 e8970e08..388e94af 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 31a8ca66..82478125 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 29/93] 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 dd812e3c..d96255dd 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 429f0b27..3699c840 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 05d2ecd3..f312b95c 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 5016bacc..4780a4fb 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 dbe9a352..feb4999f 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 632eb1a2..dc33c79f 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 6275c3dc..ab6be977 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 b6cb53b1..43b975c9 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 5089dc65..b92fb848 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 b6cb53b1..43b975c9 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 555ee660..f4352dc6 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 b6cb53b1..43b975c9 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 24d978d8..0a12b281 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 b6cb53b1..43b975c9 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 ed4d2d9a..c95b25d7 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 b6cb53b1..43b975c9 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 c4664645..58a06cee 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 b6cb53b1..43b975c9 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 651bf7c9..e6cc18e9 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 b6cb53b1..43b975c9 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 1e5486ae..34a2cc13 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 b6cb53b1..43b975c9 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 b622b7b7..a88643ba 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 aa5e54db..9291956a 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 ac97e414..37377340 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 4b0d3b84..a1a99d66 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 728b53bf..91dcc91d 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 82478125..421543f5 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 071263e7..e879d3a6 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 30/93] 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 37377340..379f4199 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 d20655e3..76864281 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 1db68bed..583cc80f 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 03218798..9118403d 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 119e5f59..8c819eb8 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 421543f5..d55074be 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 31/93] 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 d96255dd..7d142c90 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 48fae16e..23907838 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 d55074be..6a8c2dac 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 26af65d3..4f55fb4a 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 32/93] 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 feb4999f..ce5c7831 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 5c0c5552..843e9a8e 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 c30e729a..823b20f8 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 33/93] 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 6a8c2dac..661c8d17 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 00000000..ec9b2b9a
--- /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 00000000..d7f67de6
--- /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 00000000..46ffd599
--- /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 a3748616..467d4e08 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 2332990e96e7df3836fd7dee2d1c94d6b162df96 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Mon, 7 Apr 2025 13:13:28 -0400
Subject: [PATCH 34/93] Don't follow recursive symlinks during project
discovery (#1270)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Project discovery is done by crawling the workspace with `fast-glob`
looking for CSS files, JS/TS config files, Sass/Less files, etc…
A major problem with this approach though is that `fast-glob` crawls the
filesystem and traverses into symlinked directories. If the symlink
points back to a parent folder the crawler may cause a handful of
issues:
- An infinite traversal loop
- Out of memory crashes (causing the server to restart repeatedly)
- A rather long discovery time (10s+)
- Errors because of file path length limits (esp on Windows)
I've rewritten the traversal to use `tinyglobby` which is a
`fast-glob`-compatible replacement. It avoids all of these issues due to
its use of `fdir` which explicitly detects and avoids traversal of
recursive symlinks.
All existing tests pass as well as an additional test that previously
timed out due to the use of recursive symlinks. This PR is going to
require extensive testing on Windows with network shares, mapped drives,
etc…
Fixes #1056
Fixes #1185
---
.../tailwindcss-language-server/package.json | 2 +-
.../src/project-locator.test.ts | 34 +++++++++++++
.../src/project-locator.ts | 9 ++--
.../tests/prepare.mjs | 12 +++--
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
pnpm-lock.yaml | 49 +++++++++----------
6 files changed, 72 insertions(+), 35 deletions(-)
diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json
index 7d142c90..c4dc2984 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.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts
index 3699c840..f43479ae 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', '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', 'dir'),
+ 'src/b/c/d/index.css': css``,
+ '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: [
+ {
+ version: '4.1.1 (bundled)',
+ config: '/src/a/b/c/index.css',
+ content: [],
+ },
+ ],
+})
+
// ---
function testLocator({
diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts
index c04738bf..de562b63 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 19b2c6d5..c529e630 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/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 661c8d17..1750eb69 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
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e879d3a6..c4e0edaf 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 0610bf4df2170b23a4a4c14dfabf1ccff8804cc7 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Mon, 7 Apr 2025 14:03:51 -0400
Subject: [PATCH 35/93] Handle when project config is re-created (#1300)
If a project's main CSS config file is deleted and then re-created the
server won't re-create the project.
For now we'll restart the server when events like this happen which will
ensure that the filesystem is re-scanned and the files can be picked up
again.
---
.../src/projects.ts | 5 +
.../src/testing/index.ts | 42 +++--
.../tailwindcss-language-server/src/tw.ts | 44 ++++-
.../tests/env/restart.test.ts | 169 ++++++++++++++++++
.../tests/utils/client.ts | 8 +-
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
6 files changed, 253 insertions(+), 16 deletions(-)
create mode 100644 packages/tailwindcss-language-server/tests/env/restart.test.ts
diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts
index a8012920..25aacd5f 100644
--- a/packages/tailwindcss-language-server/src/projects.ts
+++ b/packages/tailwindcss-language-server/src/projects.ts
@@ -1164,6 +1164,11 @@ export async function createProjectService(
let elapsed = process.hrtime.bigint() - start
console.log(`---- RELOADED IN ${(Number(elapsed) / 1e6).toFixed(2)}ms ----`)
+
+ let isTestMode = params.initializationOptions?.testMode ?? false
+ if (!isTestMode) return
+
+ connection.sendNotification('@/tailwindCSS/projectReloaded')
},
state,
diff --git a/packages/tailwindcss-language-server/src/testing/index.ts b/packages/tailwindcss-language-server/src/testing/index.ts
index 91a07b81..0a11bb9d 100644
--- a/packages/tailwindcss-language-server/src/testing/index.ts
+++ b/packages/tailwindcss-language-server/src/testing/index.ts
@@ -1,13 +1,20 @@
-import { onTestFinished, test, TestOptions } from 'vitest'
+import { onTestFinished, test, TestContext, TestOptions } from 'vitest'
import * as os from 'node:os'
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import * as proc from 'node:child_process'
import dedent from 'dedent'
-export interface TestUtils {
+export interface TestUtils> {
/** The "cwd" for this test */
root: string
+
+ /**
+ * The input for this test — taken from the `inputs` in the test config
+ *
+ * @see {TestConfig}
+ */
+ input?: TestInput
}
export interface StorageSymlink {
@@ -21,29 +28,39 @@ export interface Storage {
[filePath: string]: string | Uint8Array | StorageSymlink
}
-export interface TestConfig {
+export interface TestConfig> {
name: string
+ inputs?: TestInput[]
+
fs?: Storage
debug?: boolean
- prepare?(utils: TestUtils): Promise
- handle(utils: TestUtils & Extras): void | Promise
+ prepare?(utils: TestUtils): Promise
+ handle(utils: TestUtils & Extras): void | Promise
options?: TestOptions
}
-export function defineTest(config: TestConfig) {
- return test(config.name, config.options ?? {}, async ({ expect }) => {
- let utils = await setup(config)
+export function defineTest(config: TestConfig) {
+ async function runTest(ctx: TestContext, input?: I) {
+ let utils = await setup(config, input)
let extras = await config.prepare?.(utils)
await config.handle({
...utils,
...extras,
})
- })
+ }
+
+ if (config.inputs) {
+ return test.for(config.inputs ?? [])(config.name, config.options ?? {}, (input, ctx) =>
+ runTest(ctx, input),
+ )
+ }
+
+ return test(config.name, config.options ?? {}, runTest)
}
-async function setup(config: TestConfig): Promise {
+async function setup(config: TestConfig, input: I): Promise> {
let randomId = Math.random().toString(36).substring(7)
let baseDir = path.resolve(process.cwd(), `../../.debug/${randomId}`)
@@ -56,7 +73,7 @@ async function setup(config: TestConfig): Promise {
await installDependencies(baseDir, config.fs)
}
- onTestFinished(async (result) => {
+ onTestFinished(async (ctx) => {
// Once done, move all the files to a new location
try {
await fs.rename(baseDir, doneDir)
@@ -66,7 +83,7 @@ async function setup(config: TestConfig): Promise {
console.error('Failed to move test files to done directory')
}
- if (result.state === 'fail') return
+ if (ctx.task.result?.state === 'fail') return
if (path.sep === '\\') return
@@ -79,6 +96,7 @@ async function setup(config: TestConfig): Promise {
return {
root: baseDir,
+ input,
}
}
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index d5cfba20..a0b7fa01 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -52,7 +52,7 @@ import { readCssFile } from './util/css'
import { ProjectLocator, type ProjectConfig } from './project-locator'
import type { TailwindCssSettings } from '@tailwindcss/language-service/src/util/state'
import { createResolver, Resolver } from './resolver'
-import { retry } from './util/retry'
+import { analyzeStylesheet } from './version-guesser.js'
const TRIGGER_CHARACTERS = [
// class attributes
@@ -382,6 +382,13 @@ export class TW {
for (let [, project] of this.projects) {
if (!project.state.v4) continue
+ if (
+ change.type === FileChangeType.Deleted &&
+ changeAffectsFile(normalizedFilename, [project.projectConfig.configPath])
+ ) {
+ continue
+ }
+
if (!changeAffectsFile(normalizedFilename, project.dependencies())) continue
needsSoftRestart = true
@@ -405,6 +412,31 @@ export class TW {
needsRestart = true
break
}
+
+ //
+ else {
+ // If the main CSS file in a project is deleted and then re-created
+ // the server won't restart because the project is gone by now and
+ // there's no concept of a "config file" for us to compare with
+ //
+ // So we'll check if the stylesheet could *potentially* create
+ // a new project but we'll only do so if no projects were found
+ //
+ // If we did this all the time we'd potentially restart the server
+ // unncessarily a lot while the user is editing their stylesheets
+ if (this.projects.size > 0) continue
+
+ let content = await readCssFile(change.file)
+ if (!content) continue
+
+ let stylesheet = analyzeStylesheet(content)
+ if (!stylesheet.root) continue
+
+ if (!stylesheet.versions.includes('4')) continue
+
+ needsRestart = true
+ break
+ }
}
let isConfigFile = isConfigMatcher(normalizedFilename)
@@ -1041,11 +1073,17 @@ export class TW {
this.watched.length = 0
}
- restart(): void {
+ async restart(): void {
+ let isTestMode = this.initializeParams.initializationOptions?.testMode ?? false
+
console.log('----------\nRESTARTING\n----------')
this.dispose()
this.initPromise = undefined
- this.init()
+ await this.init()
+
+ if (isTestMode) {
+ this.connection.sendNotification('@/tailwindCSS/serverRestarted')
+ }
}
async softRestart(): Promise {
diff --git a/packages/tailwindcss-language-server/tests/env/restart.test.ts b/packages/tailwindcss-language-server/tests/env/restart.test.ts
new file mode 100644
index 00000000..a973b2e0
--- /dev/null
+++ b/packages/tailwindcss-language-server/tests/env/restart.test.ts
@@ -0,0 +1,169 @@
+import { expect } from 'vitest'
+import * as fs from 'node:fs/promises'
+import * as path from 'node:path'
+import { css, defineTest } from '../../src/testing'
+import dedent from 'dedent'
+import { createClient } from '../utils/client'
+
+defineTest({
+ name: 'The design system is reloaded when the CSS changes ($watcher)',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+
+ @theme {
+ --color-primary: #c0ffee;
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ capabilities(caps) {
+ caps.workspace!.didChangeWatchedFiles!.dynamicRegistration = false
+ },
+ }),
+ }),
+ handle: async ({ root, client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '',
+ })
+
+ //
+ // ^
+ let hover = await doc.hover({ line: 0, character: 13 })
+
+ expect(hover).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .text-primary {
+ color: var(--color-primary) /* #c0ffee */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 24 },
+ },
+ })
+
+ let didReload = new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/projectReloaded', resolve)
+ })
+
+ // Update the CSS
+ await fs.writeFile(
+ path.resolve(root, 'app.css'),
+ css`
+ @import 'tailwindcss';
+
+ @theme {
+ --color-primary: #bada55;
+ }
+ `,
+ )
+
+ await didReload
+
+ //
+ // ^
+ let hover2 = await doc.hover({ line: 0, character: 13 })
+
+ expect(hover2).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .text-primary {
+ color: var(--color-primary) /* #bada55 */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 24 },
+ },
+ })
+ },
+})
+
+defineTest({
+ options: {
+ retry: 3,
+ },
+ name: 'Server is "restarted" when a config file is removed',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+
+ @theme {
+ --color-primary: #c0ffee;
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ capabilities(caps) {
+ caps.workspace!.didChangeWatchedFiles!.dynamicRegistration = false
+ },
+ }),
+ }),
+ handle: async ({ root, client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hover = await doc.hover({ line: 0, character: 13 })
+
+ expect(hover).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .text-primary {
+ color: var(--color-primary) /* #c0ffee */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 24 },
+ },
+ })
+
+ // Remove the CSS file
+ let didRestart = new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/serverRestarted', resolve)
+ })
+ await fs.unlink(path.resolve(root, 'app.css'))
+ await didRestart
+
+ //
+ // ^
+ let hover2 = await doc.hover({ line: 0, character: 13 })
+ expect(hover2).toEqual(null)
+
+ // Re-create the CSS file
+ let didRestartAgain = new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/serverRestarted', resolve)
+ })
+ await fs.writeFile(
+ path.resolve(root, 'app.css'),
+ css`
+ @import 'tailwindcss';
+ `,
+ )
+ await didRestartAgain
+
+ await new Promise((resolve) => setTimeout(resolve, 500))
+
+ //
+ // ^
+ let hover3 = await doc.hover({ line: 0, character: 13 })
+ expect(hover3).toEqual(null)
+ },
+})
diff --git a/packages/tailwindcss-language-server/tests/utils/client.ts b/packages/tailwindcss-language-server/tests/utils/client.ts
index 3a860230..760b805b 100644
--- a/packages/tailwindcss-language-server/tests/utils/client.ts
+++ b/packages/tailwindcss-language-server/tests/utils/client.ts
@@ -6,7 +6,6 @@ import {
CompletionList,
CompletionParams,
Diagnostic,
- DidChangeWorkspaceFoldersNotification,
Disposable,
DocumentLink,
DocumentLinkRequest,
@@ -179,6 +178,11 @@ export interface ClientOptions extends ConnectOptions {
* and the Tailwind CSS version it detects
*/
features?: Feature[]
+
+ /**
+ * Tweak the client capabilities presented to the server
+ */
+ capabilities?(caps: ClientCapabilities): ClientCapabilities | Promise
| void
}
export interface Client extends ClientWorkspace {
@@ -394,6 +398,8 @@ export async function createClient(opts: ClientOptions): Promise {
},
}
+ capabilities = (await opts.capabilities?.(capabilities)) ?? capabilities
+
trace('Client initializing')
await conn.sendRequest(InitializeRequest.type, {
processId: process.pid,
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 1750eb69..7a59b6c4 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -4,6 +4,7 @@
- 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))
+- Correctly re-create a project when its main config file is removed then re-created ([#1300](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1300))
# 0.14.13
From 3f6cc1f30af3915538489fa687cb283a6d10a10f Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Mon, 7 Apr 2025 14:14:47 -0400
Subject: [PATCH 36/93] Bump `@parcel/watcher` (#1269)
This is just a general version bump. I thought an issue was parcel
watcher related but turned out not to be. Saw we were **very** behind so
I wanted to make sure we were up to date.
---
.../tailwindcss-language-server/package.json | 10 +-
.../tailwindcss-language-server/src/tw.ts | 8 +
.../src/watcher/index.js | 21 +-
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
pnpm-lock.yaml | 182 ++++++++++++++++--
5 files changed, 196 insertions(+), 26 deletions(-)
diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json
index c4dc2984..55c890a4 100644
--- a/packages/tailwindcss-language-server/package.json
+++ b/packages/tailwindcss-language-server/package.json
@@ -34,7 +34,15 @@
"access": "public"
},
"devDependencies": {
- "@parcel/watcher": "2.0.3",
+ "@parcel/watcher": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
"@tailwindcss/aspect-ratio": "0.4.2",
"@tailwindcss/container-queries": "0.1.0",
"@tailwindcss/forms": "0.5.3",
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index a0b7fa01..682aefcd 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -521,6 +521,10 @@ export class TW {
}
}
} else if (parcel.getBinding()) {
+ console.log(
+ '[Global] Your LSP client does not support watching files on behalf of the server',
+ )
+ console.log('[Global] Using bundled file watcher: @parcel/watcher')
let typeMap = {
create: FileChangeType.Created,
update: FileChangeType.Changed,
@@ -547,6 +551,10 @@ export class TW {
},
})
} else {
+ console.log(
+ '[Global] Your LSP client does not support watching files on behalf of the server',
+ )
+ console.log('[Global] Using bundled file watcher: chokidar')
let watch: typeof chokidar.watch = require('chokidar').watch
let chokidarWatcher = watch(
[`**/${CONFIG_GLOB}`, `**/${PACKAGE_LOCK_GLOB}`, `**/${CSS_GLOB}`, `**/${TSCONFIG_GLOB}`],
diff --git a/packages/tailwindcss-language-server/src/watcher/index.js b/packages/tailwindcss-language-server/src/watcher/index.js
index ecf582e6..46cec8e8 100644
--- a/packages/tailwindcss-language-server/src/watcher/index.js
+++ b/packages/tailwindcss-language-server/src/watcher/index.js
@@ -13,21 +13,24 @@ const uv = (process.versions.uv || '').split('.')[0]
const prebuilds = {
'darwin-arm64': {
- 'node.napi.glibc.node': () =>
- require('@parcel/watcher/prebuilds/darwin-arm64/node.napi.glibc.node'),
+ 'node.napi.glibc.node': () => require('@parcel/watcher-darwin-arm64/watcher.node'),
},
'darwin-x64': {
- 'node.napi.glibc.node': () =>
- require('@parcel/watcher/prebuilds/darwin-x64/node.napi.glibc.node'),
+ 'node.napi.glibc.node': () => require('@parcel/watcher-darwin-x64/watcher.node'),
},
'linux-x64': {
- 'node.napi.glibc.node': () =>
- require('@parcel/watcher/prebuilds/linux-x64/node.napi.glibc.node'),
- 'node.napi.musl.node': () => require('@parcel/watcher/prebuilds/linux-x64/node.napi.musl.node'),
+ 'node.napi.glibc.node': () => require('@parcel/watcher-linux-x64-glibc/watcher.node'),
+ 'node.napi.musl.node': () => require('@parcel/watcher-linux-x64-musl/watcher.node'),
+ },
+ 'linux-arm64': {
+ 'node.napi.glibc.node': () => require('@parcel/watcher-linux-arm64-glibc/watcher.node'),
+ 'node.napi.musl.node': () => require('@parcel/watcher-linux-arm64-musl/watcher.node'),
},
'win32-x64': {
- 'node.napi.glibc.node': () =>
- require('@parcel/watcher/prebuilds/win32-x64/node.napi.glibc.node'),
+ 'node.napi.glibc.node': () => require('@parcel/watcher-win32-x64/watcher.node'),
+ },
+ 'win32-arm64': {
+ 'node.napi.glibc.node': () => require('@parcel/watcher-win32-arm64/watcher.node'),
},
}
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 7a59b6c4..599345e6 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -5,6 +5,7 @@
- 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))
- Correctly re-create a project when its main config file is removed then re-created ([#1300](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1300))
+- Bump `@parcel/watcher` used by the language server ([#1269](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1269))
# 0.14.13
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c4e0edaf..2255d6ad 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -33,8 +33,32 @@ importers:
packages/tailwindcss-language-server:
devDependencies:
'@parcel/watcher':
- specifier: 2.0.3
- version: 2.0.3
+ specifier: 2.5.1
+ version: 2.5.1
+ '@parcel/watcher-darwin-arm64':
+ specifier: 2.5.1
+ version: 2.5.1
+ '@parcel/watcher-darwin-x64':
+ specifier: 2.5.1
+ version: 2.5.1
+ '@parcel/watcher-linux-arm64-glibc':
+ specifier: 2.5.1
+ version: 2.5.1
+ '@parcel/watcher-linux-arm64-musl':
+ specifier: 2.5.1
+ version: 2.5.1
+ '@parcel/watcher-linux-x64-glibc':
+ specifier: 2.5.1
+ version: 2.5.1
+ '@parcel/watcher-linux-x64-musl':
+ specifier: 2.5.1
+ version: 2.5.1
+ '@parcel/watcher-win32-arm64':
+ specifier: 2.5.1
+ version: 2.5.1
+ '@parcel/watcher-win32-x64':
+ specifier: 2.5.1
+ version: 2.5.1
'@tailwindcss/aspect-ratio':
specifier: 0.4.2
version: 0.4.2(tailwindcss@3.4.17)
@@ -767,8 +791,86 @@ packages:
resolution: {integrity: sha512-wBqcGsMELZna0jDblGd7UXgOby45TQaMWmbFwWX+SEotk4HV6zG2t6rT9siyLhPk4P6YYqgfL1UO8nMWDBVJXQ==}
engines: {node: ^16.14.0 || >=18.0.0}
- '@parcel/watcher@2.0.3':
- resolution: {integrity: sha512-PHh5PArr3nYGYVj9z/NSfDmmKEBNrg2bzoFgxzjTRBBxPUKx039x3HF6VGLFIfrghjJxcYn/IeSpdVwfob7KFA==}
+ '@parcel/watcher-android-arm64@2.5.1':
+ resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ '@parcel/watcher-darwin-arm64@2.5.1':
+ resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@parcel/watcher-darwin-x64@2.5.1':
+ resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@parcel/watcher-freebsd-x64@2.5.1':
+ resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@parcel/watcher-linux-arm-glibc@2.5.1':
+ resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm-musl@2.5.1':
+ resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.1':
+ resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm64-musl@2.5.1':
+ resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@parcel/watcher-linux-x64-glibc@2.5.1':
+ resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@parcel/watcher-linux-x64-musl@2.5.1':
+ resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@parcel/watcher-win32-arm64@2.5.1':
+ resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@parcel/watcher-win32-ia32@2.5.1':
+ resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@parcel/watcher-win32-x64@2.5.1':
+ resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@parcel/watcher@2.5.1':
+ resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@pkgjs/parseargs@0.11.0':
@@ -1372,6 +1474,11 @@ packages:
resolution: {integrity: sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==}
engines: {node: '>=8'}
+ detect-libc@1.0.3:
+ resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
@@ -1910,15 +2017,11 @@ packages:
resolution: {integrity: sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==}
engines: {node: '>=10'}
- node-addon-api@3.2.1:
- resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
-
node-addon-api@4.3.0:
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
- node-gyp-build@4.8.1:
- resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==}
- hasBin: true
+ node-addon-api@7.1.1:
+ resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-releases@2.0.18:
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
@@ -3027,10 +3130,57 @@ snapshots:
dependencies:
which: 4.0.0
- '@parcel/watcher@2.0.3':
+ '@parcel/watcher-android-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-darwin-arm64@2.5.1': {}
+
+ '@parcel/watcher-darwin-x64@2.5.1': {}
+
+ '@parcel/watcher-freebsd-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.1': {}
+
+ '@parcel/watcher-linux-arm64-musl@2.5.1': {}
+
+ '@parcel/watcher-linux-x64-glibc@2.5.1': {}
+
+ '@parcel/watcher-linux-x64-musl@2.5.1': {}
+
+ '@parcel/watcher-win32-arm64@2.5.1': {}
+
+ '@parcel/watcher-win32-ia32@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-x64@2.5.1': {}
+
+ '@parcel/watcher@2.5.1':
dependencies:
- node-addon-api: 3.2.1
- node-gyp-build: 4.8.1
+ detect-libc: 1.0.3
+ is-glob: 4.0.3
+ micromatch: 4.0.8
+ node-addon-api: 7.1.1
+ optionalDependencies:
+ '@parcel/watcher-android-arm64': 2.5.1
+ '@parcel/watcher-darwin-arm64': 2.5.1
+ '@parcel/watcher-darwin-x64': 2.5.1
+ '@parcel/watcher-freebsd-x64': 2.5.1
+ '@parcel/watcher-linux-arm-glibc': 2.5.1
+ '@parcel/watcher-linux-arm-musl': 2.5.1
+ '@parcel/watcher-linux-arm64-glibc': 2.5.1
+ '@parcel/watcher-linux-arm64-musl': 2.5.1
+ '@parcel/watcher-linux-x64-glibc': 2.5.1
+ '@parcel/watcher-linux-x64-musl': 2.5.1
+ '@parcel/watcher-win32-arm64': 2.5.1
+ '@parcel/watcher-win32-ia32': 2.5.1
+ '@parcel/watcher-win32-x64': 2.5.1
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -3580,6 +3730,8 @@ snapshots:
detect-indent@6.0.0: {}
+ detect-libc@1.0.3: {}
+
detect-libc@2.0.3:
optional: true
@@ -4122,12 +4274,10 @@ snapshots:
semver: 7.7.1
optional: true
- node-addon-api@3.2.1: {}
-
node-addon-api@4.3.0:
optional: true
- node-gyp-build@4.8.1: {}
+ node-addon-api@7.1.1: {}
node-releases@2.0.18: {}
From 86bfb7d53a52422c766ac1348a9c953576d6cbdf Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Mon, 7 Apr 2025 14:20:09 -0400
Subject: [PATCH 37/93] Fix type error
---
packages/tailwindcss-language-server/src/tw.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index 682aefcd..20ac0158 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -1081,7 +1081,7 @@ export class TW {
this.watched.length = 0
}
- async restart(): void {
+ async restart(): Promise {
let isTestMode = this.initializeParams.initializationOptions?.testMode ?? false
console.log('----------\nRESTARTING\n----------')
From e24050ef98ddb4fea4fcd77dfab0764e3e69320e Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Mon, 7 Apr 2025 16:34:18 -0400
Subject: [PATCH 38/93] Skip flaky test
---
.../tailwindcss-language-server/tests/env/restart.test.ts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/packages/tailwindcss-language-server/tests/env/restart.test.ts b/packages/tailwindcss-language-server/tests/env/restart.test.ts
index a973b2e0..49b632c3 100644
--- a/packages/tailwindcss-language-server/tests/env/restart.test.ts
+++ b/packages/tailwindcss-language-server/tests/env/restart.test.ts
@@ -91,6 +91,11 @@ defineTest({
defineTest({
options: {
retry: 3,
+
+ // This test passes on all platforms but it is super flaky
+ // The server needs some re-working to ensure everything is awaited
+ // properly with respect to messages and server responses
+ skip: true,
},
name: 'Server is "restarted" when a config file is removed',
fs: {
From 933c968eb26d908334ed4ca325809c85727a9004 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Mon, 7 Apr 2025 16:43:56 -0400
Subject: [PATCH 39/93] 0.14.14
---
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 55c890a4..971c2aaf 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.13",
+ "version": "0.14.14",
"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 23907838..79f5e818 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.13",
+ "version": "0.14.14",
"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 599345e6..fa0302ae 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,6 +2,10 @@
## Prerelease
+- Nothing yet!
+
+# 0.14.14
+
- 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))
- Correctly re-create a project when its main config file is removed then re-created ([#1300](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1300))
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 4f55fb4a..2ac8c102 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "vscode-tailwindcss",
- "version": "0.14.13",
+ "version": "0.14.14",
"displayName": "Tailwind CSS IntelliSense",
"description": "Intelligent Tailwind CSS tooling for VS Code",
"author": "Brad Cornes ",
From 8f5d895f32ca7c416db9a615ea150c51b240fb13 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 9 Apr 2025 07:13:05 -0400
Subject: [PATCH 40/93] Fix infinite loop when any file exclusions start with
`/` (#1307)
See https://github.com/SuperchupuDev/tinyglobby/issues/99
Fixes #1303
---
.../src/project-locator.test.ts | 39 ++++++++++++++++++-
.../src/project-locator.ts | 13 ++++++-
packages/vscode-tailwindcss/CHANGELOG.md | 2 +-
3 files changed, 51 insertions(+), 3 deletions(-)
diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts
index f43479ae..3d5cddbb 100644
--- a/packages/tailwindcss-language-server/src/project-locator.test.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.test.ts
@@ -462,6 +462,43 @@ testLocator({
],
})
+testLocator({
+ name: 'File exclusions starting with `/` do not cause traversal to loop forever',
+ fs: {
+ 'index.css': css`
+ @import 'tailwindcss';
+ `,
+ 'vendor/a.css': css`
+ @import 'tailwindcss';
+ `,
+ 'vendor/nested/b.css': css`
+ @import 'tailwindcss';
+ `,
+ 'src/vendor/c.css': css`
+ @import 'tailwindcss';
+ `,
+ },
+ settings: {
+ tailwindCSS: {
+ files: {
+ exclude: ['/vendor'],
+ },
+ } as Settings['tailwindCSS'],
+ },
+ expected: [
+ {
+ version: '4.1.1 (bundled)',
+ config: '/index.css',
+ content: [],
+ },
+ {
+ version: '4.1.1 (bundled)',
+ config: '/src/vendor/c.css',
+ content: [],
+ },
+ ],
+})
+
// ---
function testLocator({
@@ -502,7 +539,7 @@ function testLocator({
})
}
-async function prepare({ root }: TestUtils) {
+async function prepare({ root }: TestUtils) {
let defaultSettings = {
tailwindCSS: {
files: {
diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts
index de562b63..4e3c0453 100644
--- a/packages/tailwindcss-language-server/src/project-locator.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.ts
@@ -274,11 +274,22 @@ export class ProjectLocator {
}
private async findConfigs(): Promise {
+ let ignore = this.settings.tailwindCSS.files.exclude
+
+ // NOTE: This is a temporary workaround for a bug in the `fdir` package used
+ // by `tinyglobby`. It infinite loops when the ignore pattern starts with
+ // a `/`. This should be removed once the bug is fixed.
+ ignore = ignore.map((pattern) => {
+ if (!pattern.startsWith('/')) return pattern
+
+ return pattern.slice(1)
+ })
+
// Look for config files and CSS files
let files = await glob({
patterns: [`**/${CONFIG_GLOB}`, `**/${CSS_GLOB}`],
cwd: this.base,
- ignore: this.settings.tailwindCSS.files.exclude,
+ ignore,
onlyFiles: true,
absolute: true,
followSymbolicLinks: true,
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index fa0302ae..e9a9bbde 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,7 +2,7 @@
## Prerelease
-- Nothing yet!
+- Prevent infinite loop when any file exclusion starts with `/` ([#1307](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1307))
# 0.14.14
From d561b6149804f0c313ddebb6a4bd90769d33d272 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 8 Apr 2025 23:30:54 -0400
Subject: [PATCH 41/93] 0.14.15
---
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 971c2aaf..0813d13e 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.14",
+ "version": "0.14.15",
"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 79f5e818..2a1e3079 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.14",
+ "version": "0.14.15",
"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 e9a9bbde..17f07afc 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,6 +2,10 @@
## Prerelease
+- Nothing yet!
+
+# 0.14.15
+
- Prevent infinite loop when any file exclusion starts with `/` ([#1307](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1307))
# 0.14.14
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 2ac8c102..26952a45 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "vscode-tailwindcss",
- "version": "0.14.14",
+ "version": "0.14.15",
"displayName": "Tailwind CSS IntelliSense",
"description": "Intelligent Tailwind CSS tooling for VS Code",
"author": "Brad Cornes ",
From 92ea52fa3c60c9e6cdc1cd27ae35502d34be7634 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Mon, 14 Apr 2025 11:04:45 -0400
Subject: [PATCH 42/93] Warn when using blocklisted classes (#1310)
---
.../src/projects.ts | 6 ++-
.../tests/diagnostics/diagnostics.test.js | 44 ++++++++++++++++++-
.../src/diagnostics/diagnosticsProvider.ts | 5 +++
.../getUsedBlocklistedClassDiagnostics.ts | 41 +++++++++++++++++
.../src/diagnostics/types.ts | 12 +++++
.../src/util/state.ts | 2 +
.../src/util/v4/design-system.ts | 1 +
packages/vscode-tailwindcss/CHANGELOG.md | 2 +-
packages/vscode-tailwindcss/README.md | 4 ++
packages/vscode-tailwindcss/package.json | 11 +++++
10 files changed, 125 insertions(+), 3 deletions(-)
create mode 100644 packages/tailwindcss-language-service/src/diagnostics/getUsedBlocklistedClassDiagnostics.ts
diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts
index 25aacd5f..04160569 100644
--- a/packages/tailwindcss-language-server/src/projects.ts
+++ b/packages/tailwindcss-language-server/src/projects.ts
@@ -837,6 +837,7 @@ export async function createProjectService(
)
state.designSystem = designSystem
+ state.blocklist = Array.from(designSystem.invalidCandidates ?? [])
let deps = designSystem.dependencies()
@@ -982,7 +983,9 @@ export async function createProjectService(
if (typeof state.separator !== 'string') {
state.separator = ':'
}
- state.blocklist = Array.isArray(state.config.blocklist) ? state.config.blocklist : []
+ if (!state.v4) {
+ state.blocklist = Array.isArray(state.config.blocklist) ? state.config.blocklist : []
+ }
delete state.config.blocklist
if (state.v4) {
@@ -1148,6 +1151,7 @@ export async function createProjectService(
state.designSystem = designSystem
state.classList = classList
state.variants = getVariants(state)
+ state.blocklist = Array.from(designSystem.invalidCandidates ?? [])
let deps = designSystem.dependencies()
diff --git a/packages/tailwindcss-language-server/tests/diagnostics/diagnostics.test.js b/packages/tailwindcss-language-server/tests/diagnostics/diagnostics.test.js
index 56fe9f7f..afda8837 100644
--- a/packages/tailwindcss-language-server/tests/diagnostics/diagnostics.test.js
+++ b/packages/tailwindcss-language-server/tests/diagnostics/diagnostics.test.js
@@ -1,6 +1,8 @@
+import * as fs from 'node:fs/promises'
import { expect, test } from 'vitest'
import { withFixture } from '../common'
-import * as fs from 'node:fs/promises'
+import { css, defineTest } from '../../src/testing'
+import { createClient } from '../utils/client'
withFixture('basic', (c) => {
function testFixture(fixture) {
@@ -383,3 +385,43 @@ withFixture('v4/basic', (c) => {
],
})
})
+
+defineTest({
+ name: 'Shows warning when using blocklisted classes',
+ fs: {
+ 'app.css': css`
+ @import 'tailwindcss';
+ @source not inline("{,hover:}flex");
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '',
+ })
+
+ let diagnostics = await doc.diagnostics()
+
+ expect(diagnostics).toEqual([
+ {
+ code: 'usedBlocklistedClass',
+ message: 'The class "flex" will not be generated as it has been blocklisted',
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 16 },
+ },
+ severity: 2,
+ },
+ {
+ code: 'usedBlocklistedClass',
+ message: 'The class "hover:flex" will not be generated as it has been blocklisted',
+ range: {
+ start: { line: 0, character: 27 },
+ end: { line: 0, character: 37 },
+ },
+ severity: 2,
+ },
+ ])
+ },
+})
diff --git a/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts b/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts
index 18994526..075cea38 100644
--- a/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts
+++ b/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts
@@ -9,6 +9,7 @@ import { getInvalidConfigPathDiagnostics } from './getInvalidConfigPathDiagnosti
import { getInvalidTailwindDirectiveDiagnostics } from './getInvalidTailwindDirectiveDiagnostics'
import { getRecommendedVariantOrderDiagnostics } from './getRecommendedVariantOrderDiagnostics'
import { getInvalidSourceDiagnostics } from './getInvalidSourceDiagnostics'
+import { getUsedBlocklistedClassDiagnostics } from './getUsedBlocklistedClassDiagnostics'
export async function doValidate(
state: State,
@@ -22,6 +23,7 @@ export async function doValidate(
DiagnosticKind.InvalidTailwindDirective,
DiagnosticKind.InvalidSourceDirective,
DiagnosticKind.RecommendedVariantOrder,
+ DiagnosticKind.UsedBlocklistedClass,
],
): Promise
{
const settings = await state.editor.getConfiguration(document.uri)
@@ -52,6 +54,9 @@ export async function doValidate(
...(only.includes(DiagnosticKind.RecommendedVariantOrder)
? await getRecommendedVariantOrderDiagnostics(state, document, settings)
: []),
+ ...(only.includes(DiagnosticKind.UsedBlocklistedClass)
+ ? await getUsedBlocklistedClassDiagnostics(state, document, settings)
+ : []),
]
: []
}
diff --git a/packages/tailwindcss-language-service/src/diagnostics/getUsedBlocklistedClassDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getUsedBlocklistedClassDiagnostics.ts
new file mode 100644
index 00000000..07f1084c
--- /dev/null
+++ b/packages/tailwindcss-language-service/src/diagnostics/getUsedBlocklistedClassDiagnostics.ts
@@ -0,0 +1,41 @@
+import type { TextDocument } from 'vscode-languageserver-textdocument'
+import type { State, Settings } from '../util/state'
+import { type UsedBlocklistedClassDiagnostic, DiagnosticKind } from './types'
+import { findClassListsInDocument, getClassNamesInClassList } from '../util/find'
+
+export async function getUsedBlocklistedClassDiagnostics(
+ state: State,
+ document: TextDocument,
+ settings: Settings,
+): Promise {
+ if (!state.v4) return []
+ if (!state.blocklist?.length) return []
+
+ let severity = settings.tailwindCSS.lint.usedBlocklistedClass
+ if (severity === 'ignore') return []
+
+ let blocklist = new Set(state.blocklist ?? [])
+ let diagnostics: UsedBlocklistedClassDiagnostic[] = []
+
+ let classLists = await findClassListsInDocument(state, document)
+
+ for (let classList of classLists) {
+ let classNames = getClassNamesInClassList(classList, [])
+
+ for (let className of classNames) {
+ if (!blocklist.has(className.className)) continue
+
+ diagnostics.push({
+ code: DiagnosticKind.UsedBlocklistedClass,
+ range: className.range,
+ severity:
+ severity === 'error'
+ ? 1 /* DiagnosticSeverity.Error */
+ : 2 /* DiagnosticSeverity.Warning */,
+ message: `The class "${className.className}" will not be generated as it has been blocklisted`,
+ })
+ }
+ }
+
+ return diagnostics
+}
diff --git a/packages/tailwindcss-language-service/src/diagnostics/types.ts b/packages/tailwindcss-language-service/src/diagnostics/types.ts
index 7cb68a7e..f022503a 100644
--- a/packages/tailwindcss-language-service/src/diagnostics/types.ts
+++ b/packages/tailwindcss-language-service/src/diagnostics/types.ts
@@ -10,6 +10,7 @@ export enum DiagnosticKind {
InvalidTailwindDirective = 'invalidTailwindDirective',
InvalidSourceDirective = 'invalidSourceDirective',
RecommendedVariantOrder = 'recommendedVariantOrder',
+ UsedBlocklistedClass = 'usedBlocklistedClass',
}
export type CssConflictDiagnostic = Diagnostic & {
@@ -100,6 +101,16 @@ export function isRecommendedVariantOrderDiagnostic(
return diagnostic.code === DiagnosticKind.RecommendedVariantOrder
}
+export type UsedBlocklistedClassDiagnostic = Diagnostic & {
+ code: DiagnosticKind.UsedBlocklistedClass
+}
+
+export function isUsedBlocklistedClass(
+ diagnostic: AugmentedDiagnostic,
+): diagnostic is UsedBlocklistedClassDiagnostic {
+ return diagnostic.code === DiagnosticKind.UsedBlocklistedClass
+}
+
export type AugmentedDiagnostic =
| CssConflictDiagnostic
| InvalidApplyDiagnostic
@@ -109,3 +120,4 @@ export type AugmentedDiagnostic =
| InvalidTailwindDirectiveDiagnostic
| InvalidSourceDirectiveDiagnostic
| RecommendedVariantOrderDiagnostic
+ | UsedBlocklistedClassDiagnostic
diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts
index 8c819eb8..754f2a49 100644
--- a/packages/tailwindcss-language-service/src/util/state.ts
+++ b/packages/tailwindcss-language-service/src/util/state.ts
@@ -65,6 +65,7 @@ export type TailwindCssSettings = {
invalidTailwindDirective: DiagnosticSeveritySetting
invalidSourceDirective: DiagnosticSeveritySetting
recommendedVariantOrder: DiagnosticSeveritySetting
+ usedBlocklistedClass: DiagnosticSeveritySetting
}
experimental: {
classRegex: string[] | [string, string][]
@@ -203,6 +204,7 @@ export function getDefaultTailwindSettings(): Settings {
invalidTailwindDirective: 'error',
invalidSourceDirective: 'error',
recommendedVariantOrder: 'warning',
+ usedBlocklistedClass: 'warning',
},
showPixelEquivalents: true,
includeLanguages: {},
diff --git a/packages/tailwindcss-language-service/src/util/v4/design-system.ts b/packages/tailwindcss-language-service/src/util/v4/design-system.ts
index 20b7ff32..13c657c3 100644
--- a/packages/tailwindcss-language-service/src/util/v4/design-system.ts
+++ b/packages/tailwindcss-language-service/src/util/v4/design-system.ts
@@ -40,6 +40,7 @@ export interface DesignSystem {
// Optional because it did not exist in earlier v4 alpha versions
resolveThemeValue?(path: string, forceInline?: boolean): string | undefined
+ invalidCandidates?: Set
}
export interface DesignSystem {
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 17f07afc..4c9240f6 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,7 +2,7 @@
## Prerelease
-- Nothing yet!
+- Warn when using a blocklisted class in v4 ([#1310](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1310))
# 0.14.15
diff --git a/packages/vscode-tailwindcss/README.md b/packages/vscode-tailwindcss/README.md
index efc937cc..f74b1407 100644
--- a/packages/vscode-tailwindcss/README.md
+++ b/packages/vscode-tailwindcss/README.md
@@ -172,6 +172,10 @@ Class names on the same HTML element which apply the same CSS property or proper
Class variants not in the recommended order (applies in [JIT mode](https://tailwindcss.com/docs/just-in-time-mode) only). **Default: `warning`**
+#### `tailwindCSS.lint.usedBlocklistedClass`
+
+Usage of class names that have been blocklisted via `@source not inline(…)`. **Default: `warning`**
+
### `tailwindCSS.inspectPort`
Enable the Node.js inspector agent for the language server and listen on the specified port. **Default: `null`**
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 26952a45..d3d30911 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -305,6 +305,17 @@
"markdownDescription": "Class variants not in the recommended order (applies in [JIT mode](https://tailwindcss.com/docs/just-in-time-mode) only)",
"scope": "language-overridable"
},
+ "tailwindCSS.lint.usedBlocklistedClass": {
+ "type": "string",
+ "enum": [
+ "ignore",
+ "warning",
+ "error"
+ ],
+ "default": "warning",
+ "markdownDescription": "Usage of class names that have been blocklisted via `@source not inline(…)`",
+ "scope": "language-overridable"
+ },
"tailwindCSS.experimental.classRegex": {
"type": "array",
"scope": "language-overridable"
From bd1fc8c4f52aa0baddc92a5710b649bfd5bb953d Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Mon, 14 Apr 2025 11:14:56 -0400
Subject: [PATCH 43/93] Add test for `classAttributes` matching variable names
in JS(X)/TS(X) (#1315)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Welp… this was 100% a bug that this behavior got introduced but at this
point it’s shipped and people might rely on it or otherwise want this
behavior. As such I’m explicitly encoding this in a test so it doesn’t
break later when the big parsing refactor lands.
---
.../src/util/find.test.ts | 111 +++++++++++++++++-
.../src/util/test-utils.ts | 1 +
2 files changed, 111 insertions(+), 1 deletion(-)
diff --git a/packages/tailwindcss-language-service/src/util/find.test.ts b/packages/tailwindcss-language-service/src/util/find.test.ts
index 561f82e8..839fb6d0 100644
--- a/packages/tailwindcss-language-service/src/util/find.test.ts
+++ b/packages/tailwindcss-language-service/src/util/find.test.ts
@@ -1,6 +1,6 @@
import { test } from 'vitest'
import { findClassListsInHtmlRange } from './find'
-import { js, html, createDocument } from './test-utils'
+import { js, html, pug, createDocument } from './test-utils'
test('class regex works in astro', async ({ expect }) => {
let file = createDocument({
@@ -682,3 +682,112 @@ test('classFunctions should only match in JS-like contexts', async ({ expect })
},
])
})
+
+test('classAttributes find class lists inside variables in JS(X)/TS(X)', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.html',
+ lang: 'javascript',
+ settings: {
+ tailwindCSS: {
+ classAttributes: ['className'],
+ },
+ },
+ content: js`
+ let className = {
+ a: "relative flex",
+ nested: {
+ b: "relative flex",
+ },
+ inFn: fn({
+ c: "relative flex",
+ })
+ }
+
+ let other = {
+ a: "relative flex",
+ }
+ `,
+ })
+
+ let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'js')
+
+ expect(classLists).toEqual([
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 1, character: 6 },
+ end: { line: 1, character: 19 },
+ },
+ },
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 3, character: 8 },
+ end: { line: 3, character: 21 },
+ },
+ },
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 6, character: 8 },
+ end: { line: 6, character: 21 },
+ },
+ },
+ ])
+})
+
+test('classAttributes find class lists inside pug', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.pug',
+ lang: 'pug',
+ settings: {
+ tailwindCSS: {
+ classAttributes: ['className'],
+ },
+ },
+ content: pug`
+ div(classNAme="relative flex")
+ `,
+ })
+
+ let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'js')
+
+ expect(classLists).toEqual([
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 0, character: 15 },
+ end: { line: 0, character: 28 },
+ },
+ },
+ ])
+})
+
+test('classAttributes find class lists inside Vue bindings', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.pug',
+ lang: 'vue',
+ settings: {
+ tailwindCSS: {
+ classAttributes: ['class'],
+ },
+ },
+ content: html`
+
+
+
+ `,
+ })
+
+ let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'js')
+
+ expect(classLists).toEqual([
+ {
+ classList: 'relative flex',
+ range: {
+ start: { line: 1, character: 17 },
+ end: { line: 1, character: 30 },
+ },
+ },
+ ])
+})
diff --git a/packages/tailwindcss-language-service/src/util/test-utils.ts b/packages/tailwindcss-language-service/src/util/test-utils.ts
index 088dade5..ce66ecaf 100644
--- a/packages/tailwindcss-language-service/src/util/test-utils.ts
+++ b/packages/tailwindcss-language-service/src/util/test-utils.ts
@@ -9,6 +9,7 @@ export const ts = dedent
export const tsx = dedent
export const css = dedent
export const html = dedent
+export const pug = dedent
export function createDocument({
name,
From e88d6dcb78f493ef56e374e6b2195bec3082165a Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 23 Apr 2025 10:51:23 -0400
Subject: [PATCH 44/93] Support class function hovers in Svelte and HTML
`` works
---
.../src/util/find.test.ts | 86 ++++++++++++++++++-
.../src/util/find.ts | 4 +-
.../src/util/state.ts | 1 +
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
4 files changed, 89 insertions(+), 3 deletions(-)
diff --git a/packages/tailwindcss-language-service/src/util/find.test.ts b/packages/tailwindcss-language-service/src/util/find.test.ts
index 839fb6d0..170217c0 100644
--- a/packages/tailwindcss-language-service/src/util/find.test.ts
+++ b/packages/tailwindcss-language-service/src/util/find.test.ts
@@ -1,5 +1,5 @@
import { test } from 'vitest'
-import { findClassListsInHtmlRange } from './find'
+import { findClassListsInHtmlRange, findClassNameAtPosition } from './find'
import { js, html, pug, createDocument } from './test-utils'
test('class regex works in astro', async ({ expect }) => {
@@ -791,3 +791,87 @@ test('classAttributes find class lists inside Vue bindings', async ({ expect })
},
])
})
+
+test('Can find class name inside JS/TS functions in
+ `,
+ })
+
+ let className = await findClassNameAtPosition(file.state, file.doc, {
+ line: 1,
+ character: 23,
+ })
+
+ expect(className).toEqual({
+ className: 'flex',
+ range: {
+ start: { line: 1, character: 22 },
+ end: { line: 1, character: 26 },
+ },
+ relativeRange: {
+ start: { line: 0, character: 0 },
+ end: { line: 0, character: 4 },
+ },
+ classList: {
+ classList: 'flex relative',
+ important: undefined,
+ range: {
+ start: { character: 22, line: 1 },
+ end: { character: 35, line: 1 },
+ },
+ },
+ })
+})
+
+test('Can find class name inside JS/TS functions in
+ `,
+ })
+
+ let className = await findClassNameAtPosition(file.state, file.doc, {
+ line: 1,
+ character: 23,
+ })
+
+ expect(className).toEqual({
+ className: 'flex',
+ range: {
+ start: { line: 1, character: 22 },
+ end: { line: 1, character: 26 },
+ },
+ relativeRange: {
+ start: { line: 0, character: 0 },
+ end: { line: 0, character: 4 },
+ },
+ classList: {
+ classList: 'flex relative',
+ important: undefined,
+ range: {
+ start: { character: 22, line: 1 },
+ end: { character: 35, line: 1 },
+ },
+ },
+ })
+})
diff --git a/packages/tailwindcss-language-service/src/util/find.ts b/packages/tailwindcss-language-service/src/util/find.ts
index 9118403d..53ad7195 100644
--- a/packages/tailwindcss-language-service/src/util/find.ts
+++ b/packages/tailwindcss-language-service/src/util/find.ts
@@ -5,7 +5,7 @@ import lineColumn from 'line-column'
import { isCssContext, isCssDoc } from './css'
import { isHtmlContext, isVueDoc } from './html'
import { isWithinRange } from './isWithinRange'
-import { isJsxContext } from './js'
+import { isJsContext } from './js'
import { dedupeByRange, flatten } from './array'
import { getClassAttributeLexer, getComputedClassAttributeLexer } from './lexers'
import { getLanguageBoundaries } from './getLanguageBoundaries'
@@ -526,7 +526,7 @@ export async function findClassNameAtPosition(
classNames = await findClassNamesInRange(state, doc, searchRange, 'css')
} else if (isHtmlContext(state, doc, position)) {
classNames = await findClassNamesInRange(state, doc, searchRange, 'html')
- } else if (isJsxContext(state, doc, position)) {
+ } else if (isJsContext(state, doc, position)) {
classNames = await findClassNamesInRange(state, doc, searchRange, 'jsx')
} else {
classNames = await findClassNamesInRange(state, doc, searchRange)
diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts
index 754f2a49..26cc2ed0 100644
--- a/packages/tailwindcss-language-service/src/util/state.ts
+++ b/packages/tailwindcss-language-service/src/util/state.ts
@@ -228,6 +228,7 @@ export function createState(
return {
enabled: true,
features: [],
+ blocklist: [],
...partial,
editor: {
get connection(): Connection {
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 4c9240f6..4162495c 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -3,6 +3,7 @@
## Prerelease
- Warn when using a blocklisted class in v4 ([#1310](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1310))
+- Support class function hovers in Svelte and HTML `` blocks ([#1311](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1311))
# 0.14.15
From 4fb66e12cc2beb30315ba0d883a14ed173cbd589 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 23 Apr 2025 11:12:18 -0400
Subject: [PATCH 45/93] =?UTF-8?q?Implement=20better=20`calc(=E2=80=A6)`=20?=
=?UTF-8?q?evaluation=20for=20completions=20and=20equivalents=20(#1316)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fixes #1314
- [x] Investigate the change to the TS config as I feel like it should
not be necessary
- [x] Manually verify the stated case from the issue above is addressed
Hovers:
Completions:
---
.../tailwindcss-language-service/package.json | 5 +-
.../src/util/rewriting/calc.ts | 66 ++++++-------------
.../src/util/rewriting/index.test.ts | 2 +
.../tsconfig.json | 4 +-
pnpm-lock.yaml | 59 +++++++++--------
5 files changed, 60 insertions(+), 76 deletions(-)
diff --git a/packages/tailwindcss-language-service/package.json b/packages/tailwindcss-language-service/package.json
index 2a1e3079..4a11e6c7 100644
--- a/packages/tailwindcss-language-service/package.json
+++ b/packages/tailwindcss-language-service/package.json
@@ -13,8 +13,9 @@
"test": "vitest"
},
"dependencies": {
- "@csstools/css-parser-algorithms": "2.1.1",
- "@csstools/css-tokenizer": "2.1.1",
+ "@csstools/css-calc": "2.1.2",
+ "@csstools/css-parser-algorithms": "3.0.4",
+ "@csstools/css-tokenizer": "3.0.3",
"@csstools/media-query-list-parser": "2.0.4",
"@types/culori": "^2.1.0",
"@types/moo": "0.5.3",
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/calc.ts b/packages/tailwindcss-language-service/src/util/rewriting/calc.ts
index 2420800f..e9949014 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/calc.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/calc.ts
@@ -1,56 +1,30 @@
-function parseLength(length: string): [number, string] | null {
- let regex = /^(-?\d*\.?\d+)([a-z%]*)$/i
- let match = length.match(regex)
-
- if (!match) return null
-
- let numberPart = parseFloat(match[1])
- if (isNaN(numberPart)) return null
-
- return [numberPart, match[2]]
-}
-
-function round(n: number, precision: number): number {
- return Math.round(n * Math.pow(10, precision)) / Math.pow(10, precision)
-}
+import { stringify, tokenize } from '@csstools/css-tokenizer'
+import { isFunctionNode, parseComponentValue } from '@csstools/css-parser-algorithms'
+import { calcFromComponentValues } from '@csstools/css-calc'
export function evaluateExpression(str: string): string | null {
- // We're only interested simple calc expressions of the form
- // A + B, A - B, A * B, A / B
+ let tokens = tokenize({ css: `calc(${str})` })
- let parts = str.split(/\s+([+*/-])\s+/)
+ let components = parseComponentValue(tokens, {})
+ if (!components) return null
- if (parts.length === 1) return null
- if (parts.length !== 3) return null
+ let result = calcFromComponentValues([[components]], {
+ // Ensure evaluation of random() is deterministic
+ randomSeed: 1,
- let a = parseLength(parts[0])
- let b = parseLength(parts[2])
+ // Limit precision to keep values environment independent
+ precision: 4,
+ })
- // Not parsable
- if (!a || !b) {
- return null
- }
-
- // Addition and subtraction require the same units
- if ((parts[1] === '+' || parts[1] === '-') && a[1] !== b[1]) {
- return null
- }
-
- // Multiplication and division require at least one unit to be empty
- if ((parts[1] === '*' || parts[1] === '/') && a[1] !== '' && b[1] !== '') {
- return null
- }
+ // The result array is the same shape as the original so we're guaranteed to
+ // have an element here
+ let node = result[0][0]
- switch (parts[1]) {
- case '+':
- return round(a[0] + b[0], 4).toString() + a[1]
- case '*':
- return round(a[0] * b[0], 4).toString() + a[1]
- case '-':
- return round(a[0] - b[0], 4).toString() + a[1]
- case '/':
- return round(a[0] / b[0], 4).toString() + a[1]
+ // If we have a top-level `calc(…)` node then the evaluation did not resolve
+ // to a single value and we consider it to be incomplete
+ if (isFunctionNode(node)) {
+ if (node.name[1] === 'calc(') return null
}
- return null
+ return stringify(...node.tokens())
}
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts b/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
index 6eb840ef..3adbdb44 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
@@ -95,6 +95,8 @@ test('Evaluating CSS calc expressions', () => {
expect(replaceCssCalc('calc(1.25 / 0.875)', (node) => evaluateExpression(node.value))).toBe(
'1.4286',
)
+
+ expect(replaceCssCalc('calc(1/4 * 100%)', (node) => evaluateExpression(node.value))).toBe('25%')
})
test('Inlining calc expressions using the design system', () => {
diff --git a/packages/tailwindcss-language-service/tsconfig.json b/packages/tailwindcss-language-service/tsconfig.json
index 883356e7..46eb230c 100644
--- a/packages/tailwindcss-language-service/tsconfig.json
+++ b/packages/tailwindcss-language-service/tsconfig.json
@@ -1,7 +1,7 @@
{
"include": ["src", "../../types"],
"compilerOptions": {
- "module": "NodeNext",
+ "module": "ES2022",
"lib": ["ES2022"],
"target": "ES2022",
"importHelpers": true,
@@ -13,7 +13,7 @@
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
- "moduleResolution": "NodeNext",
+ "moduleResolution": "Bundler",
"skipLibCheck": true,
"jsx": "react",
"esModuleInterop": true
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2255d6ad..f0971dbb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -248,15 +248,18 @@ importers:
packages/tailwindcss-language-service:
dependencies:
+ '@csstools/css-calc':
+ specifier: 2.1.2
+ version: 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
'@csstools/css-parser-algorithms':
- specifier: 2.1.1
- version: 2.1.1(@csstools/css-tokenizer@2.1.1)
+ specifier: 3.0.4
+ version: 3.0.4(@csstools/css-tokenizer@3.0.3)
'@csstools/css-tokenizer':
- specifier: 2.1.1
- version: 2.1.1
+ specifier: 3.0.3
+ version: 3.0.3
'@csstools/media-query-list-parser':
specifier: 2.0.4
- version: 2.0.4(@csstools/css-parser-algorithms@2.1.1(@csstools/css-tokenizer@2.1.1))(@csstools/css-tokenizer@2.1.1)
+ version: 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
'@types/culori':
specifier: ^2.1.0
version: 2.1.1
@@ -440,15 +443,22 @@ packages:
resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==}
engines: {node: '>=6.9.0'}
- '@csstools/css-parser-algorithms@2.1.1':
- resolution: {integrity: sha512-viRnRh02AgO4mwIQb2xQNJju0i+Fh9roNgmbR5xEuG7J3TGgxjnE95HnBLgsFJOJOksvcfxOUCgODcft6Y07cA==}
- engines: {node: ^14 || ^16 || >=18}
+ '@csstools/css-calc@2.1.2':
+ resolution: {integrity: sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==}
+ engines: {node: '>=18'}
peerDependencies:
- '@csstools/css-tokenizer': ^2.1.1
+ '@csstools/css-parser-algorithms': ^3.0.4
+ '@csstools/css-tokenizer': ^3.0.3
- '@csstools/css-tokenizer@2.1.1':
- resolution: {integrity: sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==}
- engines: {node: ^14 || ^16 || >=18}
+ '@csstools/css-parser-algorithms@3.0.4':
+ resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.3
+
+ '@csstools/css-tokenizer@3.0.3':
+ resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==}
+ engines: {node: '>=18'}
'@csstools/media-query-list-parser@2.0.4':
resolution: {integrity: sha512-GyYot6jHgcSDZZ+tLSnrzkR7aJhF2ZW6d+CXH66mjy5WpAQhZD4HDke2OQ36SivGRWlZJpAz7TzbW6OKlEpxAA==}
@@ -800,13 +810,11 @@ packages:
'@parcel/watcher-darwin-arm64@2.5.1':
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
engines: {node: '>= 10.0.0'}
- cpu: [arm64]
os: [darwin]
'@parcel/watcher-darwin-x64@2.5.1':
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [darwin]
'@parcel/watcher-freebsd-x64@2.5.1':
@@ -830,31 +838,26 @@ packages:
'@parcel/watcher-linux-arm64-glibc@2.5.1':
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
engines: {node: '>= 10.0.0'}
- cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-arm64-musl@2.5.1':
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
engines: {node: '>= 10.0.0'}
- cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-x64-glibc@2.5.1':
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [linux]
'@parcel/watcher-linux-x64-musl@2.5.1':
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [linux]
'@parcel/watcher-win32-arm64@2.5.1':
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
engines: {node: '>= 10.0.0'}
- cpu: [arm64]
os: [win32]
'@parcel/watcher-win32-ia32@2.5.1':
@@ -866,7 +869,6 @@ packages:
'@parcel/watcher-win32-x64@2.5.1':
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [win32]
'@parcel/watcher@2.5.1':
@@ -2908,16 +2910,21 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
- '@csstools/css-parser-algorithms@2.1.1(@csstools/css-tokenizer@2.1.1)':
+ '@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-tokenizer': 3.0.3
+
+ '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)':
dependencies:
- '@csstools/css-tokenizer': 2.1.1
+ '@csstools/css-tokenizer': 3.0.3
- '@csstools/css-tokenizer@2.1.1': {}
+ '@csstools/css-tokenizer@3.0.3': {}
- '@csstools/media-query-list-parser@2.0.4(@csstools/css-parser-algorithms@2.1.1(@csstools/css-tokenizer@2.1.1))(@csstools/css-tokenizer@2.1.1)':
+ '@csstools/media-query-list-parser@2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
dependencies:
- '@csstools/css-parser-algorithms': 2.1.1(@csstools/css-tokenizer@2.1.1)
- '@csstools/css-tokenizer': 2.1.1
+ '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-tokenizer': 3.0.3
'@esbuild/aix-ppc64@0.21.5':
optional: true
From 5c825459e8dbacef374f0b6d6a1967223685dedb Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 23 Apr 2025 11:21:36 -0400
Subject: [PATCH 46/93] Guard against infinitely recursive theme key lookup
(#1332)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When constructing the design system we compute color swatches for all
classes. This process involves:
- Computing the CSS for a utility
- Inlining any values from the theme, recursively
- Scanning for CSS color syntax that we can compute a swatch for
We guard against directly self-recursive replacements because of how the
replacement itself functions. However, if you wrapped the variable in
something then we would effectively recurse into the expression and try
to replace the variable again. This caused the expansion to then be
wrapped and then try again resulting in an infinite loop.
This fixes it by keeping track of seen variables during a replacement
and if we've seen it _and_ the expansion contains a var(…) anywhere
we'll only replace it once. This logic is not perfect as the thing that
matters is that the replacement _itself_ results in a recursion but this
is good enough for now.
Fixes #1329
---
.../src/util/rewriting/index.test.ts | 46 +++++++++++++++++--
.../src/util/rewriting/var-fallbacks.ts | 13 +++++-
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
3 files changed, 56 insertions(+), 4 deletions(-)
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts b/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
index 3adbdb44..de51a2b7 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/index.test.ts
@@ -79,9 +79,49 @@ test('replacing CSS variables with their fallbacks (when they have them)', () =>
expect(replaceCssVarsWithFallbacks(state, 'var(--level-3)')).toBe('blue')
// Circular replacements don't cause infinite loops
- expect(replaceCssVarsWithFallbacks(state, 'var(--circular-1)')).toBe('var(--circular-3)')
- expect(replaceCssVarsWithFallbacks(state, 'var(--circular-2)')).toBe('var(--circular-1)')
- expect(replaceCssVarsWithFallbacks(state, 'var(--circular-3)')).toBe('var(--circular-2)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--circular-1)')).toBe('var(--circular-1)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--circular-2)')).toBe('var(--circular-2)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--circular-3)')).toBe('var(--circular-3)')
+})
+
+test('recursive theme replacements', () => {
+ let map = new Map([
+ ['--color-a', 'var(--color-a)'],
+ ['--color-b', 'rgb(var(--color-b))'],
+ ['--color-c', 'rgb(var(--channel) var(--channel) var(--channel))'],
+ ['--channel', '255'],
+
+ ['--color-d', 'rgb(var(--indirect) var(--indirect) var(--indirect))'],
+ ['--indirect', 'var(--channel)'],
+ ['--channel', '255'],
+
+ ['--mutual-a', 'calc(var(--mutual-b) * 1)'],
+ ['--mutual-b', 'calc(var(--mutual-a) + 1)'],
+ ])
+
+ let state: State = {
+ enabled: true,
+ designSystem: {
+ theme: { prefix: null } as any,
+ resolveThemeValue: (name) => map.get(name) ?? null,
+ } as DesignSystem,
+ }
+
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-a)')).toBe('var(--color-a)')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-b)')).toBe('rgb(var(--color-b))')
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-c)')).toBe('rgb(255 255 255)')
+
+ // This one is wrong but fixing it without breaking the infinite recursion guard is complex
+ expect(replaceCssVarsWithFallbacks(state, 'var(--color-d)')).toBe(
+ 'rgb(255 var(--indirect) var(--indirect))',
+ )
+
+ expect(replaceCssVarsWithFallbacks(state, 'var(--mutual-a)')).toBe(
+ 'calc(calc(var(--mutual-a) + 1) * 1)',
+ )
+ expect(replaceCssVarsWithFallbacks(state, 'var(--mutual-b)')).toBe(
+ 'calc(calc(var(--mutual-b) * 1) + 1)',
+ )
})
test('Evaluating CSS calc expressions', () => {
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 91dcc91d..2eb8c918 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/var-fallbacks.ts
@@ -3,14 +3,25 @@ import { resolveVariableValue } from './lookup'
import { replaceCssVars } from './replacements'
export function replaceCssVarsWithFallbacks(state: State, str: string): string {
+ let seen = new Set()
+
return replaceCssVars(str, {
replace({ name, fallback }) {
// Replace with the value from the design system first. The design system
// take precedences over other sources as that emulates the behavior of a
// browser where the fallback is only used if the variable is defined.
if (state.designSystem && name.startsWith('--')) {
+ // TODO: This isn't quite right as we might skip expanding a variable
+ // that should be expanded
+ if (seen.has(name)) return null
let value = resolveVariableValue(state.designSystem, name)
- if (value !== null) return value
+ if (value !== null) {
+ if (value.includes('var(')) {
+ seen.add(name)
+ }
+
+ return value
+ }
}
if (fallback) {
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 4162495c..a734adf3 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -4,6 +4,7 @@
- Warn when using a blocklisted class in v4 ([#1310](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1310))
- Support class function hovers in Svelte and HTML `` blocks ([#1311](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1311))
+- Guard against recursive theme key lookup ([#1332](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1332))
# 0.14.15
From 7afe2267750022b6acda8de300b0982ed458881a Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 23 Apr 2025 11:29:56 -0400
Subject: [PATCH 47/93] 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 a734adf3..8d59ad80 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -4,6 +4,7 @@
- Warn when using a blocklisted class in v4 ([#1310](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1310))
- Support class function hovers in Svelte and HTML `` blocks ([#1311](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1311))
+- Evaluate complex `calc(…)` expressions in completions and equivalents ([#1316](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1316))
- Guard against recursive theme key lookup ([#1332](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1332))
# 0.14.15
From a3810154913309b9084d535e86553cf6e861521c Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 24 Apr 2025 13:23:12 -0400
Subject: [PATCH 48/93] 0.14.16
---
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 0813d13e..8d2617f9 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.15",
+ "version": "0.14.16",
"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 4a11e6c7..f7928b3e 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.15",
+ "version": "0.14.16",
"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 8d59ad80..122181c8 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,6 +2,10 @@
## Prerelease
+- Nothing yet!
+
+# 0.14.16
+
- Warn when using a blocklisted class in v4 ([#1310](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1310))
- Support class function hovers in Svelte and HTML `` blocks ([#1311](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1311))
- Evaluate complex `calc(…)` expressions in completions and equivalents ([#1316](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1316))
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index d3d30911..079055b0 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "vscode-tailwindcss",
- "version": "0.14.15",
+ "version": "0.14.16",
"displayName": "Tailwind CSS IntelliSense",
"description": "Intelligent Tailwind CSS tooling for VS Code",
"author": "Brad Cornes ",
From f511faa48fa21223f7edbc51635bd4d7b5a08d0f Mon Sep 17 00:00:00 2001
From: Akat <24731539+AkatQuas@users.noreply.github.com>
Date: Tue, 29 Apr 2025 22:44:41 +0800
Subject: [PATCH 49/93] Launch extension with additional options when debugging
(#1339)
This PR add several options for starting extension in VS Code.
Co-authored-by: TaotaoDong
---
.vscode/launch.json | 10 +++++++++-
.vscode/tasks.json | 3 +++
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/.vscode/launch.json b/.vscode/launch.json
index fd173bdc..04478652 100755
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,7 +7,15 @@
"request": "launch",
"name": "Launch Client",
"runtimeExecutable": "${execPath}",
- "args": ["--extensionDevelopmentPath=${workspaceRoot}/packages/vscode-tailwindcss"],
+ "args": [
+ // enable this flag if you want to activate the extension only when you are debugging the extension
+ // "--disable-extensions",
+ "--disable-updates",
+ "--disable-workspace-trust",
+ "--skip-release-notes",
+ "--skip-welcome",
+ "--extensionDevelopmentPath=${workspaceRoot}/packages/vscode-tailwindcss"
+ ],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": ["${workspaceRoot}/packages/vscode-tailwindcss/dist/**/*.js"],
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 1abde264..aec43080 100755
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -23,6 +23,9 @@
"panel": "dedicated",
"reveal": "never"
},
+ "options": {
+ "cwd": "${workspaceFolder}/packages/vscode-tailwindcss"
+ },
"problemMatcher": ["$tsc-watch"]
}
]
From f432c77c5567246056db1e8ba9c28aaae1bd80ae Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Mon, 5 May 2025 10:04:45 -0400
Subject: [PATCH 50/93] Rework capability registration and handler setup
(#1327)
This PR changes how capability registration works such that:
- Dynamic registrations are honored on a per-capability basis rather
than only when the client supports several capabilities being
dynamically registered
- Most capabilities are registered _once_ after project discovery and
only re-registered after a server restart
- The completion capability will not re-register itself if trigger
characters have not changed
This should, in most cases, mean that *all* supported capabilities are
dynamically registered once at startup. Trigger characters only change
for projects where the variant separator is customizable, has been
customized, and includes a character that is not already considered a
trigger character (v4 does *not* allow customizing the variant separator
but v3 did)
Fixes #1319
- [x] Need to look into creating a reproduction w/ 500-ish v3 config
files in it to see if I can repro the original issue. We call
`updateCapabilities` after a build finishes and its likely that this
is/was slow enough with IntelliSense trying to handle 500 separate v3
projects at once
---
.../src/projects.ts | 5 +
.../tailwindcss-language-server/src/tw.ts | 203 +++++++++++-------
.../tests/env/capabilities.test.ts | 184 ++++++++++++++++
.../tests/env/restart.test.ts | 18 ++
.../tests/utils/client.ts | 79 +++++++
packages/vscode-tailwindcss/CHANGELOG.md | 2 +-
6 files changed, 410 insertions(+), 81 deletions(-)
create mode 100644 packages/tailwindcss-language-server/tests/env/capabilities.test.ts
diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts
index 04160569..21e7bb3b 100644
--- a/packages/tailwindcss-language-server/src/projects.ts
+++ b/packages/tailwindcss-language-server/src/projects.ts
@@ -1086,6 +1086,11 @@ export async function createProjectService(
refreshDiagnostics()
updateCapabilities()
+
+ let isTestMode = params.initializationOptions?.testMode ?? false
+ if (!isTestMode) return
+
+ connection.sendNotification('@/tailwindCSS/projectReloaded')
}
for (let entry of projectConfig.config.entries) {
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index 20ac0158..792c887d 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -21,6 +21,8 @@ import type {
WorkspaceFolder,
CodeLensParams,
CodeLens,
+ ServerCapabilities,
+ ClientCapabilities,
} from 'vscode-languageserver/node'
import {
CompletionRequest,
@@ -624,6 +626,8 @@ export class TW {
console.log(`[Global] Initializing projects...`)
+ await this.updateCommonCapabilities()
+
// init projects for documents that are _already_ open
let readyDocuments: string[] = []
let enabledProjectCount = 0
@@ -640,8 +644,6 @@ export class TW {
console.log(`[Global] Initialized ${enabledProjectCount} projects`)
- this.setupLSPHandlers()
-
this.disposables.push(
this.connection.onDidChangeConfiguration(async ({ settings }) => {
let previousExclude = globalSettings.tailwindCSS.files.exclude
@@ -763,7 +765,7 @@ export class TW {
this.connection,
params,
this.documentService,
- () => this.updateCapabilities(),
+ () => this.updateProjectCapabilities(),
() => {
for (let document of this.documentService.getAllDocuments()) {
let project = this.getProject(document)
@@ -810,9 +812,7 @@ export class TW {
}
setupLSPHandlers() {
- if (this.lspHandlersAdded) {
- return
- }
+ if (this.lspHandlersAdded) return
this.lspHandlersAdded = true
this.connection.onHover(this.onHover.bind(this))
@@ -858,43 +858,84 @@ export class TW {
}
}
- private updateCapabilities() {
- if (!supportsDynamicRegistration(this.initializeParams)) {
- this.connection.client.register(DidChangeConfigurationNotification.type, undefined)
- return
+ // Common capabilities are always supported by the language server and do not
+ // require any project-specific information to know how to configure them.
+ //
+ // These capabilities will stay valid until/unless the server has to restart
+ // in which case they'll be unregistered and then re-registered once project
+ // discovery has completed
+ private commonRegistrations: BulkUnregistration | undefined
+ private async updateCommonCapabilities() {
+ let capabilities = BulkRegistration.create()
+
+ let client = this.initializeParams.capabilities
+
+ if (client.textDocument?.hover?.dynamicRegistration) {
+ capabilities.add(HoverRequest.type, { documentSelector: null })
}
- if (this.registrations) {
- this.registrations.then((r) => r.dispose())
+ if (client.textDocument?.colorProvider?.dynamicRegistration) {
+ capabilities.add(DocumentColorRequest.type, { documentSelector: null })
}
- let projects = Array.from(this.projects.values())
+ if (client.textDocument?.codeAction?.dynamicRegistration) {
+ capabilities.add(CodeActionRequest.type, { documentSelector: null })
+ }
- let capabilities = BulkRegistration.create()
+ if (client.textDocument?.codeLens?.dynamicRegistration) {
+ capabilities.add(CodeLensRequest.type, { documentSelector: null })
+ }
+
+ if (client.textDocument?.documentLink?.dynamicRegistration) {
+ capabilities.add(DocumentLinkRequest.type, { documentSelector: null })
+ }
+
+ if (client.workspace?.didChangeConfiguration?.dynamicRegistration) {
+ capabilities.add(DidChangeConfigurationNotification.type, undefined)
+ }
- // TODO: We should *not* be re-registering these capabilities
- // IDEA: These should probably be static registrations up front
- capabilities.add(HoverRequest.type, { documentSelector: null })
- capabilities.add(DocumentColorRequest.type, { documentSelector: null })
- capabilities.add(CodeActionRequest.type, { documentSelector: null })
- capabilities.add(CodeLensRequest.type, { documentSelector: null })
- capabilities.add(DocumentLinkRequest.type, { documentSelector: null })
- capabilities.add(DidChangeConfigurationNotification.type, undefined)
-
- // TODO: Only re-register this if trigger characters change
- capabilities.add(CompletionRequest.type, {
+ this.commonRegistrations = await this.connection.client.register(capabilities)
+ }
+
+ // These capabilities depend on the projects we've found to appropriately
+ // configure them. This may mean collecting information from all discovered
+ // projects to determine what we can do and how
+ private updateProjectCapabilities() {
+ this.updateTriggerCharacters()
+ }
+
+ private lastTriggerCharacters: Set | undefined
+ private completionRegistration: Disposable | undefined
+ private async updateTriggerCharacters() {
+ // If the client does not suppory dynamic registration of completions then
+ // we cannot update the set of trigger characters
+ let client = this.initializeParams.capabilities
+ if (!client.textDocument?.completion?.dynamicRegistration) return
+
+ // The new set of trigger characters is all the static ones plus
+ // any characters from any separator in v3 config
+ let chars = new Set(TRIGGER_CHARACTERS)
+
+ for (let project of this.projects.values()) {
+ let sep = project.state.separator
+ if (typeof sep !== 'string') continue
+
+ sep = sep.slice(-1)
+ if (!sep) continue
+
+ chars.add(sep)
+ }
+
+ // If the trigger characters haven't changed then we don't need to do anything
+ if (equal(Array.from(chars), Array.from(this.lastTriggerCharacters ?? []))) return
+ this.lastTriggerCharacters = chars
+
+ this.completionRegistration?.dispose()
+ this.completionRegistration = await this.connection.client.register(CompletionRequest.type, {
documentSelector: null,
resolveProvider: true,
- triggerCharacters: [
- ...TRIGGER_CHARACTERS,
- ...projects
- .map((project) => project.state.separator)
- .filter((sep) => typeof sep === 'string')
- .map((sep) => sep.slice(-1)),
- ].filter(Boolean),
+ triggerCharacters: Array.from(chars),
})
-
- this.registrations = this.connection.client.register(capabilities)
}
private getProject(document: TextDocumentIdentifier): ProjectService {
@@ -1016,47 +1057,58 @@ export class TW {
this.connection.onInitialize(async (params: InitializeParams): Promise => {
this.initializeParams = params
- if (supportsDynamicRegistration(params)) {
- return {
- capabilities: {
- textDocumentSync: TextDocumentSyncKind.Full,
- workspace: {
- workspaceFolders: {
- changeNotifications: true,
- },
- },
- },
- }
- }
-
this.setupLSPHandlers()
return {
- capabilities: {
- textDocumentSync: TextDocumentSyncKind.Full,
- hoverProvider: true,
- colorProvider: true,
- codeActionProvider: true,
- codeLensProvider: {
- resolveProvider: false,
- },
- documentLinkProvider: {},
- completionProvider: {
- resolveProvider: true,
- triggerCharacters: [...TRIGGER_CHARACTERS, ':'],
- },
- workspace: {
- workspaceFolders: {
- changeNotifications: true,
- },
- },
- },
+ capabilities: this.computeServerCapabilities(params.capabilities),
}
})
this.connection.onInitialized(() => this.init())
}
+ computeServerCapabilities(client: ClientCapabilities) {
+ let capabilities: ServerCapabilities = {
+ textDocumentSync: TextDocumentSyncKind.Full,
+ workspace: {
+ workspaceFolders: {
+ changeNotifications: true,
+ },
+ },
+ }
+
+ if (!client.textDocument?.hover?.dynamicRegistration) {
+ capabilities.hoverProvider = true
+ }
+
+ if (!client.textDocument?.colorProvider?.dynamicRegistration) {
+ capabilities.colorProvider = true
+ }
+
+ if (!client.textDocument?.codeAction?.dynamicRegistration) {
+ capabilities.codeActionProvider = true
+ }
+
+ if (!client.textDocument?.codeLens?.dynamicRegistration) {
+ capabilities.codeLensProvider = {
+ resolveProvider: false,
+ }
+ }
+
+ if (!client.textDocument?.completion?.dynamicRegistration) {
+ capabilities.completionProvider = {
+ resolveProvider: true,
+ triggerCharacters: [...TRIGGER_CHARACTERS, ':'],
+ }
+ }
+
+ if (!client.textDocument?.documentLink?.dynamicRegistration) {
+ capabilities.documentLinkProvider = {}
+ }
+
+ return capabilities
+ }
+
listen() {
this.connection.listen()
}
@@ -1070,10 +1122,11 @@ export class TW {
this.refreshDiagnostics()
- if (this.registrations) {
- this.registrations.then((r) => r.dispose())
- this.registrations = undefined
- }
+ this.commonRegistrations?.dispose()
+ this.commonRegistrations = undefined
+
+ this.completionRegistration?.dispose()
+ this.completionRegistration = undefined
this.disposables.forEach((d) => d.dispose())
this.disposables.length = 0
@@ -1106,13 +1159,3 @@ export class TW {
}
}
}
-
-function supportsDynamicRegistration(params: InitializeParams): boolean {
- return (
- params.capabilities.textDocument?.hover?.dynamicRegistration &&
- params.capabilities.textDocument?.colorProvider?.dynamicRegistration &&
- params.capabilities.textDocument?.codeAction?.dynamicRegistration &&
- params.capabilities.textDocument?.completion?.dynamicRegistration &&
- params.capabilities.textDocument?.documentLink?.dynamicRegistration
- )
-}
diff --git a/packages/tailwindcss-language-server/tests/env/capabilities.test.ts b/packages/tailwindcss-language-server/tests/env/capabilities.test.ts
new file mode 100644
index 00000000..22eac608
--- /dev/null
+++ b/packages/tailwindcss-language-server/tests/env/capabilities.test.ts
@@ -0,0 +1,184 @@
+import { expect } from 'vitest'
+import { defineTest, js } from '../../src/testing'
+import { createClient } from '../utils/client'
+import * as fs from 'node:fs/promises'
+
+defineTest({
+ name: 'Changing the separator registers new trigger characters',
+ fs: {
+ 'tailwind.config.js': js`
+ module.exports = {
+ separator: ':',
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ root, client }) => {
+ // Initially don't have any registered capabilities because dynamic
+ // registration is delayed until after project initialization
+ expect(client.serverCapabilities).toEqual([])
+
+ // We open a document so a project gets initialized
+ await client.open({
+ lang: 'html',
+ text: '',
+ })
+
+ // And now capabilities are registered
+ expect(client.serverCapabilities).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ method: 'textDocument/hover',
+ }),
+
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', ':'],
+ },
+ }),
+ ]),
+ )
+
+ let countBeforeChange = client.serverCapabilities.length
+ let capabilitiesDidChange = Promise.race([
+ new Promise
((_, reject) => {
+ setTimeout(() => reject('capabilities did not change within 5s'), 5_000)
+ }),
+
+ new Promise((resolve) => {
+ client.onServerCapabilitiesChanged(() => {
+ if (client.serverCapabilities.length !== countBeforeChange) return
+ resolve()
+ })
+ }),
+ ])
+
+ await fs.writeFile(
+ `${root}/tailwind.config.js`,
+ js`
+ module.exports = {
+ separator: '_',
+ }
+ `,
+ )
+
+ // After changing the config
+ client.notifyChangedFiles({
+ changed: [`${root}/tailwind.config.js`],
+ })
+
+ // We should see that the capabilities have changed
+ await capabilitiesDidChange
+
+ // Capabilities are now registered
+ expect(client.serverCapabilities).toContainEqual(
+ expect.objectContaining({
+ method: 'textDocument/hover',
+ }),
+ )
+
+ expect(client.serverCapabilities).toContainEqual(
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', '_'],
+ },
+ }),
+ )
+
+ expect(client.serverCapabilities).not.toContainEqual(
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', ':'],
+ },
+ }),
+ )
+ },
+})
+
+defineTest({
+ name: 'Config updates do not register new trigger characters if the separator has not changed',
+ fs: {
+ 'tailwind.config.js': js`
+ module.exports = {
+ separator: ':',
+ theme: {
+ colors: {
+ primary: '#f00',
+ }
+ }
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ root, client }) => {
+ // Initially don't have any registered capabilities because dynamic
+ // registration is delayed until after project initialization
+ expect(client.serverCapabilities).toEqual([])
+
+ // We open a document so a project gets initialized
+ await client.open({
+ lang: 'html',
+ text: '',
+ })
+
+ // And now capabilities are registered
+ expect(client.serverCapabilities).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ method: 'textDocument/hover',
+ }),
+
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', ':'],
+ },
+ }),
+ ]),
+ )
+
+ let idsBefore = client.serverCapabilities.map((cap) => cap.id)
+
+ await fs.writeFile(
+ `${root}/tailwind.config.js`,
+ js`
+ module.exports = {
+ separator: ':',
+ theme: {
+ colors: {
+ primary: '#0f0',
+ }
+ }
+ }
+ `,
+ )
+
+ let didReload = new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/projectReloaded', resolve)
+ })
+
+ // After changing the config
+ client.notifyChangedFiles({
+ changed: [`${root}/tailwind.config.js`],
+ })
+
+ // Wait for the project to finish building
+ await didReload
+
+ // No capabilities should have changed
+ let idsAfter = client.serverCapabilities.map((cap) => cap.id)
+
+ expect(idsBefore).toEqual(idsAfter)
+ },
+})
diff --git a/packages/tailwindcss-language-server/tests/env/restart.test.ts b/packages/tailwindcss-language-server/tests/env/restart.test.ts
index 49b632c3..35b595f8 100644
--- a/packages/tailwindcss-language-server/tests/env/restart.test.ts
+++ b/packages/tailwindcss-language-server/tests/env/restart.test.ts
@@ -140,6 +140,9 @@ defineTest({
},
})
+ expect(client.serverCapabilities).not.toEqual([])
+ let ids1 = client.serverCapabilities.map((cap) => cap.id)
+
// Remove the CSS file
let didRestart = new Promise((resolve) => {
client.conn.onNotification('@/tailwindCSS/serverRestarted', resolve)
@@ -147,6 +150,9 @@ defineTest({
await fs.unlink(path.resolve(root, 'app.css'))
await didRestart
+ expect(client.serverCapabilities).not.toEqual([])
+ let ids2 = client.serverCapabilities.map((cap) => cap.id)
+
//
// ^
let hover2 = await doc.hover({ line: 0, character: 13 })
@@ -164,11 +170,23 @@ defineTest({
)
await didRestartAgain
+ expect(client.serverCapabilities).not.toEqual([])
+ let ids3 = client.serverCapabilities.map((cap) => cap.id)
+
await new Promise((resolve) => setTimeout(resolve, 500))
//
// ^
let hover3 = await doc.hover({ line: 0, character: 13 })
expect(hover3).toEqual(null)
+
+ expect(ids1).not.toContainEqual(expect.toBeOneOf(ids2))
+ expect(ids1).not.toContainEqual(expect.toBeOneOf(ids3))
+
+ expect(ids2).not.toContainEqual(expect.toBeOneOf(ids1))
+ expect(ids2).not.toContainEqual(expect.toBeOneOf(ids3))
+
+ expect(ids3).not.toContainEqual(expect.toBeOneOf(ids1))
+ expect(ids3).not.toContainEqual(expect.toBeOneOf(ids2))
},
})
diff --git a/packages/tailwindcss-language-server/tests/utils/client.ts b/packages/tailwindcss-language-server/tests/utils/client.ts
index 760b805b..681843a8 100644
--- a/packages/tailwindcss-language-server/tests/utils/client.ts
+++ b/packages/tailwindcss-language-server/tests/utils/client.ts
@@ -6,16 +6,21 @@ import {
CompletionList,
CompletionParams,
Diagnostic,
+ DidChangeWatchedFilesNotification,
Disposable,
DocumentLink,
DocumentLinkRequest,
DocumentSymbol,
DocumentSymbolRequest,
+ FileChangeType,
+ FileEvent,
Hover,
NotificationHandler,
ProtocolConnection,
PublishDiagnosticsParams,
+ Registration,
SymbolInformation,
+ UnregistrationRequest,
WorkspaceFolder,
} from 'vscode-languageserver'
import type { Position } from 'vscode-languageserver-textdocument'
@@ -83,6 +88,12 @@ export interface DocumentDescriptor {
settings?: Settings
}
+export interface ChangedFiles {
+ created?: string[]
+ changed?: string[]
+ deleted?: string[]
+}
+
export interface ClientDocument {
/**
* The URI to the document
@@ -191,6 +202,21 @@ export interface Client extends ClientWorkspace {
*/
readonly conn: ProtocolConnection
+ /**
+ * Get the currently registered server capabilities
+ */
+ serverCapabilities: Registration[]
+
+ /**
+ * Get the currently registered server capabilities
+ */
+ onServerCapabilitiesChanged(cb: () => void): void
+
+ /**
+ * Tell the server that files on disk have changed
+ */
+ notifyChangedFiles(changes: ChangedFiles): Promise
+
/**
* Get a workspace by name
*/
@@ -428,12 +454,40 @@ export async function createClient(opts: ClientOptions): Promise {
})
}
+ let serverCapabilityChangeCallbacks: (() => void)[] = []
+
+ function onServerCapabilitiesChanged(cb: () => void) {
+ serverCapabilityChangeCallbacks.push(cb)
+ }
+
+ let registeredCapabilities: Registration[] = []
+
conn.onRequest(RegistrationRequest.type, ({ registrations }) => {
trace('Registering capabilities')
for (let registration of registrations) {
+ registeredCapabilities.push(registration)
+ trace('-', registration.method)
+ }
+
+ for (let cb of serverCapabilityChangeCallbacks) cb()
+ })
+
+ conn.onRequest(UnregistrationRequest.type, ({ unregisterations }) => {
+ trace('Unregistering capabilities')
+
+ let idsToRemove = new Set()
+
+ for (let registration of unregisterations) {
+ idsToRemove.add(registration.id)
trace('-', registration.method)
}
+
+ registeredCapabilities = registeredCapabilities.filter(
+ (capability) => !idsToRemove.has(capability.id),
+ )
+
+ for (let cb of serverCapabilityChangeCallbacks) cb()
})
// TODO: Remove this its a hack
@@ -493,8 +547,33 @@ export async function createClient(opts: ClientOptions): Promise {
await initPromise
}
+ function notifyChangedFiles(changes: ChangedFiles) {
+ let events: FileEvent[] = []
+
+ for (const path of changes?.created ?? []) {
+ events.push({ uri: URI.file(path).toString(), type: FileChangeType.Created })
+ }
+
+ for (const path of changes?.changed ?? []) {
+ events.push({ uri: URI.file(path).toString(), type: FileChangeType.Changed })
+ }
+
+ for (const path of changes?.deleted ?? []) {
+ events.push({ uri: URI.file(path).toString(), type: FileChangeType.Deleted })
+ }
+
+ return conn.sendNotification(DidChangeWatchedFilesNotification.type, {
+ changes: events,
+ })
+ }
+
return {
...clientWorkspaces[0],
+ get serverCapabilities() {
+ return registeredCapabilities
+ },
+ onServerCapabilitiesChanged,
+ notifyChangedFiles,
workspace,
updateSettings,
}
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 122181c8..53c9592a 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,7 +2,7 @@
## Prerelease
-- Nothing yet!
+- Improve dynamic capability registration in the language server ([#1327](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1327))
# 0.14.16
From 03ad23dc955ec87447d7593782ea438057057639 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 8 May 2025 11:04:43 -0400
Subject: [PATCH 51/93] Update default file exclusions (#1336)
IntelliSense, by default, ignores files for:
- Version control (`.git`, `.hg`, `.svn`)
- NPM (`node_modules`)
I'm adding three more groups to this:
- Python virtual environment folders (`.venv`, `venv`)
- Yarn v2+ metadata & caches (`.yarn`)
- *some* build caches (`.next`, `.turbo`, `.parcel-cache`,
`__pycache__`)
Discovered that we should update these when triaging #1312 (that one was
specifically about python virtual envs)
---
.../tests/env/ignored.test.ts | 111 ++++++++++++++++++
.../src/util/state.ts | 35 +++++-
packages/vscode-tailwindcss/CHANGELOG.md | 3 +
3 files changed, 148 insertions(+), 1 deletion(-)
create mode 100644 packages/tailwindcss-language-server/tests/env/ignored.test.ts
diff --git a/packages/tailwindcss-language-server/tests/env/ignored.test.ts b/packages/tailwindcss-language-server/tests/env/ignored.test.ts
new file mode 100644
index 00000000..f5f7d01f
--- /dev/null
+++ b/packages/tailwindcss-language-server/tests/env/ignored.test.ts
@@ -0,0 +1,111 @@
+import { expect } from 'vitest'
+import { css, defineTest } from '../../src/testing'
+import { createClient } from '../utils/client'
+import dedent from 'dedent'
+
+let ignored = css`
+ @import 'tailwindcss';
+ @theme {
+ --color-primary: #c0ffee;
+ }
+`
+
+let found = css`
+ @import 'tailwindcss';
+ @theme {
+ --color-primary: rebeccapurple;
+ }
+`
+
+defineTest({
+ name: 'various build folders and caches are ignored by default',
+ fs: {
+ // All of these should be ignored
+ 'aaa/.git/app.css': ignored,
+ 'aaa/.hg/app.css': ignored,
+ 'aaa/.svn/app.css': ignored,
+ 'aaa/node_modules/app.css': ignored,
+ 'aaa/.yarn/app.css': ignored,
+ 'aaa/.venv/app.css': ignored,
+ 'aaa/venv/app.css': ignored,
+ 'aaa/.next/app.css': ignored,
+ 'aaa/.parcel-cache/app.css': ignored,
+ 'aaa/.svelte-kit/app.css': ignored,
+ 'aaa/.turbo/app.css': ignored,
+ 'aaa/__pycache__/app.css': ignored,
+
+ // But this one should not be
+ 'zzz/app.css': found,
+ },
+
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '',
+ })
+
+ //
+ // ^
+ let hover = await doc.hover({ line: 0, character: 13 })
+ expect(hover).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .bg-primary {
+ background-color: var(--color-primary) /* rebeccapurple = #663399 */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 22 },
+ },
+ })
+ },
+})
+
+defineTest({
+ name: 'ignores can be overridden',
+ fs: {
+ 'aaa/app.css': ignored,
+ 'bbb/.git/app.css': found,
+ },
+
+ prepare: async ({ root }) => ({
+ client: await createClient({
+ root,
+ settings: {
+ tailwindCSS: {
+ files: {
+ exclude: ['**/aaa/**'],
+ },
+ },
+ },
+ }),
+ }),
+ handle: async ({ client }) => {
+ let doc = await client.open({
+ lang: 'html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hover = await doc.hover({ line: 0, character: 13 })
+ expect(hover).toEqual({
+ contents: {
+ language: 'css',
+ value: dedent`
+ .bg-primary {
+ background-color: var(--color-primary) /* rebeccapurple = #663399 */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 22 },
+ },
+ })
+ },
+})
diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts
index 26cc2ed0..ecd9d0d8 100644
--- a/packages/tailwindcss-language-service/src/util/state.ts
+++ b/packages/tailwindcss-language-service/src/util/state.ts
@@ -208,7 +208,40 @@ export function getDefaultTailwindSettings(): Settings {
},
showPixelEquivalents: true,
includeLanguages: {},
- files: { exclude: ['**/.git/**', '**/node_modules/**', '**/.hg/**', '**/.svn/**'] },
+ files: {
+ exclude: [
+ // These paths need to be universally ignorable. This means that we
+ // should only consider hidden folders with a commonly understood
+ // meaning unless there is a very good reason to do otherwise.
+ //
+ // This means that things like `build`, `target`, `cache`, etc… are
+ // not appropriate to include even though _in many cases_ they might
+ // be ignorable. The names are too general and ignoring them could
+ // cause us to ignore actual project files.
+
+ // Version Control
+ '**/.git/**',
+ '**/.hg/**',
+ '**/.svn/**',
+
+ // NPM
+ '**/node_modules/**',
+
+ // Yarn v2+ metadata & caches
+ '**/.yarn/**',
+
+ // Python Virtual Environments
+ '**/.venv/**',
+ '**/venv/**',
+
+ // Build caches
+ '**/.next/**',
+ '**/.parcel-cache/**',
+ '**/.svelte-kit/**',
+ '**/.turbo/**',
+ '**/__pycache__/**',
+ ],
+ },
experimental: {
classRegex: [],
configFile: null,
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 53c9592a..e57ef565 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -3,6 +3,9 @@
## Prerelease
- Improve dynamic capability registration in the language server ([#1327](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1327))
+- Ignore Python virtual env directories by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
+- Ignore Yarn v2+ metadata & cache directories by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
+- Ignore some build caches by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
# 0.14.16
From dfce548a0f20811d7c40b1b1ec2e07d43a99f470 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 8 May 2025 14:21:46 -0400
Subject: [PATCH 52/93] Enable isolated declarations for
`@tailwindcss/language-service` (#1348)
Just some code cleanup (also a bugfix revealed by doing this)
---
.../src/testing/index.ts | 12 +--
.../provideInvalidApplyCodeActions.ts | 2 +-
.../src/completionProvider.ts | 2 +-
.../diagnostics/getCssConflictDiagnostics.ts | 2 +-
.../src/metadata/extensions.ts | 4 +-
.../src/util/absoluteRange.ts | 2 +-
.../src/util/braceLevel.ts | 4 +-
.../src/util/colorEquivalents.ts | 84 +++++++++--------
.../src/util/css.ts | 4 +-
.../src/util/cssObjToAst.ts | 4 +-
.../src/util/estimated-class-size.ts | 2 +-
.../src/util/flagEnabled.ts | 2 +-
.../src/util/format-bytes.ts | 2 +-
.../src/util/getLanguageBoundaries.ts | 2 +-
.../src/util/jit.ts | 2 +-
.../src/util/language-blocks.ts | 2 +-
.../src/util/languages.ts | 17 ++--
.../src/util/lexers.ts | 6 +-
.../src/util/pixelEquivalents.ts | 92 +++++++++----------
.../src/util/rewriting/add-theme-values.ts | 2 +-
.../src/util/rewriting/inline-theme-values.ts | 2 +-
.../src/util/rewriting/lookup.ts | 2 +-
.../src/util/segment.ts | 2 +-
.../src/util/splice-changes-into-string.ts | 2 +-
.../src/util/test-utils.ts | 20 ++--
.../src/util/v4/ast.ts | 2 +-
.../tsconfig.json | 3 +-
27 files changed, 146 insertions(+), 136 deletions(-)
diff --git a/packages/tailwindcss-language-server/src/testing/index.ts b/packages/tailwindcss-language-server/src/testing/index.ts
index 0a11bb9d..efce9fd2 100644
--- a/packages/tailwindcss-language-server/src/testing/index.ts
+++ b/packages/tailwindcss-language-server/src/testing/index.ts
@@ -3,7 +3,7 @@ import * as os from 'node:os'
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import * as proc from 'node:child_process'
-import dedent from 'dedent'
+import dedent, { type Dedent } from 'dedent'
export interface TestUtils> {
/** The "cwd" for this test */
@@ -160,8 +160,8 @@ async function installDependenciesIn(dir: string) {
})
}
-export const css = dedent
-export const scss = dedent
-export const html = dedent
-export const js = dedent
-export const json = dedent
+export const css: Dedent = dedent
+export const scss: Dedent = dedent
+export const html: Dedent = dedent
+export const js: Dedent = dedent
+export const json: Dedent = dedent
diff --git a/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts b/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts
index dcbda0e0..0ead7b36 100644
--- a/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts
+++ b/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts
@@ -212,7 +212,7 @@ function classNameToAst(
obj = rule
}
- return cssObjToAst(obj, state.modules.postcss)
+ return cssObjToAst(obj, state.modules.postcss.module)
}
function appendPseudosToSelector(selector: string, pseudos: string[]): string | null {
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index 843e9a8e..bf9685a9 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -2230,7 +2230,7 @@ export async function doComplete(
document: TextDocument,
position: Position,
context?: CompletionContext,
-) {
+): Promise {
if (state === null) return { items: [], isIncomplete: false }
const result =
diff --git a/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts
index ec6dc166..dcf61089 100644
--- a/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts
+++ b/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts
@@ -239,7 +239,7 @@ export function visit(
nodes: postcss.AnyNode[],
cb: (node: postcss.AnyNode, path: postcss.AnyNode[]) => void,
path: postcss.AnyNode[] = [],
-) {
+): void {
for (let child of nodes) {
path = [...path, child]
cb(child, path)
diff --git a/packages/tailwindcss-language-service/src/metadata/extensions.ts b/packages/tailwindcss-language-service/src/metadata/extensions.ts
index 15babe94..34726d8f 100644
--- a/packages/tailwindcss-language-service/src/metadata/extensions.ts
+++ b/packages/tailwindcss-language-service/src/metadata/extensions.ts
@@ -72,5 +72,5 @@ let templateExtensions = [
'rs',
]
-export const IS_SCRIPT_SOURCE = new RegExp(`\\.(${scriptExtensions.join('|')})$`)
-export const IS_TEMPLATE_SOURCE = new RegExp(`\\.(${templateExtensions.join('|')})$`)
+export const IS_SCRIPT_SOURCE: RegExp = new RegExp(`\\.(${scriptExtensions.join('|')})$`)
+export const IS_TEMPLATE_SOURCE: RegExp = new RegExp(`\\.(${templateExtensions.join('|')})$`)
diff --git a/packages/tailwindcss-language-service/src/util/absoluteRange.ts b/packages/tailwindcss-language-service/src/util/absoluteRange.ts
index 643de4ed..de6cb642 100644
--- a/packages/tailwindcss-language-service/src/util/absoluteRange.ts
+++ b/packages/tailwindcss-language-service/src/util/absoluteRange.ts
@@ -1,6 +1,6 @@
import type { Range } from 'vscode-languageserver'
-export function absoluteRange(range: Range, reference?: Range) {
+export function absoluteRange(range: Range, reference?: Range): Range {
return {
start: {
line: (reference?.start.line || 0) + range.start.line,
diff --git a/packages/tailwindcss-language-service/src/util/braceLevel.ts b/packages/tailwindcss-language-service/src/util/braceLevel.ts
index a7245b17..46d2f5cd 100644
--- a/packages/tailwindcss-language-service/src/util/braceLevel.ts
+++ b/packages/tailwindcss-language-service/src/util/braceLevel.ts
@@ -1,4 +1,4 @@
-export function braceLevel(text: string) {
+export function braceLevel(text: string): number {
let count = 0
for (let i = text.length - 1; i >= 0; i--) {
@@ -10,7 +10,7 @@ export function braceLevel(text: string) {
return count
}
-export function parenLevel(text: string) {
+export function parenLevel(text: string): number {
let count = 0
for (let i = text.length - 1; i >= 0; i--) {
diff --git a/packages/tailwindcss-language-service/src/util/colorEquivalents.ts b/packages/tailwindcss-language-service/src/util/colorEquivalents.ts
index 95ab47bc..a33f9070 100644
--- a/packages/tailwindcss-language-service/src/util/colorEquivalents.ts
+++ b/packages/tailwindcss-language-service/src/util/colorEquivalents.ts
@@ -1,4 +1,4 @@
-import type { Plugin } from 'postcss'
+import type { Plugin, PluginCreator } from 'postcss'
import parseValue from 'postcss-value-parser'
import { inGamut } from 'culori'
import { formatColor, getColorFromValue } from './color'
@@ -16,51 +16,55 @@ export function getEquivalentColor(value: string): string {
return formatColor(color)
}
-export function equivalentColorValues({ comments }: { comments: Comment[] }): Plugin {
- return {
- postcssPlugin: 'plugin',
- Declaration(decl) {
- if (!allowedFunctions.some((fn) => decl.value.includes(fn))) {
- return
- }
-
- parseValue(decl.value).walk((node) => {
- if (node.type !== 'function') {
- return true
+export const equivalentColorValues: PluginCreator = Object.assign(
+ ({ comments }: { comments: Comment[] }): Plugin => {
+ return {
+ postcssPlugin: 'plugin',
+ Declaration(decl) {
+ if (!allowedFunctions.some((fn) => decl.value.includes(fn))) {
+ return
}
- if (node.value === 'var') {
- return true
- }
+ parseValue(decl.value).walk((node) => {
+ if (node.type !== 'function') {
+ return true
+ }
- if (!allowedFunctions.includes(node.value)) {
- return false
- }
+ if (node.value === 'var') {
+ return true
+ }
- const values = node.nodes.filter((n) => n.type === 'word').map((n) => n.value)
- if (values.length < 3) {
- return false
- }
+ if (!allowedFunctions.includes(node.value)) {
+ return false
+ }
- let color = `${node.value}(${values.join(' ')})`
+ const values = node.nodes.filter((n) => n.type === 'word').map((n) => n.value)
+ if (values.length < 3) {
+ return false
+ }
- let equivalent = getEquivalentColor(color)
+ let color = `${node.value}(${values.join(' ')})`
- if (equivalent === color) {
- return false
- }
+ let equivalent = getEquivalentColor(color)
- comments.push({
- index:
- decl.source.start.offset +
- `${decl.prop}${decl.raws.between}`.length +
- node.sourceEndIndex,
- value: equivalent,
- })
+ if (equivalent === color) {
+ return false
+ }
- return false
- })
- },
- }
-}
-equivalentColorValues.postcss = true
+ comments.push({
+ index:
+ decl.source.start.offset +
+ `${decl.prop}${decl.raws.between}`.length +
+ node.sourceEndIndex,
+ value: equivalent,
+ })
+
+ return false
+ })
+ },
+ }
+ },
+ {
+ postcss: true as const,
+ },
+)
diff --git a/packages/tailwindcss-language-service/src/util/css.ts b/packages/tailwindcss-language-service/src/util/css.ts
index c5f3e3a4..f43219aa 100644
--- a/packages/tailwindcss-language-service/src/util/css.ts
+++ b/packages/tailwindcss-language-service/src/util/css.ts
@@ -6,7 +6,7 @@ import type { State } from './state'
import { cssLanguages } from './languages'
import { getLanguageBoundaries } from './getLanguageBoundaries'
-function getCssLanguages(state: State) {
+function getCssLanguages(state: State): string[] {
const userCssLanguages = Object.keys(state.editor.userLanguages).filter((lang) =>
cssLanguages.includes(state.editor.userLanguages[lang]),
)
@@ -14,7 +14,7 @@ function getCssLanguages(state: State) {
return [...cssLanguages, ...userCssLanguages]
}
-export function isCssLanguage(state: State, lang: string) {
+export function isCssLanguage(state: State, lang: string): boolean {
return getCssLanguages(state).indexOf(lang) !== -1
}
diff --git a/packages/tailwindcss-language-service/src/util/cssObjToAst.ts b/packages/tailwindcss-language-service/src/util/cssObjToAst.ts
index 42826f75..ce151bdd 100644
--- a/packages/tailwindcss-language-service/src/util/cssObjToAst.ts
+++ b/packages/tailwindcss-language-service/src/util/cssObjToAst.ts
@@ -120,7 +120,9 @@ function parse(obj, parent, postcss) {
}
}
-export function cssObjToAst(obj, postcss) {
+import type { Postcss, Root } from 'postcss'
+
+export function cssObjToAst(obj: any, postcss: Postcss): Root {
var root = postcss.root()
parse(obj, root, postcss)
return root
diff --git a/packages/tailwindcss-language-service/src/util/estimated-class-size.ts b/packages/tailwindcss-language-service/src/util/estimated-class-size.ts
index 57dc4235..2e6eb36a 100644
--- a/packages/tailwindcss-language-service/src/util/estimated-class-size.ts
+++ b/packages/tailwindcss-language-service/src/util/estimated-class-size.ts
@@ -6,7 +6,7 @@ import { segment } from './segment'
* This is meant to be a lower bound, as the actual size of a class can vary
* depending on the actual CSS properties and values, configured theme, etc…
*/
-export function estimatedClassSize(className: string) {
+export function estimatedClassSize(className: string): number {
let size = 0
// We estimate the size using the following structure which gives a reasonable
diff --git a/packages/tailwindcss-language-service/src/util/flagEnabled.ts b/packages/tailwindcss-language-service/src/util/flagEnabled.ts
index f6f4dd02..70b7776a 100644
--- a/packages/tailwindcss-language-service/src/util/flagEnabled.ts
+++ b/packages/tailwindcss-language-service/src/util/flagEnabled.ts
@@ -1,7 +1,7 @@
import type { State } from './state'
import dlv from 'dlv'
-export function flagEnabled(state: State, flag: string) {
+export function flagEnabled(state: State, flag: string): boolean {
if (state.featureFlags.future.includes(flag)) {
return state.config.future === 'all' || dlv(state.config, ['future', flag], false)
}
diff --git a/packages/tailwindcss-language-service/src/util/format-bytes.ts b/packages/tailwindcss-language-service/src/util/format-bytes.ts
index a7b0b050..36b27159 100644
--- a/packages/tailwindcss-language-service/src/util/format-bytes.ts
+++ b/packages/tailwindcss-language-service/src/util/format-bytes.ts
@@ -1,6 +1,6 @@
const UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte', 'petabyte']
-export function formatBytes(n: number) {
+export function formatBytes(n: number): string {
let i = n == 0 ? 0 : Math.floor(Math.log(n) / Math.log(1000))
return new Intl.NumberFormat('en', {
notation: 'compact',
diff --git a/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts b/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts
index 42a4a495..786cc2f7 100644
--- a/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts
+++ b/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts
@@ -139,7 +139,7 @@ let vueLexer = moo.states(vueStates)
let cache = new Cache({ max: 25, maxAge: 1000 })
-export function clearLanguageBoundariesCache() {
+export function clearLanguageBoundariesCache(): void {
cache.clear()
}
diff --git a/packages/tailwindcss-language-service/src/util/jit.ts b/packages/tailwindcss-language-service/src/util/jit.ts
index ca95df7f..b2a1b218 100644
--- a/packages/tailwindcss-language-service/src/util/jit.ts
+++ b/packages/tailwindcss-language-service/src/util/jit.ts
@@ -4,7 +4,7 @@ import { addPixelEquivalentsToValue } from './pixelEquivalents'
import { addEquivalents } from './equivalents'
import { addThemeValues, inlineThemeValues } from './rewriting'
-export function bigSign(bigIntValue) {
+export function bigSign(bigIntValue: number | bigint): number {
// @ts-ignore
return (bigIntValue > 0n) - (bigIntValue < 0n)
}
diff --git a/packages/tailwindcss-language-service/src/util/language-blocks.ts b/packages/tailwindcss-language-service/src/util/language-blocks.ts
index 10a3fe14..5fbc038c 100644
--- a/packages/tailwindcss-language-service/src/util/language-blocks.ts
+++ b/packages/tailwindcss-language-service/src/util/language-blocks.ts
@@ -1,5 +1,5 @@
import type { State } from '../util/state'
-import { type Range } from 'vscode-languageserver'
+import type { Range } from 'vscode-languageserver'
import type { TextDocument } from 'vscode-languageserver-textdocument'
import { getLanguageBoundaries } from '../util/getLanguageBoundaries'
import { isCssDoc } from '../util/css'
diff --git a/packages/tailwindcss-language-service/src/util/languages.ts b/packages/tailwindcss-language-service/src/util/languages.ts
index 7a00d1f7..2e5fce74 100644
--- a/packages/tailwindcss-language-service/src/util/languages.ts
+++ b/packages/tailwindcss-language-service/src/util/languages.ts
@@ -1,6 +1,6 @@
import type { EditorState } from './state'
-export const htmlLanguages = [
+export const htmlLanguages: string[] = [
'aspnetcorerazor',
'astro',
'astro-markdown',
@@ -36,7 +36,7 @@ export const htmlLanguages = [
'twig',
]
-export const cssLanguages = [
+export const cssLanguages: string[] = [
'css',
'less',
'postcss',
@@ -47,7 +47,7 @@ export const cssLanguages = [
'tailwindcss',
]
-export const jsLanguages = [
+export const jsLanguages: string[] = [
'javascript',
'javascriptreact',
'reason',
@@ -58,16 +58,21 @@ export const jsLanguages = [
'glimmer-ts',
]
-export const specialLanguages = ['vue', 'svelte']
+export const specialLanguages: string[] = ['vue', 'svelte']
-export const languages = [...cssLanguages, ...htmlLanguages, ...jsLanguages, ...specialLanguages]
+export const languages: string[] = [
+ ...cssLanguages,
+ ...htmlLanguages,
+ ...jsLanguages,
+ ...specialLanguages,
+]
const semicolonlessLanguages = ['sass', 'sugarss', 'stylus']
export function isSemicolonlessCssLanguage(
languageId: string,
userLanguages: EditorState['userLanguages'] = {},
-) {
+): boolean {
return (
semicolonlessLanguages.includes(languageId) ||
semicolonlessLanguages.includes(userLanguages[languageId])
diff --git a/packages/tailwindcss-language-service/src/util/lexers.ts b/packages/tailwindcss-language-service/src/util/lexers.ts
index a8315f51..cfd062b5 100644
--- a/packages/tailwindcss-language-service/src/util/lexers.ts
+++ b/packages/tailwindcss-language-service/src/util/lexers.ts
@@ -1,5 +1,5 @@
import moo from 'moo'
-import { lazy } from './lazy'
+import { Lazy, lazy } from './lazy'
const classAttributeStates: () => { [x: string]: moo.Rules } = () => ({
doubleClassList: {
@@ -66,7 +66,7 @@ const simpleClassAttributeStates: { [x: string]: moo.Rules } = {
},
}
-export const getClassAttributeLexer = lazy(() => {
+export const getClassAttributeLexer: Lazy = lazy(() => {
let supportsNegativeLookbehind = true
try {
new RegExp('(? {
return moo.states(simpleClassAttributeStates)
})
-export const getComputedClassAttributeLexer = lazy(() => {
+export const getComputedClassAttributeLexer: Lazy = lazy(() => {
let supportsNegativeLookbehind = true
try {
new RegExp('(? = Object.assign(
+ ({ comments, rootFontSize }: { comments: Comment[]; rootFontSize: number }): Plugin => {
+ return {
+ postcssPlugin: 'plugin',
+ AtRule: {
+ media(atRule) {
+ if (!atRule.params.includes('em')) {
+ return
+ }
+
+ comments.push(
+ ...getPixelEquivalentsForMediaQuery(atRule.params).map(({ index, value }) => ({
+ index: index + atRule.source.start.offset + `@media${atRule.raws.afterName}`.length,
+ value,
+ })),
+ )
+ },
+ },
+ Declaration(decl) {
+ if (!decl.value.includes('rem')) {
return
}
- comments.push(
- ...getPixelEquivalentsForMediaQuery(atRule.params).map(({ index, value }) => ({
- index: index + atRule.source.start.offset + `@media${atRule.raws.afterName}`.length,
- value,
- })),
- )
- },
- },
- Declaration(decl) {
- if (!decl.value.includes('rem')) {
- return
- }
-
- parseValue(decl.value).walk((node) => {
- if (node.type !== 'word') {
- return true
- }
+ parseValue(decl.value).walk((node) => {
+ if (node.type !== 'word') {
+ return true
+ }
- let unit = parseValue.unit(node.value)
- if (!unit || unit.unit !== 'rem') {
- return false
- }
+ let unit = parseValue.unit(node.value)
+ if (!unit || unit.unit !== 'rem') {
+ return false
+ }
- comments.push({
- index:
- decl.source.start.offset +
- `${decl.prop}${decl.raws.between}`.length +
- node.sourceEndIndex,
- value: `${parseFloat(unit.number) * rootFontSize}px`,
- })
+ comments.push({
+ index:
+ decl.source.start.offset +
+ `${decl.prop}${decl.raws.between}`.length +
+ node.sourceEndIndex,
+ value: `${parseFloat(unit.number) * rootFontSize}px`,
+ })
- return false
- })
- },
- }
-}
-equivalentPixelValues.postcss = true
+ return false
+ })
+ },
+ }
+ },
+ {
+ postcss: true as const,
+ },
+)
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/add-theme-values.ts b/packages/tailwindcss-language-service/src/util/rewriting/add-theme-values.ts
index b0f30891..c84d6751 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/add-theme-values.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/add-theme-values.ts
@@ -7,7 +7,7 @@ import { applyComments, Comment } from '../comments'
import { getEquivalentColor } from '../colorEquivalents'
import { resolveVariableValue } from './lookup'
-export function addThemeValues(css: string, state: State, settings: TailwindCssSettings) {
+export function addThemeValues(css: string, state: State, settings: TailwindCssSettings): string {
if (!state.designSystem) return css
let comments: Comment[] = []
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/inline-theme-values.ts b/packages/tailwindcss-language-service/src/util/rewriting/inline-theme-values.ts
index c002ab9c..5c1eb8ed 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/inline-theme-values.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/inline-theme-values.ts
@@ -4,7 +4,7 @@ import { evaluateExpression } from './calc'
import { resolveVariableValue } from './lookup'
import { replaceCssVars, replaceCssCalc } from './replacements'
-export function inlineThemeValues(css: string, state: State) {
+export function inlineThemeValues(css: string, state: State): string {
if (!state.designSystem) return css
css = replaceCssCalc(css, (expr) => {
diff --git a/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts b/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts
index 2dbb7146..5f7f353e 100644
--- a/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts
+++ b/packages/tailwindcss-language-service/src/util/rewriting/lookup.ts
@@ -1,7 +1,7 @@
import { DesignSystem } from '../v4'
// Resolve a variable value from the design system
-export function resolveVariableValue(design: DesignSystem, name: string) {
+export function resolveVariableValue(design: DesignSystem, name: string): string | null {
let prefix = design.theme.prefix ?? null
if (prefix && name.startsWith(`--${prefix}`)) {
diff --git a/packages/tailwindcss-language-service/src/util/segment.ts b/packages/tailwindcss-language-service/src/util/segment.ts
index 018485db..70faec52 100644
--- a/packages/tailwindcss-language-service/src/util/segment.ts
+++ b/packages/tailwindcss-language-service/src/util/segment.ts
@@ -26,7 +26,7 @@ const closingBracketStack = new Uint8Array(256)
* x x x ╰──────── Split because top-level
* ╰──────────────┴──┴───────────── Ignored b/c inside >= 1 levels of parens
*/
-export function segment(input: string, separator: string) {
+export function segment(input: string, separator: string): string[] {
// SAFETY: We can use an index into a shared buffer because this function is
// synchronous, non-recursive, and runs in a single-threaded environment.
let stackPos = 0
diff --git a/packages/tailwindcss-language-service/src/util/splice-changes-into-string.ts b/packages/tailwindcss-language-service/src/util/splice-changes-into-string.ts
index e1e6f168..ec601708 100644
--- a/packages/tailwindcss-language-service/src/util/splice-changes-into-string.ts
+++ b/packages/tailwindcss-language-service/src/util/splice-changes-into-string.ts
@@ -8,7 +8,7 @@ export interface StringChange {
* Apply the changes to the string such that a change in the length
* of the string does not break the indexes of the subsequent changes.
*/
-export function spliceChangesIntoString(str: string, changes: StringChange[]) {
+export function spliceChangesIntoString(str: string, changes: StringChange[]): string {
// If there are no changes, return the original string
if (!changes[0]) return str
diff --git a/packages/tailwindcss-language-service/src/util/test-utils.ts b/packages/tailwindcss-language-service/src/util/test-utils.ts
index ce66ecaf..9a01cf9a 100644
--- a/packages/tailwindcss-language-service/src/util/test-utils.ts
+++ b/packages/tailwindcss-language-service/src/util/test-utils.ts
@@ -1,15 +1,15 @@
-import { createState, getDefaultTailwindSettings, Settings } from './state'
+import { createState, getDefaultTailwindSettings, Settings, State } from './state'
import { TextDocument } from 'vscode-languageserver-textdocument'
import type { DeepPartial } from '../types'
-import dedent from 'dedent'
+import dedent, { type Dedent } from 'dedent'
-export const js = dedent
-export const jsx = dedent
-export const ts = dedent
-export const tsx = dedent
-export const css = dedent
-export const html = dedent
-export const pug = dedent
+export const js: Dedent = dedent
+export const jsx: Dedent = dedent
+export const ts: Dedent = dedent
+export const tsx: Dedent = dedent
+export const css: Dedent = dedent
+export const html: Dedent = dedent
+export const pug: Dedent = dedent
export function createDocument({
name,
@@ -21,7 +21,7 @@ export function createDocument({
lang: string
content: string | string[]
settings?: DeepPartial
-}) {
+}): { doc: TextDocument; state: State } {
let doc = TextDocument.create(
`file://${name}`,
lang,
diff --git a/packages/tailwindcss-language-service/src/util/v4/ast.ts b/packages/tailwindcss-language-service/src/util/v4/ast.ts
index 452f35ff..a60d7934 100644
--- a/packages/tailwindcss-language-service/src/util/v4/ast.ts
+++ b/packages/tailwindcss-language-service/src/util/v4/ast.ts
@@ -22,7 +22,7 @@ export function visit(
nodes: AstNode[],
cb: (node: AstNode, path: AstNode[]) => void,
path: AstNode[] = [],
-) {
+): void {
for (let child of nodes) {
path = [...path, child]
cb(child, path)
diff --git a/packages/tailwindcss-language-service/tsconfig.json b/packages/tailwindcss-language-service/tsconfig.json
index 46eb230c..10bf6a9f 100644
--- a/packages/tailwindcss-language-service/tsconfig.json
+++ b/packages/tailwindcss-language-service/tsconfig.json
@@ -16,6 +16,7 @@
"moduleResolution": "Bundler",
"skipLibCheck": true,
"jsx": "react",
- "esModuleInterop": true
+ "esModuleInterop": true,
+ "isolatedDeclarations": true
}
}
From 1c77f60f70b6f6ec7b09caa6b33bd72e89d60e0a Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 8 May 2025 15:44:02 -0400
Subject: [PATCH 53/93] Add Node 24 to test matrix (#1349)
technically this replaces v23 with v24 but odd numbered node releases
don't ever become LTS
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index af79a008..e572b9a8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- node: [18, 20, 22, 23]
+ node: [18, 20, 22, 24]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
From 9112e9684ee4bbf491975617eb84ba17657683b5 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 13 May 2025 11:54:34 -0400
Subject: [PATCH 54/93] Gracefully handle color parsing failures (#1363)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fixes #1362
Basically when we'd see `rgb(1rem)` and tried to parse it with Culori it
would throw an error instead of returning `undefined` as would normally
be the case. Pretty sure this isn't supposed to throw but 🤷♂️
---
.../src/util/color.ts | 18 ++++++++++++++----
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/packages/tailwindcss-language-service/src/util/color.ts b/packages/tailwindcss-language-service/src/util/color.ts
index a1a99d66..4d17a9ea 100644
--- a/packages/tailwindcss-language-service/src/util/color.ts
+++ b/packages/tailwindcss-language-service/src/util/color.ts
@@ -61,7 +61,7 @@ function getColorsInString(state: State, str: string): (culori.Color | KeywordCo
function toColor(match: RegExpMatchArray) {
let color = match[1].replace(/var\([^)]+\)/, '1')
- return getKeywordColor(color) ?? culori.parse(color)
+ return getKeywordColor(color) ?? tryParseColor(color)
}
str = replaceCssVarsWithFallbacks(state, str)
@@ -275,8 +275,8 @@ export function getColorFromValue(value: unknown): culori.Color | KeywordColor |
) {
return null
}
- const color = culori.parse(trimmedValue)
- return color ?? null
+
+ return tryParseColor(trimmedValue)
}
let toRgb = culori.converter('rgb')
@@ -296,11 +296,21 @@ export function formatColor(color: culori.Color): string {
const COLOR_MIX_REGEX = /color-mix\(in [^,]+,\s*(.*?)\s*(\d+|\.\d+|\d+\.\d+)%,\s*transparent\)/g
+function tryParseColor(color: string) {
+ try {
+ return culori.parse(color) ?? null
+ } catch (err) {
+ console.error('Error parsing color', color)
+ console.error(err)
+ return null
+ }
+}
+
function removeColorMixWherePossible(str: string) {
return str.replace(COLOR_MIX_REGEX, (match, color, percentage) => {
if (color.startsWith('var(')) return match
- let parsed = culori.parse(color)
+ let parsed = tryParseColor(color)
if (!parsed) return match
let alpha = Number(percentage) / 100
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index e57ef565..8e28ad34 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -6,6 +6,7 @@
- Ignore Python virtual env directories by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
- Ignore Yarn v2+ metadata & cache directories by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
- Ignore some build caches by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
+- Gracefully handle color parsing failures ([#1363](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1363))
# 0.14.16
From c14eb12689912c94094a2ad40008fc3a9873038a Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 13 May 2025 11:56:17 -0400
Subject: [PATCH 55/93] Calculate swatches for HSL colors with angular units
(#1360)
Fixes #1359
We only computed color swatches for HSL values you omitted the angular
unit e.g. `hsl(50 50% 50%)` I've tweaked the regex so we accept values
with `deg`, `rad`, `grad`, and `turn` units.
---
packages/tailwindcss-language-service/src/util/color.ts | 2 +-
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/tailwindcss-language-service/src/util/color.ts b/packages/tailwindcss-language-service/src/util/color.ts
index 4d17a9ea..5eb8ec53 100644
--- a/packages/tailwindcss-language-service/src/util/color.ts
+++ b/packages/tailwindcss-language-service/src/util/color.ts
@@ -50,7 +50,7 @@ function getKeywordColor(value: unknown): KeywordColor | null {
// https://github.com/khalilgharbaoui/coloregex
const colorRegex = new RegExp(
- `(?:^|\\s|\\(|,)(#(?:[0-9a-f]{2}){2,4}|(#[0-9a-f]{3})|(rgba?|hsla?|(?:ok)?(?:lab|lch))\\(\\s*(-?[\\d.]+%?(\\s*[,/]\\s*|\\s+)+){2,3}\\s*([\\d.]+%?|var\\([^)]+\\))?\\)|transparent|currentColor|${Object.keys(
+ `(?:^|\\s|\\(|,)(#(?:[0-9a-f]{2}){2,4}|(#[0-9a-f]{3})|(rgba?|hsla?|(?:ok)?(?:lab|lch))\\(\\s*(-?[\\d.]+(%|deg|rad|grad|turn)?(\\s*[,/]\\s*|\\s+)+){2,3}\\s*([\\d.]+%?|var\\([^)]+\\))?\\)|transparent|currentColor|${Object.keys(
namedColors,
).join('|')})(?:$|\\s|\\)|,)`,
'gi',
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 8e28ad34..4ef83b82 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -7,6 +7,7 @@
- Ignore Yarn v2+ metadata & cache directories by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
- Ignore some build caches by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
- Gracefully handle color parsing failures ([#1363](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1363))
+- Calculate swatches for HSL colors with angular units ([#1360](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1360))
# 0.14.16
From f4250594a60c179a8c42096d06c6e7f40db59e6c Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 13 May 2025 12:11:47 -0400
Subject: [PATCH 56/93] =?UTF-8?q?Don=E2=80=99t=20skip=20suggesting=20empty?=
=?UTF-8?q?=20variant=20implementations=20(#1352)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A variant written like this wouldn't be suggested:
```
@custom-variant foo {
@slot;
}
```
This is because it generates no additional selectors / at rules. These
don't really make much sense _but_ they're still technically valid so
should still be available in completions.
---
.../tests/completions/completions.test.js | 28 +++++++++----------
.../tests/env/v4.test.js | 4 +--
.../src/completionProvider.ts | 2 --
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
4 files changed, 17 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 ce5c7831..079cfe29 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(19283)
- expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(346)
+ expect(result.items.length).not.toBe(0)
+ expect(result.items.filter((item) => item.label.endsWith(':')).length).not.toBe(0)
expect(result).toEqual({
isIncomplete: false,
items: expect.arrayContaining([
@@ -692,7 +692,7 @@ defineTest({
// ^
let completion = await document.completions({ line: 0, character: 23 })
- expect(completion?.items.length).toBe(19236)
+ expect(completion?.items.length).not.toBe(0)
},
})
@@ -714,7 +714,7 @@ defineTest({
// ^
let completion = await document.completions({ line: 0, character: 22 })
- expect(completion?.items.length).toBe(19236)
+ expect(completion?.items.length).not.toBe(0)
},
})
@@ -736,7 +736,7 @@ defineTest({
// ^
let completion = await document.completions({ line: 0, character: 31 })
- expect(completion?.items.length).toBe(19236)
+ expect(completion?.items.length).not.toBe(0)
},
})
@@ -758,7 +758,7 @@ defineTest({
// ^
let completion = await document.completions({ line: 0, character: 26 })
- expect(completion?.items.length).toBe(19236)
+ expect(completion?.items.length).not.toBe(0)
},
})
@@ -780,7 +780,7 @@ defineTest({
// ^
let completion = await document.completions({ line: 0, character: 12 })
- expect(completion?.items.length).toBe(19237)
+ expect(completion?.items.length).not.toBe(0)
// Verify that variants and utilities are all prefixed
let prefixed = completion.items.filter((item) => !item.label.startsWith('tw:'))
@@ -806,7 +806,7 @@ defineTest({
// ^
let completion = await document.completions({ line: 0, character: 15 })
- expect(completion?.items.length).toBe(19236)
+ expect(completion?.items.length).not.toBe(0)
// Verify that no variants and utilities have prefixes
let prefixed = completion.items.filter((item) => item.label.startsWith('tw:'))
@@ -839,7 +839,7 @@ defineTest({
// ^
let completion = await document.completions({ line: 0, character: 20 })
- expect(completion?.items.length).toBe(19236)
+ expect(completion?.items.length).not.toBe(0)
},
})
@@ -870,7 +870,7 @@ defineTest({
// ^
let completion = await document.completions({ line: 1, character: 22 })
- expect(completion?.items.length).toBe(19236)
+ expect(completion?.items.length).not.toBe(0)
},
})
@@ -960,24 +960,24 @@ defineTest({
// ^
let completionA = await document.completions({ line: 0, character: 13 })
- expect(completionA?.items.length).toBe(19236)
+ expect(completionA?.items.length).not.toBe(0)
// return ;
// ^
let completionB = await document.completions({ line: 3, character: 30 })
- expect(completionB?.items.length).toBe(19236)
+ expect(completionB?.items.length).not.toBe(0)
// return ;
// ^
let completionC = await document.completions({ line: 7, character: 30 })
- expect(completionC?.items.length).toBe(19236)
+ expect(completionC?.items.length).not.toBe(0)
// let y = cva("");
// ^
let completionD = await document.completions({ line: 10, character: 13 })
- expect(completionD?.items.length).toBe(19236)
+ expect(completionD?.items.length).not.toBe(0)
},
})
diff --git a/packages/tailwindcss-language-server/tests/env/v4.test.js b/packages/tailwindcss-language-server/tests/env/v4.test.js
index dc33c79f..af3916cf 100644
--- a/packages/tailwindcss-language-server/tests/env/v4.test.js
+++ b/packages/tailwindcss-language-server/tests/env/v4.test.js
@@ -49,7 +49,7 @@ defineTest({
},
})
- expect(completion?.items.length).toBe(19235)
+ expect(completion?.items.length).not.toBe(0)
},
})
@@ -233,7 +233,7 @@ defineTest({
},
})
- expect(completion?.items.length).toBe(19235)
+ expect(completion?.items.length).not.toBe(0)
},
})
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index bf9685a9..bbfb0b1a 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -202,9 +202,7 @@ export function completionsFromClassList(
variant,
err,
})
- }
- if (selectors.length === 0) {
continue
}
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 4ef83b82..e0d90b3d 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -8,6 +8,7 @@
- Ignore some build caches by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
- Gracefully handle color parsing failures ([#1363](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1363))
- Calculate swatches for HSL colors with angular units ([#1360](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1360))
+- Don’t skip suggesting empty variant implementations ([#1352](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1352))
# 0.14.16
From 888e042667f92218f15f176ac1df3d5d6ad9f8f2 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 13 May 2025 12:12:46 -0400
Subject: [PATCH 57/93] Fix error when using VSCode < 1.78 (#1353)
Fixes #1350
---
packages/tailwindcss-language-server/src/tw.ts | 4 +++-
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index 792c887d..f2376154 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -41,6 +41,7 @@ import { URI } from 'vscode-uri'
import normalizePath from 'normalize-path'
import * as path from 'node:path'
import * as fs from 'node:fs/promises'
+import * as fsSync from 'node:fs'
import type * as chokidar from 'chokidar'
import picomatch from 'picomatch'
import * as parcel from './watcher/index.js'
@@ -188,7 +189,8 @@ export class TW {
let base = baseUri.fsPath
try {
- await fs.access(base, fs.constants.F_OK | fs.constants.R_OK)
+ // TODO: Change this to fs.constants after the node version bump
+ await fs.access(base, fsSync.constants.F_OK | fsSync.constants.R_OK)
} catch (err) {
console.error(
`Unable to access the workspace folder [${base}]. This may happen if the directory does not exist or the current user does not have the necessary permissions to access it.`,
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index e0d90b3d..cea2da55 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -8,6 +8,7 @@
- Ignore some build caches by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
- Gracefully handle color parsing failures ([#1363](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1363))
- Calculate swatches for HSL colors with angular units ([#1360](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1360))
+- Fix error when using VSCode < 1.78 ([#1353](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1353))
- Don’t skip suggesting empty variant implementations ([#1352](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1352))
# 0.14.16
From e2cfc2c6b077c1137182d5bbd86ae348721316b1 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 13 May 2025 12:20:10 -0400
Subject: [PATCH 58/93] Handle balanced parens when searching for helper
functions (#1354)
Fixes #1285
The regex would cause things like this to not work properly:
```
.foo {
color: my-config(theme('fontFamily.sans'))
}
```
You'd get an error message like this:
```
'theme('fontFamily.sans' does not exist in your theme config.(invalidConfigPath)
```
Which means:
1. It saw `config` instead of `my-config`
2. It didn't handle nested parens correctly
3. the nested `theme` function wasn't found
This PR fixes all three of these
---
.../src/util/find.test.ts | 173 +++++++++++++++++-
.../src/util/find.ts | 166 +++++++++++++----
packages/vscode-tailwindcss/CHANGELOG.md | 1 +
3 files changed, 304 insertions(+), 36 deletions(-)
diff --git a/packages/tailwindcss-language-service/src/util/find.test.ts b/packages/tailwindcss-language-service/src/util/find.test.ts
index 170217c0..9c65f23d 100644
--- a/packages/tailwindcss-language-service/src/util/find.test.ts
+++ b/packages/tailwindcss-language-service/src/util/find.test.ts
@@ -1,6 +1,16 @@
import { test } from 'vitest'
-import { findClassListsInHtmlRange, findClassNameAtPosition } from './find'
-import { js, html, pug, createDocument } from './test-utils'
+import {
+ findClassListsInHtmlRange,
+ findClassNameAtPosition,
+ findHelperFunctionsInDocument,
+} from './find'
+import { js, html, pug, createDocument, css } from './test-utils'
+import type { Range } from 'vscode-languageserver-textdocument'
+
+const range = (startLine: number, startCol: number, endLine: number, endCol: number): Range => ({
+ start: { line: startLine, character: startCol },
+ end: { line: endLine, character: endCol },
+})
test('class regex works in astro', async ({ expect }) => {
let file = createDocument({
@@ -875,3 +885,162 @@ test('Can find class name inside JS/TS functions in tags (Svelte)', asy
},
})
})
+
+test('Can find helper functions in CSS', async ({ expect }) => {
+ let file = createDocument({
+ name: 'file.css',
+ lang: 'css',
+ settings: {
+ tailwindCSS: {
+ classFunctions: ['clsx'],
+ },
+ },
+ content: `
+ .a { color: theme(foo); }
+ .a { color: theme(foo, default); }
+ .a { color: theme("foo"); }
+ .a { color: theme("foo", default); }
+ .a { color: theme(foo / 0.5); }
+ .a { color: theme(foo / 0.5, default); }
+ .a { color: theme("foo" / 0.5); }
+ .a { color: theme("foo" / 0.5, default); }
+
+ /* nested invocations */
+ .a { color: from-config(theme(foo)); }
+ .a { color: from-config(theme(foo, default)); }
+ .a { color: from-config(theme("foo")); }
+ .a { color: from-config(theme("foo", default)); }
+ .a { color: from-config(theme(foo / 0.5)); }
+ .a { color: from-config(theme(foo / 0.5, default)); }
+ .a { color: from-config(theme("foo" / 0.5)); }
+ .a { color: from-config(theme("foo" / 0.5, default)); }
+ `,
+ })
+
+ let fns = findHelperFunctionsInDocument(file.state, file.doc)
+
+ expect(fns).toEqual([
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(1, 24, 1, 27), path: range(1, 24, 1, 27) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(2, 24, 2, 36), path: range(2, 24, 2, 27) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(3, 24, 3, 29), path: range(3, 25, 3, 28) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(4, 24, 4, 38), path: range(4, 25, 4, 28) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(5, 24, 5, 33), path: range(5, 24, 5, 27) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(6, 24, 6, 42), path: range(6, 24, 6, 27) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(7, 24, 7, 35), path: range(7, 25, 7, 28) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(8, 24, 8, 44), path: range(8, 25, 8, 28) },
+ },
+
+ // Nested
+ {
+ helper: 'config',
+ path: 'theme(foo)',
+ ranges: { full: range(11, 30, 11, 40), path: range(11, 30, 11, 40) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(11, 36, 11, 39), path: range(11, 36, 11, 39) },
+ },
+ {
+ helper: 'config',
+ path: 'theme(foo, default)',
+ ranges: { full: range(12, 30, 12, 49), path: range(12, 30, 12, 49) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(12, 36, 12, 48), path: range(12, 36, 12, 39) },
+ },
+ {
+ helper: 'config',
+ path: 'theme("foo")',
+ ranges: { full: range(13, 30, 13, 42), path: range(13, 30, 13, 42) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(13, 36, 13, 41), path: range(13, 37, 13, 40) },
+ },
+ {
+ helper: 'config',
+ path: 'theme("foo", default)',
+ ranges: { full: range(14, 30, 14, 51), path: range(14, 30, 14, 51) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(14, 36, 14, 50), path: range(14, 37, 14, 40) },
+ },
+ {
+ helper: 'config',
+ path: 'theme(foo / 0.5)',
+ ranges: { full: range(15, 30, 15, 46), path: range(15, 30, 15, 46) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(15, 36, 15, 45), path: range(15, 36, 15, 39) },
+ },
+ {
+ helper: 'config',
+ path: 'theme(foo / 0.5, default)',
+ ranges: { full: range(16, 30, 16, 55), path: range(16, 30, 16, 55) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(16, 36, 16, 54), path: range(16, 36, 16, 39) },
+ },
+ {
+ helper: 'config',
+ path: 'theme("foo" / 0.5)',
+ ranges: { full: range(17, 30, 17, 48), path: range(17, 30, 17, 48) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(17, 36, 17, 47), path: range(17, 37, 17, 40) },
+ },
+ {
+ helper: 'config',
+ path: 'theme("foo" / 0.5, default)',
+ ranges: { full: range(18, 30, 18, 57), path: range(18, 30, 18, 57) },
+ },
+ {
+ helper: 'theme',
+ path: 'foo',
+ ranges: { full: range(18, 36, 18, 56), path: range(18, 37, 18, 40) },
+ },
+ ])
+})
diff --git a/packages/tailwindcss-language-service/src/util/find.ts b/packages/tailwindcss-language-service/src/util/find.ts
index 53ad7195..11f5946f 100644
--- a/packages/tailwindcss-language-service/src/util/find.ts
+++ b/packages/tailwindcss-language-service/src/util/find.ts
@@ -403,13 +403,12 @@ export function findHelperFunctionsInRange(
doc: TextDocument,
range?: Range,
): DocumentHelperFunction[] {
- const text = getTextWithoutComments(doc, 'css', range)
- let matches = findAll(
- /(?[\W])(?config|theme|--theme|var)(?\(\s*)(?[^)]*?)\s*\)/g,
- text,
- )
+ let text = getTextWithoutComments(doc, 'css', range)
+
+ // Find every instance of a helper function
+ let matches = findAll(/\b(?config|theme|--theme|var)\(/g, text)
- // Eliminate matches that are on an `@import`
+ // Eliminate matches that are attached to an `@import`
matches = matches.filter((match) => {
// Scan backwards to see if we're in an `@import` statement
for (let i = match.index - 1; i >= 0; i--) {
@@ -427,58 +426,157 @@ export function findHelperFunctionsInRange(
return true
})
- return matches.map((match) => {
- let quotesBefore = ''
- let path = match.groups.path
- let commaIndex = getFirstCommaIndex(path)
- if (commaIndex !== null) {
- path = path.slice(0, commaIndex).trimEnd()
- }
- path = path.replace(/['"]+$/, '').replace(/^['"]+/, (m) => {
- quotesBefore = m
- return ''
- })
- let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/)
- if (matches) {
- path = matches[1]
+ let fns: DocumentHelperFunction[] = []
+
+ // Collect the first argument of each fn accounting for balanced params
+ const COMMA = 0x2c
+ const SLASH = 0x2f
+ const BACKSLASH = 0x5c
+ const OPEN_PAREN = 0x28
+ const CLOSE_PAREN = 0x29
+ const DOUBLE_QUOTE = 0x22
+ const SINGLE_QUOTE = 0x27
+
+ let len = text.length
+
+ for (let match of matches) {
+ let argsStart = match.index + match[0].length
+ let argsEnd = null
+ let pathStart = argsStart
+ let pathEnd = null
+ let depth = 1
+
+ // Scan until we find a `,` or balanced `)` not in quotes
+ for (let idx = argsStart; idx < len; ++idx) {
+ let char = text.charCodeAt(idx)
+
+ if (char === BACKSLASH) {
+ idx += 1
+ }
+
+ //
+ else if (char === SINGLE_QUOTE || char === DOUBLE_QUOTE) {
+ while (++idx < len) {
+ let nextChar = text.charCodeAt(idx)
+ if (nextChar === BACKSLASH) {
+ idx += 1
+ continue
+ }
+ if (nextChar === char) break
+ }
+ }
+
+ //
+ else if (char === OPEN_PAREN) {
+ depth += 1
+ }
+
+ //
+ else if (char === CLOSE_PAREN) {
+ depth -= 1
+
+ if (depth === 0) {
+ pathEnd ??= idx
+ argsEnd = idx
+ break
+ }
+ }
+
+ //
+ else if (char === COMMA && depth === 1) {
+ pathEnd ??= idx
+ }
}
- path = path.replace(/['"]*\s*$/, '')
- let startIndex =
- match.index +
- match.groups.prefix.length +
- match.groups.helper.length +
- match.groups.innerPrefix.length
+ if (argsEnd === null) continue
- let helper: 'config' | 'theme' | 'var' = 'config'
+ let helper: 'config' | 'theme' | 'var'
if (match.groups.helper === 'theme' || match.groups.helper === '--theme') {
helper = 'theme'
} else if (match.groups.helper === 'var') {
helper = 'var'
+ } else if (match.groups.helper === 'config') {
+ helper = 'config'
+ } else {
+ continue
}
- return {
+ let path = text.slice(pathStart, pathEnd)
+
+ // Skip leading/trailing whitespace
+ pathStart += path.match(/^\s+/)?.length ?? 0
+ pathEnd -= path.match(/\s+$/)?.length ?? 0
+
+ // Skip leading/trailing quotes
+ let quoteStart = path.match(/^['"]+/)?.length ?? 0
+ let quoteEnd = path.match(/['"]+$/)?.length ?? 0
+
+ if (quoteStart && quoteEnd) {
+ pathStart += quoteStart
+ pathEnd -= quoteEnd
+ }
+
+ // Clip to the top-level slash
+ depth = 1
+ for (let idx = pathStart; idx < pathEnd; ++idx) {
+ let char = text.charCodeAt(idx)
+ if (char === BACKSLASH) {
+ idx += 1
+ } else if (char === OPEN_PAREN) {
+ depth += 1
+ } else if (char === CLOSE_PAREN) {
+ depth -= 1
+ } else if (char === SLASH && depth === 1) {
+ pathEnd = idx
+ }
+ }
+
+ // Re-slice
+ path = text.slice(pathStart, pathEnd)
+
+ // Skip leading/trailing whitespace
+ //
+ // This can happen if we've clipped the path down to before the `/`
+ pathStart += path.match(/^\s+/)?.length ?? 0
+ pathEnd -= path.match(/\s+$/)?.length ?? 0
+
+ // Re-slice
+ path = text.slice(pathStart, pathEnd)
+
+ // Skip leading/trailing quotes
+ quoteStart = path.match(/^['"]+/)?.length ?? 0
+ quoteEnd = path.match(/['"]+$/)?.length ?? 0
+
+ pathStart += quoteStart
+ pathEnd -= quoteEnd
+
+ // Re-slice
+ path = text.slice(pathStart, pathEnd)
+
+ fns.push({
helper,
path,
ranges: {
full: absoluteRange(
{
- start: indexToPosition(text, startIndex),
- end: indexToPosition(text, startIndex + match.groups.path.length),
+ start: indexToPosition(text, argsStart),
+ end: indexToPosition(text, argsEnd),
},
range,
),
path: absoluteRange(
{
- start: indexToPosition(text, startIndex + quotesBefore.length),
- end: indexToPosition(text, startIndex + quotesBefore.length + path.length),
+ start: indexToPosition(text, pathStart),
+ end: indexToPosition(text, pathEnd),
},
range,
),
},
- }
- })
+ })
+ }
+
+ return fns
}
export function indexToPosition(str: string, index: number): Position {
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index cea2da55..b0edc93d 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -10,6 +10,7 @@
- Calculate swatches for HSL colors with angular units ([#1360](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1360))
- Fix error when using VSCode < 1.78 ([#1353](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1353))
- Don’t skip suggesting empty variant implementations ([#1352](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1352))
+- Handle helper function lookups in nested parens ([#1354](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1354))
# 0.14.16
From c08068885bca7099d6b299fae50cc5f33b29ace6 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 13 May 2025 12:27:48 -0400
Subject: [PATCH 59/93] Simplify details for utility completions (#1356)
Fixes #1347
This PR addresses two problems (see screenshots below):
1. Utilities that generate `@property` rules will include the
declarations from `@property` in the completion details.
2. Custom variants that include declarations will include those
declarations in the completion details.
To fix these we are now:
1. Generating details using the "base" utility
2. Dropping any uses of `@property` before calculating the list of
declarations
**Before**
**After**
Note that the "documentation" portion of completion items is unchanged
and shows the full candidate with all the CSS included (for non-color
utilities):
---
.../src/completionProvider.ts | 27 ++++++++++++++-----
packages/vscode-tailwindcss/CHANGELOG.md | 2 ++
2 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts
index bbfb0b1a..8f81fdc5 100644
--- a/packages/tailwindcss-language-service/src/completionProvider.ts
+++ b/packages/tailwindcss-language-service/src/completionProvider.ts
@@ -2280,7 +2280,10 @@ export async function resolveCompletionItem(
if (state.v4) {
if (item.kind === 9) return item
if (item.detail && item.documentation) return item
+
+ let base = state.designSystem.compile([className])[0]
let root = state.designSystem.compile([[...variants, className].join(state.separator)])[0]
+
let rules = root.nodes.filter((node) => node.type === 'rule')
if (rules.length === 0) return item
@@ -2288,16 +2291,26 @@ export async function resolveCompletionItem(
if (rules.length === 1) {
let decls: postcss.Declaration[] = []
- root.walkDecls((node) => {
+ // Remove any `@property` rules
+ base = base.clone()
+ base.walkAtRules((rule) => {
+ // Ignore declarations inside `@property` rules
+ if (rule.name === 'property') {
+ rule.remove()
+ }
+
+ // Ignore declarations @supports (-moz-orient: inline)
+ // this is a hack used for `@property` fallbacks in Firefox
+ if (rule.name === 'supports' && rule.params === '(-moz-orient: inline)') {
+ rule.remove()
+ }
+ })
+
+ base.walkDecls((node) => {
decls.push(node)
})
- item.detail = await jit.stringifyDecls(
- state,
- postcss.rule({
- nodes: decls,
- }),
- )
+ item.detail = await jit.stringifyDecls(state, postcss.rule({ nodes: decls }))
} else {
item.detail = `${rules.length} rules`
}
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index b0edc93d..14775414 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -11,6 +11,8 @@
- Fix error when using VSCode < 1.78 ([#1353](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1353))
- Don’t skip suggesting empty variant implementations ([#1352](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1352))
- Handle helper function lookups in nested parens ([#1354](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1354))
+- Hide `@property` declarations from completion details ([#1356](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1356))
+- Hide variant-provided declarations from completion details for a utility ([#1356](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1356))
# 0.14.16
From d90770164164f866482e5a93666fce420b548150 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lubom=C3=ADr=20Bla=C5=BEek?=
Date: Tue, 13 May 2025 18:40:24 +0200
Subject: [PATCH 60/93] Add support for latte templates (#1361)
This adds support for Latte PHP templates https://latte.nette.org/en/
---
packages/tailwindcss-language-service/src/util/languages.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/tailwindcss-language-service/src/util/languages.ts b/packages/tailwindcss-language-service/src/util/languages.ts
index 2e5fce74..458261cf 100644
--- a/packages/tailwindcss-language-service/src/util/languages.ts
+++ b/packages/tailwindcss-language-service/src/util/languages.ts
@@ -21,6 +21,7 @@ export const htmlLanguages: string[] = [
'html-eex',
'htmldjango',
'jade',
+ 'latte',
'leaf',
'liquid',
'markdown',
From ab0bef53160cdc43591bd6d30e1d6c0e64721492 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Wed, 14 May 2025 13:08:21 -0400
Subject: [PATCH 61/93] Compute correct document selectors when a project is
initialized (#1335)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fixes #1302
Possibly helps #1322
Possibly helps #1323
This PR fixes a problem where files may fail to match against the
appropriate project in a given workspace — and in some cases this
behavior could be "fixed" by opening multiple files until all projects
in a workspace had their selectors recomputed. (A selector is a file
pattern/path paired with a "priority" that tells us how files match
different projects in a workspace)
The problem here is caused by a few things:
- We fixed a bug where auto source detection in v4 silently failed in
IntelliSense. After fixing this a file could get matched against one of
the globs or file paths detected by Oxide.
- A workspace with lots of CSS files may end up creating more than one
"project"
- Upon project initialization we would recompute these selectors **based
on the resolved JS config** (necessary for v3 projects because we
compile ESM or TS configs during intiialization and not discovery).
Obviously, v4 projects do not have JS configs (even if you're using
`@config` or `@plugin` it's not actually the config file we care about.
It's the design system created from the CSS file that matters.) so we
were then throwing away these document selectors.
In a workspace with multiple detected projects (could be because of
multiple CSS "roots", some v3 and some v4 files, etc…) we would check
the file against the selectors of each project, pick out the most
specific match, then initialize the project. The problem is that, when
we re-computed these selectors during initialization they changed. This
has the side effect of dropping the patterns that we picked up from
Oxide for a v4 project.
This would then cause any subsequent requests for a file to match a
*different* project. So for example, a request to compute the document
colors would cause a project to be matched then initialized. Then a
hover in the same file would end up matching a completely different
project.
This PR addresses this by doing two things:
1. Using the same codepath for computing a projects document selectors
during discovery and initalization
2. Normalize Windows drive letters in source paths picked up by Oxide.
This would have the effect of some content paths not matching a project
when it otherwise should on Windows.
In the future it'd probably be a good idea to make documents "sticky"
while they are open such that an open document picks a project and
"sticks" to it. We'd still want to recompute this stickiness if the
config a file is attached to changed but this is a future task as there
might be side effects from doing this.
---
.../src/project-locator.ts | 148 ++++++++++--------
.../src/projects.ts | 38 ++---
.../tests/env/multi-config-content.test.js | 97 +++++++++---
packages/vscode-tailwindcss/CHANGELOG.md | 2 +
4 files changed, 175 insertions(+), 110 deletions(-)
diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts
index 4e3c0453..0f3751ed 100644
--- a/packages/tailwindcss-language-server/src/project-locator.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.ts
@@ -206,62 +206,7 @@ export class ProjectLocator {
// Look for the package root for the config
config.packageRoot = await getPackageRoot(path.dirname(config.path), this.base)
- let selectors: DocumentSelector[] = []
-
- // selectors:
- // - CSS files
- for (let entry of config.entries) {
- if (entry.type !== 'css') continue
- selectors.push({
- pattern: entry.path,
- priority: DocumentSelectorPriority.CSS_FILE,
- })
- }
-
- // - Config File
- selectors.push({
- pattern: config.path,
- priority: DocumentSelectorPriority.CONFIG_FILE,
- })
-
- // - Content patterns from config
- for await (let selector of contentSelectorsFromConfig(
- config,
- tailwind.features,
- this.resolver,
- )) {
- selectors.push(selector)
- }
-
- // - Directories containing the CSS files
- for (let entry of config.entries) {
- if (entry.type !== 'css') continue
- selectors.push({
- pattern: normalizePath(path.join(path.dirname(entry.path), '**')),
- priority: DocumentSelectorPriority.CSS_DIRECTORY,
- })
- }
-
- // - Directory containing the config
- selectors.push({
- pattern: normalizePath(path.join(path.dirname(config.path), '**')),
- priority: DocumentSelectorPriority.CONFIG_DIRECTORY,
- })
-
- // - Root of package that contains the config
- selectors.push({
- pattern: normalizePath(path.join(config.packageRoot, '**')),
- priority: DocumentSelectorPriority.PACKAGE_DIRECTORY,
- })
-
- // Reorder selectors from most specific to least specific
- selectors.sort((a, z) => a.priority - z.priority)
-
- // Eliminate duplicate selector patterns
- selectors = selectors.filter(
- ({ pattern }, index, documentSelectors) =>
- documentSelectors.findIndex(({ pattern: p }) => p === pattern) === index,
- )
+ let selectors = await calculateDocumentSelectors(config, tailwind.features, this.resolver)
return {
config,
@@ -545,13 +490,14 @@ function contentSelectorsFromConfig(
entry: ConfigEntry,
features: Feature[],
resolver: Resolver,
+ actualConfig?: any,
): AsyncIterable {
if (entry.type === 'css') {
return contentSelectorsFromCssConfig(entry, resolver)
}
if (entry.type === 'js') {
- return contentSelectorsFromJsConfig(entry, features)
+ return contentSelectorsFromJsConfig(entry, features, actualConfig)
}
}
@@ -586,11 +532,18 @@ async function* contentSelectorsFromJsConfig(
if (typeof item !== 'string') continue
let filepath = item.startsWith('!')
- ? `!${path.resolve(contentBase, item.slice(1))}`
+ ? path.resolve(contentBase, item.slice(1))
: path.resolve(contentBase, item)
+ filepath = normalizePath(filepath)
+ filepath = normalizeDriveLetter(filepath)
+
+ if (item.startsWith('!')) {
+ filepath = `!${filepath}`
+ }
+
yield {
- pattern: normalizePath(filepath),
+ pattern: filepath,
priority: DocumentSelectorPriority.CONTENT_FILE,
}
}
@@ -603,8 +556,11 @@ async function* contentSelectorsFromCssConfig(
let auto = false
for (let item of entry.content) {
if (item.kind === 'file') {
+ let filepath = item.file
+ filepath = normalizePath(filepath)
+ filepath = normalizeDriveLetter(filepath)
yield {
- pattern: normalizePath(item.file),
+ pattern: filepath,
priority: DocumentSelectorPriority.CONTENT_FILE,
}
} else if (item.kind === 'auto' && !auto) {
@@ -657,12 +613,16 @@ async function* detectContentFiles(
if (!result) return
for (let file of result.files) {
- yield normalizePath(file)
+ file = normalizePath(file)
+ file = normalizeDriveLetter(file)
+ yield file
}
for (let { base, pattern } of result.globs) {
// Do not normalize the glob itself as it may contain escape sequences
- yield normalizePath(base) + '/' + pattern
+ base = normalizePath(base)
+ base = normalizeDriveLetter(base)
+ yield `${base}/${pattern}`
}
} catch {
//
@@ -793,3 +753,67 @@ function requiresPreprocessor(filepath: string) {
return ext === '.scss' || ext === '.sass' || ext === '.less' || ext === '.styl' || ext === '.pcss'
}
+
+export async function calculateDocumentSelectors(
+ config: ConfigEntry,
+ features: Feature[],
+ resolver: Resolver,
+ actualConfig?: any,
+) {
+ let selectors: DocumentSelector[] = []
+
+ // selectors:
+ // - CSS files
+ for (let entry of config.entries) {
+ if (entry.type !== 'css') continue
+
+ selectors.push({
+ pattern: normalizeDriveLetter(normalizePath(entry.path)),
+ priority: DocumentSelectorPriority.CSS_FILE,
+ })
+ }
+
+ // - Config File
+ selectors.push({
+ pattern: normalizeDriveLetter(normalizePath(config.path)),
+ priority: DocumentSelectorPriority.CONFIG_FILE,
+ })
+
+ // - Content patterns from config
+ for await (let selector of contentSelectorsFromConfig(config, features, resolver, actualConfig)) {
+ selectors.push(selector)
+ }
+
+ // - Directories containing the CSS files
+ for (let entry of config.entries) {
+ if (entry.type !== 'css') continue
+
+ selectors.push({
+ pattern: normalizeDriveLetter(normalizePath(path.join(path.dirname(entry.path), '**'))),
+ priority: DocumentSelectorPriority.CSS_DIRECTORY,
+ })
+ }
+
+ // - Directory containing the config
+ selectors.push({
+ pattern: normalizeDriveLetter(normalizePath(path.join(path.dirname(config.path), '**'))),
+ priority: DocumentSelectorPriority.CONFIG_DIRECTORY,
+ })
+
+ // - Root of package that contains the config
+ selectors.push({
+ pattern: normalizeDriveLetter(normalizePath(path.join(config.packageRoot, '**'))),
+ priority: DocumentSelectorPriority.PACKAGE_DIRECTORY,
+ })
+
+ // Reorder selectors from most specific to least specific
+ selectors.sort((a, z) => a.priority - z.priority)
+
+ // Eliminate duplicate selector patterns
+ selectors = selectors.filter(
+ ({ pattern }, index, documentSelectors) =>
+ documentSelectors.findIndex(({ pattern: p }) => p === pattern) === index,
+ )
+
+ return selectors
+}
diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts
index 21e7bb3b..0c54942c 100644
--- a/packages/tailwindcss-language-server/src/projects.ts
+++ b/packages/tailwindcss-language-server/src/projects.ts
@@ -80,7 +80,7 @@ import {
normalizeDriveLetter,
} from './utils'
import type { DocumentService } from './documents'
-import type { ProjectConfig } from './project-locator'
+import { calculateDocumentSelectors, type ProjectConfig } from './project-locator'
import { supportedFeatures } from '@tailwindcss/language-service/src/features'
import { loadDesignSystem } from './util/v4'
import { readCssFile } from './util/css'
@@ -286,7 +286,9 @@ export async function createProjectService(
)
}
- function onFileEvents(changes: Array<{ file: string; type: FileChangeType }>): void {
+ async function onFileEvents(
+ changes: Array<{ file: string; type: FileChangeType }>,
+ ): Promise {
let needsInit = false
let needsRebuild = false
@@ -307,16 +309,11 @@ export async function createProjectService(
projectConfig.configPath &&
(isConfigFile || isDependency)
) {
- documentSelector = [
- ...documentSelector.filter(
- ({ priority }) => priority !== DocumentSelectorPriority.CONTENT_FILE,
- ),
- ...getContentDocumentSelectorFromConfigFile(
- projectConfig.configPath,
- initialTailwindVersion,
- projectConfig.folder,
- ),
- ]
+ documentSelector = await calculateDocumentSelectors(
+ projectConfig.config,
+ state.features,
+ resolver,
+ )
checkOpenDocuments()
}
@@ -963,17 +960,12 @@ export async function createProjectService(
/////////////////////
if (!projectConfig.isUserConfigured) {
- documentSelector = [
- ...documentSelector.filter(
- ({ priority }) => priority !== DocumentSelectorPriority.CONTENT_FILE,
- ),
- ...getContentDocumentSelectorFromConfigFile(
- state.configPath,
- tailwindcss.version,
- projectConfig.folder,
- originalConfig,
- ),
- ]
+ documentSelector = await calculateDocumentSelectors(
+ projectConfig.config,
+ state.features,
+ resolver,
+ originalConfig,
+ )
}
//////////////////////
diff --git a/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js b/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js
index 44b4cb64..f9e7da2f 100644
--- a/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js
+++ b/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js
@@ -1,38 +1,85 @@
-import { test } from 'vitest'
-import { withFixture } from '../common'
+import { expect } from 'vitest'
+import { css, defineTest, html, js, json, symlinkTo } from '../../src/testing'
+import dedent from 'dedent'
+import { createClient } from '../utils/client'
-withFixture('multi-config-content', (c) => {
- test.concurrent('multi-config with content config - 1', async ({ expect }) => {
- let textDocument = await c.openDocument({ text: '', dir: 'one' })
- let res = await c.sendRequest('textDocument/hover', {
- textDocument,
- position: { line: 0, character: 13 },
+defineTest({
+ name: 'multi-config with content config',
+ fs: {
+ 'tailwind.config.one.js': js`
+ module.exports = {
+ content: ['./one/**/*'],
+ theme: {
+ extend: {
+ colors: {
+ foo: 'red',
+ },
+ },
+ },
+ }
+ `,
+ 'tailwind.config.two.js': js`
+ module.exports = {
+ content: ['./two/**/*'],
+ theme: {
+ extend: {
+ colors: {
+ foo: 'blue',
+ },
+ },
+ },
+ }
+ `,
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ client }) => {
+ let one = await client.open({
+ lang: 'html',
+ name: 'one/index.html',
+ text: '
',
})
- expect(res).toEqual({
+ let two = await client.open({
+ lang: 'html',
+ name: 'two/index.html',
+ text: '
',
+ })
+
+ //
+ // ^
+ let hoverOne = await one.hover({ line: 0, character: 13 })
+ let hoverTwo = await two.hover({ line: 0, character: 13 })
+
+ expect(hoverOne).toEqual({
contents: {
language: 'css',
- value:
- '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(255 0 0 / var(--tw-bg-opacity, 1)) /* #ff0000 */;\n}',
+ value: dedent`
+ .bg-foo {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 0 0 / var(--tw-bg-opacity, 1)) /* #ff0000 */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 18 },
},
- range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } },
- })
- })
-
- test.concurrent('multi-config with content config - 2', async ({ expect }) => {
- let textDocument = await c.openDocument({ text: '
', dir: 'two' })
- let res = await c.sendRequest('textDocument/hover', {
- textDocument,
- position: { line: 0, character: 13 },
})
- expect(res).toEqual({
+ expect(hoverTwo).toEqual({
contents: {
language: 'css',
- value:
- '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 255 / var(--tw-bg-opacity, 1)) /* #0000ff */;\n}',
+ value: dedent`
+ .bg-foo {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 255 / var(--tw-bg-opacity, 1)) /* #0000ff */;
+ }
+ `,
+ },
+ range: {
+ start: { line: 0, character: 12 },
+ end: { line: 0, character: 18 },
},
- range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } },
})
- })
+ },
})
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 14775414..a80f579a 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -13,6 +13,8 @@
- Handle helper function lookups in nested parens ([#1354](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1354))
- Hide `@property` declarations from completion details ([#1356](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1356))
- Hide variant-provided declarations from completion details for a utility ([#1356](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1356))
+- Compute correct document selectors when a project is initialized ([#1335](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1335))
+- Fix matching of some content file paths on Windows ([#1335](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1335))
# 0.14.16
From 9edadaf15dd54d19f1abeff32e291ba832ae9a8a Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 15 May 2025 11:01:05 -0400
Subject: [PATCH 62/93] Bump typescript (#1369)
---
.../tailwindcss-language-server/package.json | 2 +-
.../tailwindcss-language-service/package.json | 2 +-
packages/vscode-tailwindcss/package.json | 2 +-
packages/vscode-tailwindcss/tsconfig.json | 2 +-
pnpm-lock.yaml | 30 +++++++++----------
5 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json
index 8d2617f9..11cda390 100644
--- a/packages/tailwindcss-language-server/package.json
+++ b/packages/tailwindcss-language-server/package.json
@@ -95,7 +95,7 @@
"tinyglobby": "^0.2.12",
"tsconfck": "^3.1.4",
"tsconfig-paths": "^4.2.0",
- "typescript": "5.3.3",
+ "typescript": "5.8.3",
"vite-tsconfig-paths": "^4.3.1",
"vitest": "^3.0.9",
"vscode-css-languageservice": "6.3.3",
diff --git a/packages/tailwindcss-language-service/package.json b/packages/tailwindcss-language-service/package.json
index f7928b3e..e0a67b9f 100644
--- a/packages/tailwindcss-language-service/package.json
+++ b/packages/tailwindcss-language-service/package.json
@@ -52,7 +52,7 @@
"esbuild-node-externals": "^1.9.0",
"minimist": "^1.2.8",
"tslib": "2.2.0",
- "typescript": "^5.3.3",
+ "typescript": "^5.8.3",
"vitest": "^3.0.9"
}
}
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 079055b0..8cc1321e 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -389,7 +389,7 @@
"normalize-path": "3.0.0",
"picomatch": "^4.0.1",
"rimraf": "3.0.2",
- "typescript": "5.3.3",
+ "typescript": "5.8.3",
"vscode-languageclient": "8.0.2"
}
}
diff --git a/packages/vscode-tailwindcss/tsconfig.json b/packages/vscode-tailwindcss/tsconfig.json
index 4f3a8ec5..3b30d56e 100755
--- a/packages/vscode-tailwindcss/tsconfig.json
+++ b/packages/vscode-tailwindcss/tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
- "target": "es6",
+ "target": "ES2022",
"lib": ["ES2022"],
"rootDir": "..",
"sourceMap": true,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f0971dbb..207b8cc9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -211,16 +211,16 @@ importers:
version: 0.2.12
tsconfck:
specifier: ^3.1.4
- version: 3.1.4(typescript@5.3.3)
+ version: 3.1.4(typescript@5.8.3)
tsconfig-paths:
specifier: ^4.2.0
version: 4.2.0
typescript:
- specifier: 5.3.3
- version: 5.3.3
+ specifier: 5.8.3
+ version: 5.8.3
vite-tsconfig-paths:
specifier: ^4.3.1
- version: 4.3.2(typescript@5.3.3)(vite@5.4.14(@types/node@18.19.43))
+ version: 4.3.2(typescript@5.8.3)(vite@5.4.14(@types/node@18.19.43))
vitest:
specifier: ^3.0.9
version: 3.0.9(@types/node@18.19.43)
@@ -361,8 +361,8 @@ importers:
specifier: 2.2.0
version: 2.2.0
typescript:
- specifier: ^5.3.3
- version: 5.3.3
+ specifier: ^5.8.3
+ version: 5.8.3
vitest:
specifier: ^3.0.9
version: 3.0.9(@types/node@18.19.43)
@@ -415,8 +415,8 @@ importers:
specifier: 3.0.2
version: 3.0.2
typescript:
- specifier: 5.3.3
- version: 5.3.3
+ specifier: 5.8.3
+ version: 5.8.3
vscode-languageclient:
specifier: 8.0.2
version: 8.0.2
@@ -2652,8 +2652,8 @@ packages:
typed-rest-client@1.8.11:
resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==}
- typescript@5.3.3:
- resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
+ typescript@5.8.3:
+ resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
engines: {node: '>=14.17'}
hasBin: true
@@ -4921,9 +4921,9 @@ snapshots:
ts-interface-checker@0.1.13: {}
- tsconfck@3.1.4(typescript@5.3.3):
+ tsconfck@3.1.4(typescript@5.8.3):
optionalDependencies:
- typescript: 5.3.3
+ typescript: 5.8.3
tsconfig-paths@4.2.0:
dependencies:
@@ -4952,7 +4952,7 @@ snapshots:
tunnel: 0.0.6
underscore: 1.13.7
- typescript@5.3.3: {}
+ typescript@5.8.3: {}
uc.micro@1.0.6: {}
@@ -5001,11 +5001,11 @@ snapshots:
- supports-color
- terser
- vite-tsconfig-paths@4.3.2(typescript@5.3.3)(vite@5.4.14(@types/node@18.19.43)):
+ vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@5.4.14(@types/node@18.19.43)):
dependencies:
debug: 4.3.6
globrex: 0.1.2
- tsconfck: 3.1.4(typescript@5.3.3)
+ tsconfck: 3.1.4(typescript@5.8.3)
optionalDependencies:
vite: 5.4.14(@types/node@18.19.43)
transitivePeerDependencies:
From d39f17e47dd75a804643452d7933bc69d7ef1cc3 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 15 May 2025 10:26:27 -0400
Subject: [PATCH 63/93] 0.14.17
---
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 11cda390..e011083c 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.16",
+ "version": "0.14.17",
"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 e0a67b9f..6fc849fa 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.16",
+ "version": "0.14.17",
"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 a80f579a..d11b9911 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,6 +2,10 @@
## Prerelease
+- Nothing yet!
+
+# 0.14.17
+
- Improve dynamic capability registration in the language server ([#1327](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1327))
- Ignore Python virtual env directories by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
- Ignore Yarn v2+ metadata & cache directories by default ([#1336](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1336))
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 8cc1321e..2539ccda 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "vscode-tailwindcss",
- "version": "0.14.16",
+ "version": "0.14.17",
"displayName": "Tailwind CSS IntelliSense",
"description": "Intelligent Tailwind CSS tooling for VS Code",
"author": "Brad Cornes ",
From 98a02eb9b24601eb76fa546954115bc14591806c Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 15 May 2025 14:16:56 -0400
Subject: [PATCH 64/93] Update project readme (#1364)
Closes #1321
Closes #1228
---------
Co-authored-by: Junepil Lee
Co-authored-by: Jonathan Reinink
---
packages/vscode-tailwindcss/README.md | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/packages/vscode-tailwindcss/README.md b/packages/vscode-tailwindcss/README.md
index f74b1407..e1d7a413 100644
--- a/packages/vscode-tailwindcss/README.md
+++ b/packages/vscode-tailwindcss/README.md
@@ -6,7 +6,11 @@ Tailwind CSS IntelliSense enhances the Tailwind development experience by provid
**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)**
-In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation) and a [Tailwind config file](https://tailwindcss.com/docs/installation#create-your-configuration-file) named `tailwind.config.{js,cjs,mjs,ts,cts,mts}` in your workspace.
+In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation) and one of these:
+
+- For v4 and later, a `.css` file that imports a Tailwind CSS stylesheet (e.g. `@import "tailwindcss"`)
+- For v3 and earlier, a [Tailwind CSS config file](https://v3.tailwindcss.com/docs/configuration#creating-your-configuration-file) named `tailwind.config.{js,cjs,mjs,ts,cts,mts}` in your workspace.
+- For v3 and earlier, a stylesheet that points to a config file via `@config`
## Features
@@ -94,11 +98,13 @@ The HTML attributes for which to provide class completions, hover previews, lint
Functions in which to provide completions, hover previews, linting etc. Currently, this works for both function calls and tagged template literals in JavaScript / TypeScript.
+Each entry is treated as regex pattern that matches on a function name. You *cannot* match on content before or after the function name — matches are limited to function names only.
+
Example:
```json
{
- "tailwindCSS.classFunctions": ["tw", "clsx"]
+ "tailwindCSS.classFunctions": ["tw", "clsx", "tw\\.[a-z-]+"]
}
```
@@ -108,6 +114,7 @@ let classes2 = clsx([
"flex bg-red-500",
{ "text-red-500": true }
])
+let element = tw.div`flex bg-red-500`
```
### `tailwindCSS.colorDecorators`
@@ -237,7 +244,10 @@ For projects with multiple config files, use an object where each key is a confi
If you’re having issues getting the IntelliSense features to activate, there are a few things you can check:
-- Ensure that you have a Tailwind config file in your workspace and that this is named `tailwind.config.{js,cjs,mjs,ts,cts,mts}`. Check out the Tailwind documentation for details on [creating a config file](https://tailwindcss.com/docs/configuration#creating-your-configuration-file).
-- Ensure that the `tailwindcss` module is installed in your workspace, via `npm`, `yarn`, or `pnpm`.
-- Make sure your VS Code settings aren’t causing your Tailwind config file to be hidden/ignored, for example via the `files.exclude` or `files.watcherExclude` settings.
+- You must have `tailwindcss` installed in your workspace via `npm`, `pnpm`, or `yarn`. The extension will then attempt to detect your Tailwind CSS configuration, which can be located in one of the following:
+ - For Tailwind CSS **v4** projects, configuration defined directly within your main CSS file using directives like `@import "tailwindcss";` and `@theme { ... }`. Preprocessor files like Less, Sass, or Stylus are not supported. A `.css` file is **required** for IntelliSense to function.
+ - For Tailwind CSS **v3 and earlier**, a Tailwind CSS config file in your workspace whose name matches (`tailwind.config.{js,cjs,mjs,ts,cts,mts}`), or a stylesheet that points to a config file via `@config`.
+
+- Make sure your VS Code settings aren’t causing your stylesheet or your Tailwind CSS config file to be hidden/ignored, for example via the `files.exclude`, `files.watcherExclude`, or `tailwindCSS.files.exclude` settings.
- Take a look at the language server output by running the `Tailwind CSS: Show Output` command from the command palette. This may show errors that are preventing the extension from activating.
+- For projects with multiple installations of Tailwind CSS, multiple config files, or several stylesheets with `@import "tailwindcss"` we recommend using the `tailwindCSS.experimental.configFile` setting to explicitly state your stylesheet or config paths.
From db9768908e5f192a4ee9792eb640bf93caa89242 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 15 May 2025 14:19:48 -0400
Subject: [PATCH 65/93] Update
---
packages/vscode-tailwindcss/README.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/vscode-tailwindcss/README.md b/packages/vscode-tailwindcss/README.md
index e1d7a413..6ce777d1 100644
--- a/packages/vscode-tailwindcss/README.md
+++ b/packages/vscode-tailwindcss/README.md
@@ -247,7 +247,6 @@ If you’re having issues getting the IntelliSense features to activate, there a
- You must have `tailwindcss` installed in your workspace via `npm`, `pnpm`, or `yarn`. The extension will then attempt to detect your Tailwind CSS configuration, which can be located in one of the following:
- For Tailwind CSS **v4** projects, configuration defined directly within your main CSS file using directives like `@import "tailwindcss";` and `@theme { ... }`. Preprocessor files like Less, Sass, or Stylus are not supported. A `.css` file is **required** for IntelliSense to function.
- For Tailwind CSS **v3 and earlier**, a Tailwind CSS config file in your workspace whose name matches (`tailwind.config.{js,cjs,mjs,ts,cts,mts}`), or a stylesheet that points to a config file via `@config`.
-
- Make sure your VS Code settings aren’t causing your stylesheet or your Tailwind CSS config file to be hidden/ignored, for example via the `files.exclude`, `files.watcherExclude`, or `tailwindCSS.files.exclude` settings.
- Take a look at the language server output by running the `Tailwind CSS: Show Output` command from the command palette. This may show errors that are preventing the extension from activating.
- For projects with multiple installations of Tailwind CSS, multiple config files, or several stylesheets with `@import "tailwindcss"` we recommend using the `tailwindCSS.experimental.configFile` setting to explicitly state your stylesheet or config paths.
From 9d5b4f2452e6449f0547d14a9fc7d6b4ac859e8d Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 20 May 2025 08:36:28 -0400
Subject: [PATCH 66/93] Display color swatches when using before/after variants
(#1374)
Fixes #1370
Turns out *all* color swatches for `before:{utility}` and
`after:{utility}` were broken. This fixes that so utilities using these
variants show color swatches.
---
.../src/util/color.ts | 15 ++++++++++-----
packages/vscode-tailwindcss/CHANGELOG.md | 2 +-
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/packages/tailwindcss-language-service/src/util/color.ts b/packages/tailwindcss-language-service/src/util/color.ts
index 5eb8ec53..514fc286 100644
--- a/packages/tailwindcss-language-service/src/util/color.ts
+++ b/packages/tailwindcss-language-service/src/util/color.ts
@@ -79,11 +79,16 @@ function getColorFromDecls(
): culori.Color | KeywordColor | null {
let props = Object.keys(decls).filter((prop) => {
// ignore content: "";
- if (
- prop === 'content' &&
- (decls[prop] === '""' || decls[prop] === "''" || decls[prop] === 'var(--tw-content)')
- ) {
- return false
+ if (prop === 'content') {
+ let value = decls[prop]
+
+ if (Array.isArray(value) && value.length === 1) {
+ value = value[0]
+ }
+
+ if (value === '""' || value === "''" || value === 'var(--tw-content)') {
+ return false
+ }
}
// ignore mask-image & mask-composite
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index d11b9911..878b52f3 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,7 +2,7 @@
## Prerelease
-- Nothing yet!
+- Display color swatches when using `before`/`after` variants ([#1374](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1374))
# 0.14.17
From fa87e8a1219da1ef5b394ce9750b8160cb029f60 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 20 May 2025 08:37:05 -0400
Subject: [PATCH 67/93] Clear trigger characters when restarting server (#1375)
Fixes #1372
---
.../tailwindcss-language-server/src/tw.ts | 9 ++-
.../tests/env/capabilities.test.ts | 62 +++++++++++++++++++
2 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index f2376154..882d1be2 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -929,7 +929,13 @@ export class TW {
}
// If the trigger characters haven't changed then we don't need to do anything
- if (equal(Array.from(chars), Array.from(this.lastTriggerCharacters ?? []))) return
+ if (
+ this.completionRegistration &&
+ equal(Array.from(chars), Array.from(this.lastTriggerCharacters ?? []))
+ ) {
+ return
+ }
+
this.lastTriggerCharacters = chars
this.completionRegistration?.dispose()
@@ -1127,6 +1133,7 @@ export class TW {
this.commonRegistrations?.dispose()
this.commonRegistrations = undefined
+ this.lastTriggerCharacters.clear()
this.completionRegistration?.dispose()
this.completionRegistration = undefined
diff --git a/packages/tailwindcss-language-server/tests/env/capabilities.test.ts b/packages/tailwindcss-language-server/tests/env/capabilities.test.ts
index 22eac608..6799c0ba 100644
--- a/packages/tailwindcss-language-server/tests/env/capabilities.test.ts
+++ b/packages/tailwindcss-language-server/tests/env/capabilities.test.ts
@@ -182,3 +182,65 @@ defineTest({
expect(idsBefore).toEqual(idsAfter)
},
})
+
+defineTest({
+ name: 'Trigger characters are registered after a server restart',
+ fs: {
+ 'app.css': '@import "tailwindcss"',
+ },
+ prepare: async ({ root }) => ({ client: await createClient({ root }) }),
+ handle: async ({ root, client }) => {
+ // Initially don't have any registered capabilities because dynamic
+ // registration is delayed until after project initialization
+ expect(client.serverCapabilities).toEqual([])
+
+ // We open a document so a project gets initialized
+ await client.open({
+ lang: 'html',
+ text: '',
+ })
+
+ // And now capabilities are registered
+ expect(client.serverCapabilities).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ method: 'textDocument/hover',
+ }),
+
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', ':'],
+ },
+ }),
+ ]),
+ )
+
+ let didRestart = new Promise((resolve) => {
+ client.conn.onNotification('@/tailwindCSS/serverRestarted', resolve)
+ })
+
+ // Force a server restart by telling the server tsconfig.json changed
+ client.notifyChangedFiles({
+ changed: [`${root}/tsconfig.json`],
+ })
+
+ // Wait for the server initialization to finish
+ await didRestart
+
+ expect(client.serverCapabilities).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ method: 'textDocument/completion',
+ registerOptions: {
+ documentSelector: null,
+ resolveProvider: true,
+ triggerCharacters: ['"', "'", '`', ' ', '.', '(', '[', ']', '!', '/', '-', ':'],
+ },
+ }),
+ ]),
+ )
+ },
+})
From 8170e660aa98c4910730d3355a56388507551356 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 20 May 2025 11:54:53 -0400
Subject: [PATCH 68/93] =?UTF-8?q?Don't=20register=20ability=20to=20hover,?=
=?UTF-8?q?=20request=20colors,=20etc=E2=80=A6=20more=20than=20once=20(#13?=
=?UTF-8?q?78)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We're currently re-registering "common" capabilities per workspace
folder. This causes VSCode to make multiple requests to the server for
things like hover causing higher CPU usage. This then results multiple
hovers being displayed. What's worse is that for some scenarios where
the server has to internally do a restart the old registrations weren't
getting disposed of because of a race condition when calling it
concurrently for multiple folders.
I've done two things to address this:
- Common capability registration will only happen after all project
folders have been initialized — hopefully this doesn't cause any
problems (though if it does it's revealing a bigger underlying one)
- Common capabilities will be explicitly disposed before registering
again
This should mean that this only happens one time per server
initialization.
Fixes #1371
---
.../tailwindcss-language-server/src/tw.ts | 30 +++++++++++++++----
1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index 882d1be2..bc1b3bcc 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -174,6 +174,10 @@ export class TW {
}
}
+ if (results.some((result) => result.status === 'fulfilled')) {
+ await this.updateCommonCapabilities()
+ }
+
await this.listenForEvents()
}
@@ -628,8 +632,6 @@ export class TW {
console.log(`[Global] Initializing projects...`)
- await this.updateCommonCapabilities()
-
// init projects for documents that are _already_ open
let readyDocuments: string[] = []
let enabledProjectCount = 0
@@ -896,6 +898,7 @@ export class TW {
capabilities.add(DidChangeConfigurationNotification.type, undefined)
}
+ this.commonRegistrations?.dispose()
this.commonRegistrations = await this.connection.client.register(capabilities)
}
@@ -907,7 +910,7 @@ export class TW {
}
private lastTriggerCharacters: Set | undefined
- private completionRegistration: Disposable | undefined
+ private completionRegistration: Promise | undefined
private async updateTriggerCharacters() {
// If the client does not suppory dynamic registration of completions then
// we cannot update the set of trigger characters
@@ -938,12 +941,27 @@ export class TW {
this.lastTriggerCharacters = chars
- this.completionRegistration?.dispose()
- this.completionRegistration = await this.connection.client.register(CompletionRequest.type, {
+ let current = this.completionRegistration
+ this.completionRegistration = this.connection.client.register(CompletionRequest.type, {
documentSelector: null,
resolveProvider: true,
triggerCharacters: Array.from(chars),
})
+
+ // NOTE:
+ // This weird setup works around a race condition where multiple projects
+ // with different separators update their capabilities at the same time. It
+ // is extremely unlikely but it could cause `CompletionRequest` to be
+ // registered more than once with the LSP client.
+ //
+ // We store the promises meaning everything up to this point is synchronous
+ // so it should be fine but really the proper fix here is to:
+ // - Refactor workspace folder initialization so discovery, initialization,
+ // file events, config watchers, etc… are all shared.
+ // - Remove the need for the "restart" concept in the server for as much as
+ // possible. Each project should be capable of reloading its modules.
+ await current?.then((r) => r.dispose())
+ await this.completionRegistration
}
private getProject(document: TextDocumentIdentifier): ProjectService {
@@ -1134,7 +1152,7 @@ export class TW {
this.commonRegistrations = undefined
this.lastTriggerCharacters.clear()
- this.completionRegistration?.dispose()
+ this.completionRegistration?.then((r) => r.dispose())
this.completionRegistration = undefined
this.disposables.forEach((d) => d.dispose())
From d6276d654b7cbb8545653d88c13965065a182cb9 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 20 May 2025 11:59:06 -0400
Subject: [PATCH 69/93] Update changelog
---
packages/vscode-tailwindcss/CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 878b52f3..fafc049d 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -3,6 +3,8 @@
## Prerelease
- Display color swatches when using `before`/`after` variants ([#1374](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1374))
+- Clear trigger characters when restarting server ([#1375](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1375))
+- Don't register ability to hover, request colors, etc… more than once ([#1378](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1378))
# 0.14.17
From f9496db14b32b1e80abf1fc8ae73546edd0e5163 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Tue, 20 May 2025 11:59:29 -0400
Subject: [PATCH 70/93] 0.14.18
---
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 e011083c..4fea6905 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.17",
+ "version": "0.14.18",
"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 6fc849fa..f73f44fe 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.17",
+ "version": "0.14.18",
"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 fafc049d..51b612cc 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -2,6 +2,10 @@
## Prerelease
+- Nothing yet!
+
+# 0.14.18
+
- Display color swatches when using `before`/`after` variants ([#1374](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1374))
- Clear trigger characters when restarting server ([#1375](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1375))
- Don't register ability to hover, request colors, etc… more than once ([#1378](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1378))
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 2539ccda..7c6aaeae 100644
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "vscode-tailwindcss",
- "version": "0.14.17",
+ "version": "0.14.18",
"displayName": "Tailwind CSS IntelliSense",
"description": "Intelligent Tailwind CSS tooling for VS Code",
"author": "Brad Cornes ",
From 9c2a9d5e29166fd2d86ea6307ba53247266c14fb Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 22 May 2025 17:52:45 -0400
Subject: [PATCH 71/93] Speed up project selector matching (#1381)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When a request comes in to the language server for a document we have to
determine what project that document belongs to. This is because your
workspace can have many tailwind config files and potentially across
different Tailwind CSS versions.
The process for this happens in two stages:
1. Initialization computes a list of selectors for a project. These
selectors contain glob patterns that match a filepath to one or more
projects. In addition to the glob pattern is a priority which is a
"lowest match wins" scale. So things like user configured patterns in
settings are the most important, then your config files, then content
from the `content` array in v3 or detected sources in v4, approximate
path matches based on folder, etc…
2. When we get a request for hovers, completions, etc… we check every
project against the list of selectors to see if it matches and at what
priority. This involves compiling glob patterns.
The *lowest* priority match wins. If multiple projects match a document
at the same priority then first match wins. If a project contains a
selector that discards a particular document then that project is
removed from consideration before continuing.
Now for the problem, we were re-compiling globs *over and over and over
again*. Normally, for a small project this isn't an issue but automatic
content detection means that a large number of paths can be returned.
And if you have lots of projects? Welp… this adds up. What's more is
that when VSCode needed to compute document symbols requests were made
to our language server (… i don't know why actually) which then incurred
a perf hit caused by these globs being repeatedly recompiled… and since
this is effectively a single threaded operation it delays any waiting
promises.
So this PR does two things:
- It moves the sorting that was happening for every request for every
project to happen *during project initialization*. If selectors are
computed or recomputed they are sorted then. We do this sorting to move
the negative matchers to the front so we can disqualify files from
matching a project quicker.
- We cache the compiled matchers. This is especially important if you
have several v4 projects and many of them list the same paths.
In a realworld project this lowered the time to show suggestions from
emmet from **4 SECONDS** (omg) to about 15-20ms on an M3 Max machine.
aside: There was also sometimes a delay in time even getting completion
requests due to the single-threaded nature of JS. That could also end up
being multiple seconds. So in reality the time could be from range from
4–8s depending on when you made the request.
---
.../src/matching.ts | 24 +++++++++
.../src/project-locator.ts | 7 +++
.../tailwindcss-language-server/src/tw.ts | 50 ++++++-------------
.../src/util/isExcluded.ts | 1 +
4 files changed, 48 insertions(+), 34 deletions(-)
create mode 100644 packages/tailwindcss-language-server/src/matching.ts
diff --git a/packages/tailwindcss-language-server/src/matching.ts b/packages/tailwindcss-language-server/src/matching.ts
new file mode 100644
index 00000000..a373b116
--- /dev/null
+++ b/packages/tailwindcss-language-server/src/matching.ts
@@ -0,0 +1,24 @@
+import picomatch from 'picomatch'
+import { DefaultMap } from './util/default-map'
+
+export interface PathMatcher {
+ anyMatches(pattern: string, paths: string[]): boolean
+ clear(): void
+}
+
+export function createPathMatcher(): PathMatcher {
+ let matchers = new DefaultMap((pattern) => {
+ // Escape picomatch special characters so they're matched literally
+ pattern = pattern.replace(/[\[\]{}()]/g, (m) => `\\${m}`)
+
+ return picomatch(pattern, { dot: true })
+ })
+
+ return {
+ anyMatches: (pattern, paths) => {
+ let check = matchers.get(pattern)
+ return paths.some((path) => check(path))
+ },
+ clear: () => matchers.clear(),
+ }
+}
diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts
index 0f3751ed..99d68505 100644
--- a/packages/tailwindcss-language-server/src/project-locator.ts
+++ b/packages/tailwindcss-language-server/src/project-locator.ts
@@ -815,5 +815,12 @@ export async function calculateDocumentSelectors(
documentSelectors.findIndex(({ pattern: p }) => p === pattern) === index,
)
+ // Move all the negated patterns to the front
+ selectors = selectors.sort((a, z) => {
+ if (a.pattern.startsWith('!') && !z.pattern.startsWith('!')) return -1
+ if (!a.pattern.startsWith('!') && z.pattern.startsWith('!')) return 1
+ return 0
+ })
+
return selectors
}
diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts
index bc1b3bcc..da8de46c 100644
--- a/packages/tailwindcss-language-server/src/tw.ts
+++ b/packages/tailwindcss-language-server/src/tw.ts
@@ -56,6 +56,7 @@ import { ProjectLocator, type ProjectConfig } from './project-locator'
import type { TailwindCssSettings } from '@tailwindcss/language-service/src/util/state'
import { createResolver, Resolver } from './resolver'
import { analyzeStylesheet } from './version-guesser.js'
+import { createPathMatcher, PathMatcher } from './matching.js'
const TRIGGER_CHARACTERS = [
// class attributes
@@ -104,12 +105,14 @@ export class TW {
private watched: string[] = []
private settingsCache: SettingsCache
+ private pathMatcher: PathMatcher
constructor(private connection: Connection) {
this.documentService = new DocumentService(this.connection)
this.projects = new Map()
this.projectCounter = 0
this.settingsCache = createSettingsCache(connection)
+ this.pathMatcher = createPathMatcher()
}
async init(): Promise