diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json index 75842624..1f72e816 100644 --- a/packages/tailwindcss-language-server/package.json +++ b/packages/tailwindcss-language-server/package.json @@ -59,6 +59,7 @@ "color-name": "1.1.4", "culori": "^4.0.1", "debounce": "1.2.0", + "dedent": "^1.5.3", "deepmerge": "4.2.2", "dlv": "1.1.3", "dset": "3.1.2", @@ -80,7 +81,8 @@ "resolve": "1.20.0", "rimraf": "3.0.2", "stack-trace": "0.0.10", - "tailwindcss": "3.4.4", + "tailwindcss": "3.4.17", + "tailwindcss-v4": "npm:tailwindcss@4.0.0", "tsconfck": "^3.1.4", "tsconfig-paths": "^4.2.0", "typescript": "5.3.3", diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts index 2a165eee..063211f1 100644 --- a/packages/tailwindcss-language-server/src/project-locator.ts +++ b/packages/tailwindcss-language-server/src/project-locator.ts @@ -454,6 +454,24 @@ export class ProjectLocator { } } catch {} + // A local version of Tailwind CSS was not found so we need to use the + // fallback bundled with the language server. This is especially important + // for projects using the standalone CLI. + + // This is a v4-style CSS config + if (config.type === 'css') { + let { version } = require('tailwindcss-v4/package.json') + // @ts-ignore + let mod = await import('tailwindcss-v4') + let features = supportedFeatures(version, mod) + + return { + version, + features, + isDefaultVersion: true, + } + } + let { version } = require('tailwindcss/package.json') let mod = require('tailwindcss') let features = supportedFeatures(version, mod) diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts index 38017d36..5d9e511a 100644 --- a/packages/tailwindcss-language-server/src/projects.ts +++ b/packages/tailwindcss-language-server/src/projects.ts @@ -489,8 +489,8 @@ export async function createProjectService( log('CSS-based configuration is not supported before Tailwind CSS v4') state.enabled = false enabled = false - // CSS-based configuration is not supported before Tailwind CSS v4 so bail - // TODO: Fall back to built-in version of v4 + + // The fallback to a bundled v4 is in the catch block return } @@ -673,6 +673,31 @@ export async function createProjectService( } catch (_) {} } } catch (error) { + if (projectConfig.config.source === 'css') { + // @ts-ignore + let tailwindcss = await import('tailwindcss-v4') + let tailwindcssVersion = require('tailwindcss-v4/package.json').version + let features = supportedFeatures(tailwindcssVersion, tailwindcss) + + log('Failed to load workspace modules.') + log(`Using bundled version of \`tailwindcss\`: v${tailwindcssVersion}`) + + state.configPath = configPath + state.version = tailwindcssVersion + state.isCssConfig = true + state.v4 = true + state.v4Fallback = true + state.jit = true + state.modules = { + tailwindcss: { version: tailwindcssVersion, module: tailwindcss }, + postcss: { version: null, module: null }, + resolveConfig: { module: null }, + loadConfig: { module: null }, + } + + return tryRebuild() + } + let util = await import('node:util') console.error(util.format(error)) @@ -786,6 +811,7 @@ export async function createProjectService( state.modules.tailwindcss.module, state.configPath, css, + state.v4Fallback ?? false, ) state.designSystem = designSystem @@ -1063,6 +1089,7 @@ export async function createProjectService( state.modules.tailwindcss.module, state.configPath, css, + state.v4Fallback ?? false, ) } catch (err) { console.error(err) diff --git a/packages/tailwindcss-language-server/src/testing/index.ts b/packages/tailwindcss-language-server/src/testing/index.ts new file mode 100644 index 00000000..1a9c2d63 --- /dev/null +++ b/packages/tailwindcss-language-server/src/testing/index.ts @@ -0,0 +1,107 @@ +import { afterAll, onTestFinished, test, TestOptions } from 'vitest' +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 { + /** The "cwd" for this test */ + root: string +} + +export interface Storage { + /** A list of files and their content */ + [filePath: string]: string | Uint8Array +} + +export interface TestConfig { + name: string + fs: Storage + 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) + let extras = await config.prepare?.(utils) + + await config.handle({ + ...utils, + ...extras, + }) + }) +} + +async function setup(config: TestConfig): Promise { + let randomId = Math.random().toString(36).substring(7) + + let baseDir = path.resolve(process.cwd(), `../../.debug/${randomId}`) + let doneDir = path.resolve(process.cwd(), `../../.debug/${randomId}-done`) + + await fs.mkdir(baseDir, { recursive: true }) + + await prepareFileSystem(baseDir, config.fs) + await installDependencies(baseDir, config.fs) + + onTestFinished(async (result) => { + // Once done, move all the files to a new location + await fs.rename(baseDir, doneDir) + + if (result.state === 'fail') return + + if (path.sep === '\\') return + + // Remove the directory on *nix systems. Recursive removal on Windows will + // randomly fail b/c its slow and buggy. + await fs.rm(doneDir, { recursive: true }) + }) + + return { + root: baseDir, + } +} + +async function prepareFileSystem(base: string, storage: Storage) { + // Create a temporary directory to store the test files + await fs.mkdir(base, { recursive: true }) + + // Write the files to disk + for (let [filepath, content] of Object.entries(storage)) { + let fullPath = path.resolve(base, filepath) + await fs.mkdir(path.dirname(fullPath), { recursive: true }) + await fs.writeFile(fullPath, content, { encoding: 'utf-8' }) + } +} + +async function installDependencies(base: string, storage: Storage) { + for (let filepath of Object.keys(storage)) { + if (!filepath.endsWith('package.json')) continue + + let pkgDir = path.dirname(filepath) + let basePath = path.resolve(pkgDir, base) + + await installDependenciesIn(basePath) + } +} + +async function installDependenciesIn(dir: string) { + console.log(`Installing dependencies in ${dir}`) + + await new Promise((resolve, reject) => { + proc.exec('npm install --package-lock=false', { cwd: dir }, (err, res) => { + if (err) { + reject(err) + } else { + resolve(res) + } + }) + }) +} + +export const css = dedent +export const html = dedent +export const js = dedent +export const json = dedent diff --git a/packages/tailwindcss-language-server/src/util/v4/assets.ts b/packages/tailwindcss-language-server/src/util/v4/assets.ts new file mode 100644 index 00000000..4eff6e0f --- /dev/null +++ b/packages/tailwindcss-language-server/src/util/v4/assets.ts @@ -0,0 +1,19 @@ +import index from 'tailwindcss-v4/index.css' +import preflight from 'tailwindcss-v4/preflight.css' +import theme from 'tailwindcss-v4/theme.css' +import utilities from 'tailwindcss-v4/utilities.css' + +export const assets = { + tailwindcss: index, + 'tailwindcss/index': index, + 'tailwindcss/index.css': index, + + 'tailwindcss/preflight': preflight, + 'tailwindcss/preflight.css': preflight, + + 'tailwindcss/theme': theme, + 'tailwindcss/theme.css': theme, + + 'tailwindcss/utilities': utilities, + 'tailwindcss/utilities.css': utilities, +} 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 8211bac6..f32305ce 100644 --- a/packages/tailwindcss-language-server/src/util/v4/design-system.ts +++ b/packages/tailwindcss-language-server/src/util/v4/design-system.ts @@ -8,6 +8,7 @@ import { resolveCssImports } from '../../css' import { Resolver } from '../../resolver' import { pathToFileURL } from '../../utils' import type { Jiti } from 'jiti/lib/types' +import { assets } from './assets' const HAS_V4_IMPORT = /@import\s*(?:'tailwindcss'|"tailwindcss")/ const HAS_V4_THEME = /@theme\s*\{/ @@ -79,6 +80,7 @@ export async function loadDesignSystem( tailwindcss: any, filepath: string, css: string, + isFallback: boolean, ): Promise { // This isn't a v4 project if (!tailwindcss.__unstable__loadDesignSystem) return null @@ -151,6 +153,12 @@ export async function loadDesignSystem( content: await fs.readFile(resolved, 'utf-8'), } } catch (err) { + if (isFallback && id in assets) { + console.error(`Loading fallback stylesheet for: ${id}`) + + return { base, content: assets[id] } + } + console.error(`Unable to load stylesheet: ${id}`, err) return { base, content: '' } } diff --git a/packages/tailwindcss-language-server/tests/common.ts b/packages/tailwindcss-language-server/tests/common.ts index c0356ca7..e78b94ea 100644 --- a/packages/tailwindcss-language-server/tests/common.ts +++ b/packages/tailwindcss-language-server/tests/common.ts @@ -1,6 +1,6 @@ import * as path from 'node:path' import { beforeAll, describe } from 'vitest' -import { connect } from './connection' +import { connect, launch } from './connection' import { CompletionRequest, ConfigurationRequest, @@ -12,6 +12,7 @@ import { RegistrationRequest, InitializeParams, DidOpenTextDocumentParams, + MessageType, } from 'vscode-languageserver-protocol' import type { ClientCapabilities, ProtocolConnection } from 'vscode-languageclient' import type { Feature } from '@tailwindcss/language-service/src/features' @@ -43,14 +44,45 @@ interface FixtureContext } } +export interface InitOptions { + /** + * How to connect to the LSP: + * - `in-band` runs the server in the same process (default) + * - `spawn` launches the binary as a separate process, connects via stdio, + * and requires a rebuild of the server after making changes. + */ + mode?: 'in-band' | 'spawn' + + /** + * Extra initialization options to pass to the LSP + */ + options?: Record +} + export async function init( fixture: string | string[], - options: Record = {}, + opts: InitOptions = {}, ): Promise { let settings = {} let docSettings = new Map() - const { client } = await connect() + const { client } = opts?.mode === 'spawn' ? await launch() : await connect() + + if (opts?.mode === 'spawn') { + client.onNotification('window/logMessage', ({ message, type }) => { + if (type === MessageType.Error) { + console.error(message) + } else if (type === MessageType.Warning) { + console.warn(message) + } else if (type === MessageType.Info) { + console.info(message) + } else if (type === MessageType.Log) { + console.log(message) + } else if (type === MessageType.Debug) { + console.debug(message) + } + }) + } const capabilities: ClientCapabilities = { textDocument: { @@ -162,7 +194,7 @@ export async function init( workspaceFolders, initializationOptions: { testMode: true, - ...options, + ...(opts.options ?? {}), }, } as InitializeParams) diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js index dc756f06..9bf3f73f 100644 --- a/packages/tailwindcss-language-server/tests/completions/completions.test.js +++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js @@ -281,7 +281,7 @@ withFixture('basic', (c) => { expect(resolved).toEqual({ ...item, - detail: '--tw-bg-opacity: 1; background-color: rgb(239 68 68 / var(--tw-bg-opacity));', + detail: '--tw-bg-opacity: 1; background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1));', documentation: '#ef4444', }) }) diff --git a/packages/tailwindcss-language-server/tests/env/custom-languages.test.js b/packages/tailwindcss-language-server/tests/env/custom-languages.test.js index 34725c6f..75663051 100644 --- a/packages/tailwindcss-language-server/tests/env/custom-languages.test.js +++ b/packages/tailwindcss-language-server/tests/env/custom-languages.test.js @@ -28,8 +28,10 @@ test('Unknown languages do not provide completions', async ({ expect }) => { test('Custom languages may be specified via init options (deprecated)', async ({ expect }) => { let c = await init('basic', { - userLanguages: { - 'some-lang': 'html', + options: { + userLanguages: { + 'some-lang': 'html', + }, }, }) @@ -47,7 +49,7 @@ test('Custom languages may be specified via init options (deprecated)', async ({ contents: { language: 'css', value: - '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity)) /* #000000 */;\n}', + '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1)) /* #000000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 21 } }, }) @@ -86,7 +88,7 @@ test('Custom languages may be specified via settings', async ({ expect }) => { contents: { language: 'css', value: - '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity)) /* #000000 */;\n}', + '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1)) /* #000000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 21 } }, }) @@ -102,8 +104,10 @@ test('Custom languages may be specified via settings', async ({ expect }) => { test('Custom languages are merged from init options and settings', async ({ expect }) => { let c = await init('basic', { - userLanguages: { - 'some-lang': 'html', + options: { + userLanguages: { + 'some-lang': 'html', + }, }, }) @@ -151,7 +155,7 @@ test('Custom languages are merged from init options and settings', async ({ expe contents: { language: 'css', value: - '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity)) /* #000000 */;\n}', + '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1)) /* #000000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 21 } }, }) @@ -160,7 +164,7 @@ test('Custom languages are merged from init options and settings', async ({ expe contents: { language: 'css', value: - '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity)) /* #000000 */;\n}', + '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1)) /* #000000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 21 } }, }) @@ -171,8 +175,10 @@ test('Custom languages are merged from init options and settings', async ({ expe test('Language mappings from settings take precedence', async ({ expect }) => { let c = await init('basic', { - userLanguages: { - 'some-lang': 'css', + options: { + userLanguages: { + 'some-lang': 'css', + }, }, }) @@ -198,7 +204,7 @@ test('Language mappings from settings take precedence', async ({ expect }) => { contents: { language: 'css', value: - '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity)) /* #000000 */;\n}', + '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1)) /* #000000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 21 } }, }) diff --git a/packages/tailwindcss-language-server/tests/env/document-selection.test.js b/packages/tailwindcss-language-server/tests/env/document-selection.test.js index 0789760a..4ddd8599 100644 --- a/packages/tailwindcss-language-server/tests/env/document-selection.test.js +++ b/packages/tailwindcss-language-server/tests/env/document-selection.test.js @@ -18,7 +18,7 @@ withFixture('document-selection/basic', (c) => { contents: { language: 'css', value: - '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity)) /* #000000 */;\n}', + '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1)) /* #000000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 21 } }, }) @@ -40,7 +40,7 @@ withFixture('document-selection/(parens)', (c) => { contents: { language: 'css', value: - '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity)) /* #000000 */;\n}', + '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1)) /* #000000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 21 } }, }) @@ -62,7 +62,7 @@ withFixture('document-selection/[brackets]', (c) => { contents: { language: 'css', value: - '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity)) /* #000000 */;\n}', + '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1)) /* #000000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 21 } }, }) @@ -84,7 +84,7 @@ withFixture('document-selection/{curlies}', (c) => { contents: { language: 'css', value: - '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity)) /* #000000 */;\n}', + '.bg-\\[\\#000\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1)) /* #000000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 21 } }, }) 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 0bdccd13..44b4cb64 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 @@ -13,7 +13,7 @@ withFixture('multi-config-content', (c) => { contents: { language: 'css', value: - '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(255 0 0 / var(--tw-bg-opacity)) /* #ff0000 */;\n}', + '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(255 0 0 / var(--tw-bg-opacity, 1)) /* #ff0000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } }, }) @@ -30,7 +30,7 @@ withFixture('multi-config-content', (c) => { contents: { language: 'css', value: - '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 255 / var(--tw-bg-opacity)) /* #0000ff */;\n}', + '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 255 / var(--tw-bg-opacity, 1)) /* #0000ff */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } }, }) diff --git a/packages/tailwindcss-language-server/tests/env/multi-config.test.js b/packages/tailwindcss-language-server/tests/env/multi-config.test.js index 5050314c..1522d157 100644 --- a/packages/tailwindcss-language-server/tests/env/multi-config.test.js +++ b/packages/tailwindcss-language-server/tests/env/multi-config.test.js @@ -13,7 +13,7 @@ withFixture('multi-config', (c) => { contents: { language: 'css', value: - '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(255 0 0 / var(--tw-bg-opacity)) /* #ff0000 */;\n}', + '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(255 0 0 / var(--tw-bg-opacity, 1)) /* #ff0000 */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } }, }) @@ -30,7 +30,7 @@ withFixture('multi-config', (c) => { contents: { language: 'css', value: - '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 255 / var(--tw-bg-opacity)) /* #0000ff */;\n}', + '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 255 / var(--tw-bg-opacity, 1)) /* #0000ff */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } }, }) diff --git a/packages/tailwindcss-language-server/tests/env/v4.test.js b/packages/tailwindcss-language-server/tests/env/v4.test.js new file mode 100644 index 00000000..f393f9f1 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/env/v4.test.js @@ -0,0 +1,227 @@ +import { expect } from 'vitest' +import { init } from '../common' +import { HoverRequest } from 'vscode-languageserver' +import { css, defineTest, js, json } from '../../src/testing' +import dedent from 'dedent' +import { CompletionRequest } from 'vscode-languageserver-protocol' + +defineTest({ + name: 'v4, no npm, uses fallback', + fs: { + 'app.css': css` + @import 'tailwindcss'; + `, + }, + prepare: async ({ root }) => ({ c: await init(root) }), + handle: async ({ c }) => { + let textDocument = await c.openDocument({ + lang: 'html', + text: '
', + }) + + expect(c.project).toMatchObject({ + tailwind: { + version: '4.0.0', + isDefaultVersion: true, + }, + }) + + let hover = await c.sendRequest(HoverRequest.type, { + textDocument, + + //
({ + c: await init(root, { mode: 'spawn' }), + }), + + handle: async ({ c }) => { + let textDocument = await c.openDocument({ + lang: 'html', + text: '
', + }) + + expect(c.project).toMatchObject({ + tailwind: { + version: '4.0.0', + isDefaultVersion: true, + }, + }) + + let hover = await c.sendRequest(HoverRequest.type, { + textDocument, + + //
+ // ^ + position: { line: 0, character: 13 }, + }) + + expect(hover).toEqual({ + contents: { + language: 'css', + value: dedent` + .underline { + text-decoration-line: underline; + } + `, + }, + range: { + start: { line: 0, character: 12 }, + end: { line: 0, character: 21 }, + }, + }) + + let hoverFromPlugin = await c.sendRequest(HoverRequest.type, { + textDocument, + + //
+ // ^ + position: { line: 0, character: 23 }, + }) + + expect(hoverFromPlugin).toEqual({ + contents: { + language: 'css', + value: dedent` + .example { + color: red; + } + `, + }, + range: { + start: { line: 0, character: 22 }, + end: { line: 0, character: 29 }, + }, + }) + }, +}) + +defineTest({ + name: 'v4, with npm, uses local', + fs: { + 'package.json': json` + { + "dependencies": { + "tailwindcss": "4.0.1" + } + } + `, + 'app.css': css` + @import 'tailwindcss'; + `, + }, + prepare: async ({ root }) => ({ c: await init(root) }), + handle: async ({ c }) => { + let textDocument = await c.openDocument({ + lang: 'html', + text: '
', + }) + + expect(c.project).toMatchObject({ + tailwind: { + version: '4.0.1', + isDefaultVersion: false, + }, + }) + + let hover = await c.sendRequest(HoverRequest.type, { + textDocument, + + //
{ expected: '.bg-red-500 {\n' + ' --tw-bg-opacity: 1;\n' + - ' background-color: rgb(239 68 68 / var(--tw-bg-opacity)) /* #ef4444 */;\n' + + ' background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1)) /* #ef4444 */;\n' + '}', expectedRange: { start: { line: 0, character: 12 }, diff --git a/packages/tailwindcss-language-server/vitest.config.ts b/packages/tailwindcss-language-server/vitest.config.ts index bb674195..c0105d1b 100644 --- a/packages/tailwindcss-language-server/vitest.config.ts +++ b/packages/tailwindcss-language-server/vitest.config.ts @@ -4,6 +4,7 @@ import tsconfigPaths from 'vite-tsconfig-paths' export default defineConfig({ test: { testTimeout: 15000, + css: true, }, plugins: [tsconfigPaths()], diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index d16ca186..621add49 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -126,6 +126,7 @@ export interface State { } v4?: boolean + v4Fallback?: boolean designSystem?: DesignSystem browserslist?: string[] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 596c70aa..62eaa1bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,25 +37,25 @@ importers: version: 2.0.3 '@tailwindcss/aspect-ratio': specifier: 0.4.2 - version: 0.4.2(tailwindcss@3.4.4) + version: 0.4.2(tailwindcss@3.4.17) '@tailwindcss/container-queries': specifier: 0.1.0 - version: 0.1.0(tailwindcss@3.4.4) + version: 0.1.0(tailwindcss@3.4.17) '@tailwindcss/forms': specifier: 0.5.3 - version: 0.5.3(tailwindcss@3.4.4) + version: 0.5.3(tailwindcss@3.4.17) '@tailwindcss/language-service': specifier: workspace:* version: link:../tailwindcss-language-service '@tailwindcss/line-clamp': specifier: 0.4.2 - version: 0.4.2(tailwindcss@3.4.4) + version: 0.4.2(tailwindcss@3.4.17) '@tailwindcss/oxide': specifier: ^4.0.0-alpha.19 version: 4.0.0-alpha.19 '@tailwindcss/typography': specifier: 0.5.7 - version: 0.5.7(tailwindcss@3.4.4) + version: 0.5.7(tailwindcss@3.4.17) '@types/color-name': specifier: ^1.1.3 version: 1.1.4 @@ -110,6 +110,9 @@ importers: debounce: specifier: 1.2.0 version: 1.2.0 + dedent: + specifier: ^1.5.3 + version: 1.5.3 deepmerge: specifier: 4.2.2 version: 4.2.2 @@ -174,8 +177,11 @@ importers: specifier: 0.0.10 version: 0.0.10 tailwindcss: - specifier: 3.4.4 - version: 3.4.4 + specifier: 3.4.17 + version: 3.4.17 + tailwindcss-v4: + specifier: npm:tailwindcss@4.0.0 + version: tailwindcss@4.0.0 tsconfck: specifier: ^3.1.4 version: 3.1.4(typescript@5.3.3) @@ -1294,6 +1300,14 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-eql@4.1.4: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} @@ -1706,12 +1720,8 @@ packages: resolution: {integrity: sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==} hasBin: true - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - - lilconfig@3.1.2: - resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} line-column@1.0.2: @@ -1791,6 +1801,10 @@ packages: resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -1873,6 +1887,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} @@ -2057,6 +2076,9 @@ packages: picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -2128,8 +2150,8 @@ packages: resolution: {integrity: sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==} engines: {node: '>=4'} - postcss-selector-parser@6.1.1: - resolution: {integrity: sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==} + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} postcss-value-parser@4.2.0: @@ -2143,6 +2165,10 @@ packages: resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.1: + resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} + engines: {node: ^10 || ^12 || >=14} + prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} @@ -2339,6 +2365,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + spawn-command@0.0.2: resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} @@ -2435,11 +2465,14 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tailwindcss@3.4.4: - resolution: {integrity: sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==} + tailwindcss@3.4.17: + resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} engines: {node: '>=14.0.0'} hasBin: true + tailwindcss@4.0.0: + resolution: {integrity: sha512-ULRPI3A+e39T7pSaf1xoi58AqqJxVCLg8F/uM5A3FadUbnyDTgltVnXJvdkTjwCOGA6NazqHVcwPJC5h2vRYVQ==} + tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -3077,22 +3110,22 @@ snapshots: '@sinclair/typebox@0.27.8': {} - '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.4)': + '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.17)': dependencies: - tailwindcss: 3.4.4 + tailwindcss: 3.4.17 - '@tailwindcss/container-queries@0.1.0(tailwindcss@3.4.4)': + '@tailwindcss/container-queries@0.1.0(tailwindcss@3.4.17)': dependencies: - tailwindcss: 3.4.4 + tailwindcss: 3.4.17 - '@tailwindcss/forms@0.5.3(tailwindcss@3.4.4)': + '@tailwindcss/forms@0.5.3(tailwindcss@3.4.17)': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.4 + tailwindcss: 3.4.17 - '@tailwindcss/line-clamp@0.4.2(tailwindcss@3.4.4)': + '@tailwindcss/line-clamp@0.4.2(tailwindcss@3.4.17)': dependencies: - tailwindcss: 3.4.4 + tailwindcss: 3.4.17 '@tailwindcss/oxide-android-arm64@4.0.0-alpha.19': optional: true @@ -3137,13 +3170,13 @@ snapshots: '@tailwindcss/oxide-linux-x64-musl': 4.0.0-alpha.19 '@tailwindcss/oxide-win32-x64-msvc': 4.0.0-alpha.19 - '@tailwindcss/typography@0.5.7(tailwindcss@3.4.4)': + '@tailwindcss/typography@0.5.7(tailwindcss@3.4.17)': dependencies: lodash.castarray: 4.4.0 lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.4.4 + tailwindcss: 3.4.17 '@types/braces@3.0.1': {} @@ -3541,6 +3574,8 @@ snapshots: mimic-response: 3.1.0 optional: true + dedent@1.5.3: {} + deep-eql@4.1.4: dependencies: type-detect: 4.1.0 @@ -3723,7 +3758,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fastq@1.17.1: dependencies: @@ -3973,9 +4008,7 @@ snapshots: transitivePeerDependencies: - supports-color - lilconfig@2.1.0: {} - - lilconfig@3.1.2: {} + lilconfig@3.1.3: {} line-column@1.0.2: dependencies: @@ -4062,6 +4095,11 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + mime@1.6.0: {} mimic-fn@4.0.0: {} @@ -4134,6 +4172,8 @@ snapshots: nanoid@3.3.7: {} + nanoid@3.3.8: {} + napi-build-utils@1.0.2: optional: true @@ -4307,6 +4347,8 @@ snapshots: picocolors@1.0.1: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} picomatch@4.0.2: {} @@ -4325,9 +4367,9 @@ snapshots: dependencies: find-up: 3.0.0 - postcss-import@15.1.0(postcss@8.4.31): + postcss-import@15.1.0(postcss@8.5.1): dependencies: - postcss: 8.4.31 + postcss: 8.5.1 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.20.0 @@ -4339,27 +4381,27 @@ snapshots: read-cache: 1.0.0 resolve: 1.20.0 - postcss-js@4.0.1(postcss@8.4.31): + postcss-js@4.0.1(postcss@8.5.1): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.31 + postcss: 8.5.1 postcss-load-config@3.0.1: dependencies: cosmiconfig: 7.1.0 import-cwd: 3.0.0 - postcss-load-config@4.0.2(postcss@8.4.31): + postcss-load-config@4.0.2(postcss@8.5.1): dependencies: - lilconfig: 3.1.2 + lilconfig: 3.1.3 yaml: 2.5.0 optionalDependencies: - postcss: 8.4.31 + postcss: 8.5.1 - postcss-nested@6.2.0(postcss@8.4.31): + postcss-nested@6.2.0(postcss@8.5.1): dependencies: - postcss: 8.4.31 - postcss-selector-parser: 6.1.1 + postcss: 8.5.1 + postcss-selector-parser: 6.1.2 postcss-selector-parser@6.0.10: dependencies: @@ -4372,7 +4414,7 @@ snapshots: indexes-of: 1.0.1 uniq: 1.0.1 - postcss-selector-parser@6.1.1: + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -4391,6 +4433,12 @@ snapshots: picocolors: 1.0.1 source-map-js: 1.2.0 + postcss@8.5.1: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prebuild-install@7.1.2: dependencies: detect-libc: 2.0.3 @@ -4624,6 +4672,8 @@ snapshots: source-map-js@1.2.0: {} + source-map-js@1.2.1: {} + spawn-command@0.0.2: {} spdx-compare@1.0.0: @@ -4730,7 +4780,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwindcss@3.4.4: + tailwindcss@3.4.17: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -4741,22 +4791,24 @@ snapshots: glob-parent: 6.0.2 is-glob: 4.0.3 jiti: 1.21.6 - lilconfig: 2.1.0 - micromatch: 4.0.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 - picocolors: 1.0.1 - postcss: 8.4.31 - postcss-import: 15.1.0(postcss@8.4.31) - postcss-js: 4.0.1(postcss@8.4.31) - postcss-load-config: 4.0.2(postcss@8.4.31) - postcss-nested: 6.2.0(postcss@8.4.31) - postcss-selector-parser: 6.1.1 + picocolors: 1.1.1 + postcss: 8.5.1 + postcss-import: 15.1.0(postcss@8.5.1) + postcss-js: 4.0.1(postcss@8.5.1) + postcss-load-config: 4.0.2(postcss@8.5.1) + postcss-nested: 6.2.0(postcss@8.5.1) + postcss-selector-parser: 6.1.2 resolve: 1.22.8 sucrase: 3.35.0 transitivePeerDependencies: - ts-node + tailwindcss@4.0.0: {} + tapable@2.2.1: {} tar-fs@2.1.1: