diff --git a/packages/tailwindcss-language-server/package.json b/packages/tailwindcss-language-server/package.json index 813e139be..d877569a0 100644 --- a/packages/tailwindcss-language-server/package.json +++ b/packages/tailwindcss-language-server/package.json @@ -36,7 +36,7 @@ "@tailwindcss/forms": "0.5.3", "@tailwindcss/language-service": "workspace:*", "@tailwindcss/line-clamp": "0.4.2", - "@tailwindcss/oxide": "^4.0.0-alpha.16", + "@tailwindcss/oxide": "^4.0.0-alpha.19", "@tailwindcss/typography": "0.5.7", "@types/color-name": "^1.1.3", "@types/culori": "^2.1.0", diff --git a/packages/tailwindcss-language-server/src/css/extract-source-directives.ts b/packages/tailwindcss-language-server/src/css/extract-source-directives.ts new file mode 100644 index 000000000..9de33a9b3 --- /dev/null +++ b/packages/tailwindcss-language-server/src/css/extract-source-directives.ts @@ -0,0 +1,13 @@ +import type { Plugin } from 'postcss' + +export function extractSourceDirectives(sources: string[]): Plugin { + return { + postcssPlugin: 'extract-at-rules', + AtRule: { + source: ({ params }) => { + if (params[0] !== '"' && params[0] !== "'") return + sources.push(params.slice(1, -1)) + }, + }, + } +} diff --git a/packages/tailwindcss-language-server/src/css/fix-relative-paths.ts b/packages/tailwindcss-language-server/src/css/fix-relative-paths.ts new file mode 100644 index 000000000..46d5d1693 --- /dev/null +++ b/packages/tailwindcss-language-server/src/css/fix-relative-paths.ts @@ -0,0 +1,69 @@ +import path from 'node:path' +import type { AtRule, Plugin } from 'postcss' +import { normalizePath } from '../utils' + +const SINGLE_QUOTE = "'" +const DOUBLE_QUOTE = '"' + +export function fixRelativePaths(): Plugin { + // Retain a list of touched at-rules to avoid infinite loops + let touched: WeakSet = new WeakSet() + + function fixRelativePath(atRule: AtRule) { + if (touched.has(atRule)) return + + let rootPath = atRule.root().source?.input.file + if (!rootPath) return + + let inputFilePath = atRule.source?.input.file + if (!inputFilePath) return + + let value = atRule.params[0] + + let quote = + value[0] === DOUBLE_QUOTE && value[value.length - 1] === DOUBLE_QUOTE + ? DOUBLE_QUOTE + : value[0] === SINGLE_QUOTE && value[value.length - 1] === SINGLE_QUOTE + ? SINGLE_QUOTE + : null + + if (!quote) return + + let glob = atRule.params.slice(1, -1) + + // Handle eventual negative rules. We only support one level of negation. + let negativePrefix = '' + if (glob.startsWith('!')) { + glob = glob.slice(1) + negativePrefix = '!' + } + + // We only want to rewrite relative paths. + if (!glob.startsWith('./') && !glob.startsWith('../')) { + return + } + + let absoluteGlob = path.posix.join(normalizePath(path.dirname(inputFilePath)), glob) + let absoluteRootPosixPath = path.posix.dirname(normalizePath(rootPath)) + + let relative = path.posix.relative(absoluteRootPosixPath, absoluteGlob) + + // If the path points to a file in the same directory, `path.relative` will + // remove the leading `./` and we need to add it back in order to still + // consider the path relative + if (!relative.startsWith('.')) { + relative = './' + relative + } + + atRule.params = quote + negativePrefix + relative + quote + touched.add(atRule) + } + + return { + postcssPlugin: 'tailwindcss-postcss-fix-relative-paths', + AtRule: { + source: fixRelativePath, + plugin: fixRelativePath, + }, + } +} diff --git a/packages/tailwindcss-language-server/src/css/index.ts b/packages/tailwindcss-language-server/src/css/index.ts new file mode 100644 index 000000000..9028d9de1 --- /dev/null +++ b/packages/tailwindcss-language-server/src/css/index.ts @@ -0,0 +1,2 @@ +export * from './resolve-css-imports' +export * from './extract-source-directives' diff --git a/packages/tailwindcss-language-server/src/resolve-css-imports.ts b/packages/tailwindcss-language-server/src/css/resolve-css-imports.ts similarity index 77% rename from packages/tailwindcss-language-server/src/resolve-css-imports.ts rename to packages/tailwindcss-language-server/src/css/resolve-css-imports.ts index 2824c35aa..cf0e130a9 100644 --- a/packages/tailwindcss-language-server/src/resolve-css-imports.ts +++ b/packages/tailwindcss-language-server/src/css/resolve-css-imports.ts @@ -1,6 +1,7 @@ import postcss from 'postcss' import postcssImport from 'postcss-import' -import { createResolver } from './util/resolve' +import { createResolver } from '../util/resolve' +import { fixRelativePaths } from './fix-relative-paths' const resolver = createResolver({ extensions: ['.css'], @@ -15,6 +16,7 @@ const resolveImports = postcss([ return paths ? paths : id }, }), + fixRelativePaths(), ]) export function resolveCssImports() { diff --git a/packages/tailwindcss-language-server/src/language/cssServer.ts b/packages/tailwindcss-language-server/src/language/cssServer.ts index a9b854983..6c47a727b 100644 --- a/packages/tailwindcss-language-server/src/language/cssServer.ts +++ b/packages/tailwindcss-language-server/src/language/cssServer.ts @@ -381,7 +381,7 @@ async function validateTextDocument(textDocument: TextDocument): Promise { .filter((diagnostic) => { if ( diagnostic.code === 'unknownAtRules' && - /Unknown at rule @(tailwind|apply|config|theme)/.test(diagnostic.message) + /Unknown at rule @(tailwind|apply|config|theme|plugin|source)/.test(diagnostic.message) ) { return false } diff --git a/packages/tailwindcss-language-server/src/oxide.ts b/packages/tailwindcss-language-server/src/oxide.ts new file mode 100644 index 000000000..ba9f2b5d7 --- /dev/null +++ b/packages/tailwindcss-language-server/src/oxide.ts @@ -0,0 +1,143 @@ +import { lte } from 'tailwindcss-language-service/src/util/semver' + +// This covers the Oxide API from v4.0.0-alpha.1 to v4.0.0-alpha.18 +declare namespace OxideV1 { + interface GlobEntry { + base: string + glob: string + } + + interface ScanOptions { + base: string + globs?: boolean + } + + interface ScanResult { + files: Array + globs: Array + } +} + +// This covers the Oxide API from v4.0.0-alpha.19 +declare namespace OxideV2 { + interface GlobEntry { + base: string + pattern: string + } + + interface ScanOptions { + base: string + sources: Array + } + + interface ScanResult { + files: Array + globs: Array + } +} + +// This covers the Oxide API from v4.0.0-alpha.20+ +declare namespace OxideV3 { + interface GlobEntry { + base: string + pattern: string + } + + interface ScannerOptions { + detectSources?: { base: string } + sources: Array + } + + interface ScannerConstructor { + new (options: ScannerOptions): Scanner + } + + interface Scanner { + files: Array + globs: Array + } +} + +interface Oxide { + scanDir?(options: OxideV1.ScanOptions): OxideV1.ScanResult + scanDir?(options: OxideV2.ScanOptions): OxideV2.ScanResult + Scanner?: OxideV3.ScannerConstructor +} + +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 + + return oxide +} + +interface GlobEntry { + base: string + pattern: string +} + +interface ScanOptions { + oxidePath: string + oxideVersion: string + basePath: string + sources: Array +} + +interface ScanResult { + files: Array + globs: Array +} + +/** + * This is a helper function that leverages the Oxide API to scan a directory + * and a set of sources and turn them into files and globs. + * + * Because the Oxide API has changed over time this function presents a unified + * interface that works with all versions of the Oxide API but the results may + * be different depending on the version of Oxide that is being used. + * + * 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) + if (!oxide) return null + + // V1 + if (lte(options.oxideVersion, '4.0.0-alpha.18')) { + let result = oxide.scanDir?.({ + base: options.basePath, + globs: true, + }) + + return { + files: result.files, + globs: result.globs.map((g) => ({ base: g.base, pattern: g.glob })), + } + } + + // V2 + if (lte(options.oxideVersion, '4.0.0-alpha.19')) { + let result = oxide.scanDir({ + base: options.basePath, + sources: options.sources, + }) + + return { + files: result.files, + globs: result.globs, + } + } + + // V3 + let scanner = new oxide.Scanner({ + detectSources: { base: options.basePath }, + sources: options.sources, + }) + + return { + files: scanner.files, + globs: scanner.globs, + } +} diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts index 34bb73ec5..7db5f1b19 100644 --- a/packages/tailwindcss-language-server/src/project-locator.test.ts +++ b/packages/tailwindcss-language-server/src/project-locator.test.ts @@ -141,3 +141,29 @@ testFixture('v4/auto-content', [ ], }, ]) + +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}/admin/foo.bin', + '{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}', + ], + }, + { + 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}/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}', + ], + }, +]) diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts index 6a96a9671..50c3d426f 100644 --- a/packages/tailwindcss-language-server/src/project-locator.ts +++ b/packages/tailwindcss-language-server/src/project-locator.ts @@ -7,14 +7,16 @@ import type { Settings } from '@tailwindcss/language-service/src/util/state' import { CONFIG_GLOB, CSS_GLOB } from './lib/constants' import { readCssFile } from './util/css' import { Graph } from './graph' -import type { Message } from 'postcss' +import type { AtRule, Message } from 'postcss' import { type DocumentSelector, DocumentSelectorPriority } from './projects' import { CacheMap } from './cache-map' import { getPackageRoot } from './util/get-package-root' import resolveFrom from './util/resolveFrom' import { type Feature, supportedFeatures } from '@tailwindcss/language-service/src/features' -import { resolveCssImports } from './resolve-css-imports' +import { extractSourceDirectives, resolveCssImports } from './css' import { normalizeDriveLetter, normalizePath, pathToFileURL } from './utils' +import postcss from 'postcss' +import * as oxide from './oxide' export interface ProjectConfig { /** The folder that contains the project */ @@ -341,6 +343,9 @@ export class ProjectLocator { // Resolve real paths for all the files in the CSS import graph await Promise.all(imports.map((file) => file.resolveRealpaths())) + // Resolve all @source directives + await Promise.all(imports.map((file) => file.resolveSourceDirectives())) + // Create a graph of all the CSS files that might (indirectly) use Tailwind let graph = new Graph() @@ -500,7 +505,13 @@ async function* contentSelectorsFromCssConfig(entry: ConfigEntry): AsyncIterable } } else if (item.kind === 'auto' && !auto) { auto = true - for await (let pattern of detectContentFiles(entry.packageRoot)) { + + // Note: the file representing `entry` is not guaranteed to be the first + // file so we use `flatMap`` here to simplify the logic but none of the + // other entries should have sources. + let sources = entry.entries.flatMap((entry) => entry.sources) + + for await (let pattern of detectContentFiles(entry.packageRoot, entry.path, sources)) { yield { pattern, priority: DocumentSelectorPriority.CONTENT_FILE, @@ -510,25 +521,37 @@ async function* contentSelectorsFromCssConfig(entry: ConfigEntry): AsyncIterable } } -async function* detectContentFiles(base: string): AsyncIterable { +async function* detectContentFiles( + base: string, + inputFile: string, + sources: string[], +): AsyncIterable { try { let oxidePath = resolveFrom(path.dirname(base), '@tailwindcss/oxide') oxidePath = pathToFileURL(oxidePath).href - - const oxide: typeof import('@tailwindcss/oxide') = await import(oxidePath) + let oxidePackageJsonPath = resolveFrom(path.dirname(base), '@tailwindcss/oxide/package.json') + let oxidePackageJson = JSON.parse(await fs.readFile(oxidePackageJsonPath, 'utf8')) + + let result = await oxide.scan({ + oxidePath, + oxideVersion: oxidePackageJson.version, + basePath: base, + sources: sources.map((pattern) => ({ + base: path.dirname(inputFile), + pattern, + })), + }) // This isn't a v4 project - if (!oxide.scanDir) return - - let { files, globs } = oxide.scanDir({ base, globs: true }) + if (!result) return - for (let file of files) { + for (let file of result.files) { yield normalizePath(file) } - for (let { base, glob } of globs) { + for (let { base, pattern } of result.globs) { // Do not normalize the glob itself as it may contain escape sequences - yield normalizePath(base) + '/' + glob + yield normalizePath(base) + '/' + pattern } } catch { // @@ -553,6 +576,7 @@ class FileEntry { content: string | null deps: FileEntry[] = [] realpath: string | null + sources: string[] = [] constructor( public type: 'js' | 'css', @@ -579,7 +603,8 @@ class FileEntry { // Replace the file content with the processed CSS this.content = result.css } catch { - // + // TODO: Errors here should be surfaced in tests and possibly the user in + // `trace` logs or something like that } } @@ -589,6 +614,20 @@ class FileEntry { await Promise.all(this.deps.map((entry) => entry.resolveRealpaths())) } + async resolveSourceDirectives() { + if (this.sources.length > 0) { + return + } + + // Note: This should eventually use the DesignSystem to extract the same + // sources also discovered by tailwind. Since we don't have everything yet + // to initialize the design system though, we set up a simple postcss at + // rule exporter instead for now. + await postcss([extractSourceDirectives(this.sources)]).process(this.content, { + from: this.realpath, + }) + } + /** * Look for `@config` directives in a CSS file and return the path to the config * file that it points to. This path is (possibly) relative to the CSS file so 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 818902da0..07cc3766d 100644 --- a/packages/tailwindcss-language-server/src/util/v4/design-system.ts +++ b/packages/tailwindcss-language-server/src/util/v4/design-system.ts @@ -1,7 +1,7 @@ import type { DesignSystem } from '@tailwindcss/language-service/src/util/v4' import postcss from 'postcss' -import { resolveCssImports } from '../../resolve-css-imports' +import { resolveCssImports } from '../../css' const HAS_V4_IMPORT = /@import\s*(?:'tailwindcss'|"tailwindcss")/ const HAS_V4_THEME = /@theme\s*\{/ diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js index 32b283cd8..6f44f8b15 100644 --- a/packages/tailwindcss-language-server/tests/completions/completions.test.js +++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js @@ -310,8 +310,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(12376) - expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(220) + expect(result.items.length).toBe(12400) + expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(224) expect(result).toEqual({ isIncomplete: false, items: expect.arrayContaining([ 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 803850fa7..318dad5e1 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,37 +5,39 @@ "packages": { "": { "dependencies": { - "@tailwindcss/oxide": "^4.0.0-alpha.16", - "tailwindcss": "^4.0.0-alpha.16" + "@tailwindcss/oxide": "^4.0.0-alpha.19", + "tailwindcss": "^4.0.0-alpha.19" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.0-alpha.16.tgz", - "integrity": "sha512-sm/Y8dcTyM7WtNqGhpBkzcjWwhMqt46CN2VQ0KxAFH+FAz4BElnl/8eWaSd1ZKWosxDQCXB8d2Yy38h7Yqbw8g==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.0-alpha.19.tgz", + "integrity": "sha512-DdkrVz/MKPoe9v7W3c0+SEFKRDIPMSsxgN7gPC+xeTnTL4BGoT5b1EiVGFuXWEyLbDmWztuN6z75Yuze2BwvMQ==", + "license": "MIT", "engines": { "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.0.0-alpha.16", - "@tailwindcss/oxide-darwin-arm64": "4.0.0-alpha.16", - "@tailwindcss/oxide-darwin-x64": "4.0.0-alpha.16", - "@tailwindcss/oxide-freebsd-x64": "4.0.0-alpha.16", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.0-alpha.16", - "@tailwindcss/oxide-linux-arm64-gnu": "4.0.0-alpha.16", - "@tailwindcss/oxide-linux-arm64-musl": "4.0.0-alpha.16", - "@tailwindcss/oxide-linux-x64-gnu": "4.0.0-alpha.16", - "@tailwindcss/oxide-linux-x64-musl": "4.0.0-alpha.16", - "@tailwindcss/oxide-win32-x64-msvc": "4.0.0-alpha.16" + "@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" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.0-alpha.16.tgz", - "integrity": "sha512-duaTHvkAeUJQoqfA5XnYIp6F0PtqdcjXILuUF43wV0hC3NH2CECaxAgG2Ca5OVFAGqCI3fo29iqPMVcEEDlyjA==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.0-alpha.19.tgz", + "integrity": "sha512-NhpXem1j7g0uSGyLucmMj0VVQMeUrWc6kR/Ymnri3tpw2eaykgFYwLfdnI7jdJRxUxa/nNJip9yBJ3diZXl60w==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -45,12 +47,13 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.0-alpha.16.tgz", - "integrity": "sha512-4T5+35t5Qb0hZLLmclhVzsV5tmnjMwCqEySMnG8YLMB7YlATvmZG9TL8JqJLQjxqwjMDsl5tCddkui4FAxgLbA==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.0-alpha.19.tgz", + "integrity": "sha512-KCdalT+huX2cW9snNmPr+B66V91cSzIobBCXVgYCPCh0NZF4ueKu+X+kQN2gFxurDUm/D+aKW/0rQUIsUmFpdQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -60,12 +63,13 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.0-alpha.16.tgz", - "integrity": "sha512-dchop1QRdOcnh8hwI/w1HrUgE3ZAvvz8iCEv5akEA0zOglBsHd3hGA2u8zAt5PrDz/wBmdOpr+R5H2bYLw1MPw==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.0-alpha.19.tgz", + "integrity": "sha512-ETOWA08loUmOVTEa3zhRhY8HyqdGtR9DNhXdrRZBi67ZwCAmA+jg5B+mZaYeQJ6CjETx07BnhcGmmxGz3/6c8w==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -75,12 +79,13 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.0-alpha.16.tgz", - "integrity": "sha512-K5otxfNigxsY2fkgHI63Jjm+hvSI4gCFa2xGtsvTVUEHPUTOEo4n+aj9yIkNFgGpeIDii2nt3DtKYjhKyfUirw==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.0-alpha.19.tgz", + "integrity": "sha512-uB0rYLpqPnmyqtYSKHu1AtnHeerNcVH+de0sIufGCBDFGYNxmW8feCKNZwo6r7U/Fzg+AF9BOjwvdvd4yLfQ8g==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -90,12 +95,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.0-alpha.16.tgz", - "integrity": "sha512-AT8tYba/32q5FVLnJThcvYS8zmOBwLU5JzScaTY0Lc34WbGQ0+y6dtPlZoyyW+e+OBI8mDsTiD2BR3h0rdqb7g==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.0-alpha.19.tgz", + "integrity": "sha512-hIfm6DNh18rkz2PFRsQANINH0tpso6/vaU8p0Qw7rgYqqrxJTRpyLVsnvx3ahMOplJrDT6Z+Nfak8udnZN2C/Q==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -105,12 +111,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.0-alpha.16.tgz", - "integrity": "sha512-RcKr+fXs0kOT679UM2SEBRqGkXTP+jzk9+G96gwqa4OLgp6fiW1TSRB22V8j+Q10oWqfMHxsBSe9awM7F2ebuw==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.0-alpha.19.tgz", + "integrity": "sha512-n1Hr+8Hup2GLmeonQy9ydZxMBCs0FR1rcv4K7AHip+6PbD0se8k9LBIZac3OguFNj2hTehiadaiRb18rsVUw0g==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -120,12 +127,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.0-alpha.16.tgz", - "integrity": "sha512-lsUf21WkPufMVSOmj3EwoSGdb0KbCq0czMChkeIyLlt5WC/ZvH0ZMd9U5sfHQ7c1Q9usWfhz+Is9SbX7n2WvuA==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.0-alpha.19.tgz", + "integrity": "sha512-KhwthLZh9Js3t5URkuRURw45iU3rSh9vhuHRaV4KQT10ZFiXQMUlFfMKJyxRMcgC2fcL5vsiqwjOaMwp7Y8vsQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -135,12 +143,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.0-alpha.16.tgz", - "integrity": "sha512-D+uu2PCFb1fOuVWWS+xhtKVfbNbPmGYdMy4xwplOdHn8gacokUvDGsKdW/nogFoHtSws4+U6O4+mFjSQH3heSg==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.0-alpha.19.tgz", + "integrity": "sha512-qqEuULSiczyZkdWVzwkiiFyOYqx5RR2De75iwYREzXUuHRHval1ep2qO7tvZdgt37t2vgjoQwaPA6zO+JGUa+Q==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -150,12 +159,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.0-alpha.16.tgz", - "integrity": "sha512-zFoaEQvx9DhXO7LUNRlmUFm8N92LXs9n1YD/60MOYJqpVzPdqLBplk+Ltpw1NPE/Y2BZ7XvXyrBl11XH6Wj0/A==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.0-alpha.19.tgz", + "integrity": "sha512-JKTYCiNz83sYl2FgKJk3dL11FS4dAj7Rgmuz3TVANmeMYTBtwmFPthoH0qAmF+hjPJgT5Ne7lSwplfuHJAD3MQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -165,12 +175,13 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.0-alpha.16.tgz", - "integrity": "sha512-ClCzUFuD6xptvcksYtoLJekUdSN9TVoSrr66eNVAErtA+vKKTThOyliEz/pZfe7lHsI93sDR22HMtu/zP0prJA==", + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.0-alpha.19.tgz", + "integrity": "sha512-D43tji14+i/GhJn5YZX8c2FXFmAqlI6DDGX8caUM35dga/uT+sJUfLJ84YigJyvAdSF1gBVdm7MvhYRcVYHOwg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -180,9 +191,10 @@ } }, "node_modules/tailwindcss": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-alpha.16.tgz", - "integrity": "sha512-h6UIkQEpOJZy0N8tXeWgIhsEYPfUyqST9Oidr46+1W78p8S9hjJDfnW08/bKW17NA9/ro8sZvFHT98LtwwxtSQ==" + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-alpha.19.tgz", + "integrity": "sha512-4k8i6ml9ZQaj4xmdTHA2ZVp4YGsA+9+8VsIj/VkwyD38J5jX3L3NSi7TnPvlYT+WXcqAJF7EX8x76yCTfi2CyA==", + "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 874817d09..50f951e76 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-alpha.16", - "@tailwindcss/oxide": "^4.0.0-alpha.16" + "tailwindcss": "^4.0.0-alpha.19", + "@tailwindcss/oxide": "^4.0.0-alpha.19" } } 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 4e476083b..cf1b17a6d 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,14 @@ "packages": { "": { "dependencies": { - "tailwindcss": "^4.0.0-alpha.16" + "tailwindcss": "^4.0.0-alpha.19" } }, "node_modules/tailwindcss": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-alpha.16.tgz", - "integrity": "sha512-h6UIkQEpOJZy0N8tXeWgIhsEYPfUyqST9Oidr46+1W78p8S9hjJDfnW08/bKW17NA9/ro8sZvFHT98LtwwxtSQ==" + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-alpha.19.tgz", + "integrity": "sha512-4k8i6ml9ZQaj4xmdTHA2ZVp4YGsA+9+8VsIj/VkwyD38J5jX3L3NSi7TnPvlYT+WXcqAJF7EX8x76yCTfi2CyA==", + "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 996efe041..efa461d1b 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-alpha.16" + "tailwindcss": "^4.0.0-alpha.19" } } 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 new file mode 100644 index 000000000..41dcb5f80 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/app.css @@ -0,0 +1,2 @@ +@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 new file mode 100644 index 000000000..ec56c4341 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/foo.bin @@ -0,0 +1 @@ +

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 new file mode 100644 index 000000000..9c0a79199 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/tw.css @@ -0,0 +1,2 @@ +@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 new file mode 100644 index 000000000..24ea64d28 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/admin/ui.css @@ -0,0 +1,3 @@ +@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 new file mode 100644 index 000000000..2bc339890 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package-lock.json @@ -0,0 +1,16 @@ +{ + "name": "custom-source", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "tailwindcss": "^4.0.0-alpha.19" + } + }, + "node_modules/tailwindcss": { + "version": "4.0.0-alpha.19", + "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 new file mode 100644 index 000000000..efa461d1b --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "tailwindcss": "^4.0.0-alpha.19" + } +} 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 new file mode 100644 index 000000000..49b293bf4 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/shared.html @@ -0,0 +1 @@ +

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 new file mode 100644 index 000000000..9357eb024 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/web/app.css @@ -0,0 +1,2 @@ +@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 new file mode 100644 index 000000000..56ee61a2a --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/v4/custom-source/web/bar.bin @@ -0,0 +1 @@ +

Web

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 697a00965..b64395e49 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,14 @@ "packages": { "": { "dependencies": { - "tailwindcss": "^4.0.0-alpha.16" + "tailwindcss": "^4.0.0-alpha.19" } }, "node_modules/tailwindcss": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-alpha.16.tgz", - "integrity": "sha512-h6UIkQEpOJZy0N8tXeWgIhsEYPfUyqST9Oidr46+1W78p8S9hjJDfnW08/bKW17NA9/ro8sZvFHT98LtwwxtSQ==" + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-alpha.19.tgz", + "integrity": "sha512-4k8i6ml9ZQaj4xmdTHA2ZVp4YGsA+9+8VsIj/VkwyD38J5jX3L3NSi7TnPvlYT+WXcqAJF7EX8x76yCTfi2CyA==", + "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 996efe041..efa461d1b 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-alpha.16" + "tailwindcss": "^4.0.0-alpha.19" } } 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 1dd6e822d..df3317ef2 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-alpha.16" + "tailwindcss": "^4.0.0-alpha.19" } }, "node_modules/@private/admin": { @@ -32,9 +32,10 @@ "link": true }, "node_modules/tailwindcss": { - "version": "4.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-alpha.16.tgz", - "integrity": "sha512-h6UIkQEpOJZy0N8tXeWgIhsEYPfUyqST9Oidr46+1W78p8S9hjJDfnW08/bKW17NA9/ro8sZvFHT98LtwwxtSQ==" + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-alpha.19.tgz", + "integrity": "sha512-4k8i6ml9ZQaj4xmdTHA2ZVp4YGsA+9+8VsIj/VkwyD38J5jX3L3NSi7TnPvlYT+WXcqAJF7EX8x76yCTfi2CyA==", + "license": "MIT" }, "packages/admin": { "name": "@private/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 3f0204c30..700667f29 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-alpha.16" + "tailwindcss": "^4.0.0-alpha.19" } } diff --git a/packages/tailwindcss-language-service/src/util/doc.ts b/packages/tailwindcss-language-service/src/util/doc.ts index 4975ec463..770612d70 100644 --- a/packages/tailwindcss-language-service/src/util/doc.ts +++ b/packages/tailwindcss-language-service/src/util/doc.ts @@ -1,6 +1,7 @@ import type { Range } from 'vscode-languageserver' import type { TextDocument } from 'vscode-languageserver-textdocument' import moo from 'moo' +import { spliceChangesIntoString, StringChange } from './splice-changes-into-string' export function getTextWithoutComments( doc: TextDocument, @@ -8,6 +9,7 @@ export function getTextWithoutComments( range?: Range, ): string export function getTextWithoutComments(text: string, type: 'html' | 'js' | 'jsx' | 'css'): string + export function getTextWithoutComments( docOrText: TextDocument | string, type: 'html' | 'js' | 'jsx' | 'css', @@ -20,12 +22,78 @@ export function getTextWithoutComments( } if (type === 'css') { - return text.replace(/\/\*.*?\*\//gs, replace) + return getCssWithoutComments(text) } return text.replace(//gs, replace) } +function getCssWithoutComments(input: string) { + const DOUBLE_QUOTE = 0x22 // " + const SINGLE_QUOTE = 0x27 // ' + const BACKSLASH = 0x5c // \ + const SLASH = 0x2f // / + const ASTERISK = 0x2a // * + const LINE_BREAK = 0x0a // \n + + let changes: StringChange[] = [] + + // Collect ranges for every comment in the input. + for (let i = 0; i < input.length; ++i) { + let currentChar = input.charCodeAt(i) + + if (currentChar === BACKSLASH) { + i += 1 + } + + // Skip over strings — they are to be left untouched + else if (currentChar === SINGLE_QUOTE || currentChar === DOUBLE_QUOTE) { + for (let j = i + 1; j < input.length; ++j) { + let peekChar = input.charCodeAt(j) + + // Current character is a `\` therefore the next character is escaped. + if (peekChar === BACKSLASH) { + j += 1 + } + + // End of the string. + else if (peekChar === currentChar) { + i = j + break + } else if (peekChar === LINE_BREAK) { + i = j + break + } + } + } else if (currentChar === SLASH && input.charCodeAt(i + 1) === ASTERISK) { + let start = i + + for (let j = i + 2; j < input.length; j++) { + let peekChar = input.charCodeAt(j) + + // Current character is a `\` therefore the next character is escaped. + if (peekChar === BACKSLASH) { + j += 1 + } + + // End of the comment + else if (peekChar === ASTERISK && input.charCodeAt(j + 1) === SLASH) { + i = j + 1 + break + } + } + + changes.push({ + start, + end: i + 1, + replacement: replace(input.slice(start, i + 1)), + }) + } + } + + return spliceChangesIntoString(input, changes) +} + function replace(match: string): string { return match.replace(/./gs, (char) => (char === '\n' ? '\n' : ' ')) } 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 new file mode 100644 index 000000000..e1e6f1685 --- /dev/null +++ b/packages/tailwindcss-language-service/src/util/splice-changes-into-string.ts @@ -0,0 +1,42 @@ +export interface StringChange { + start: number + end: number + replacement: string +} + +/** + * 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[]) { + // If there are no changes, return the original string + if (!changes[0]) return str + + // Sort all changes in order to make it easier to apply them + changes.sort((a, b) => { + return a.end - b.end || a.start - b.start + }) + + // Append original string between each chunk, and then the chunk itself + // This is sort of a String Builder pattern, thus creating less memory pressure + let result = '' + + let previous = changes[0] + + result += str.slice(0, previous.start) + result += previous.replacement + + for (let i = 1; i < changes.length; ++i) { + let change = changes[i] + + result += str.slice(previous.end, change.start) + result += change.replacement + + previous = change + } + + // Add leftover string from last chunk to end + result += str.slice(previous.end) + + return result +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d746a236c..851a1c973 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.4) '@tailwindcss/oxide': - specifier: ^4.0.0-alpha.16 - version: 4.0.0-alpha.18 + 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) @@ -840,68 +840,68 @@ packages: peerDependencies: tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' - '@tailwindcss/oxide-android-arm64@4.0.0-alpha.18': - resolution: {integrity: sha512-fAdyr3GC5CDEdiVs/wg6aZFr0BD/RZxlEAjzGUL9mFyqyX5HsGbu0rVwyJvDaFjKMw5nB6hjfyaZMpuwHfQr8w==} + '@tailwindcss/oxide-android-arm64@4.0.0-alpha.19': + resolution: {integrity: sha512-NhpXem1j7g0uSGyLucmMj0VVQMeUrWc6kR/Ymnri3tpw2eaykgFYwLfdnI7jdJRxUxa/nNJip9yBJ3diZXl60w==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.0.0-alpha.18': - resolution: {integrity: sha512-K3YoYtAQNyaLXJ8q6f2ib4K6hm1+MT2ajh59C8rLOjfBp0jL4Whd8k9DuL8ctjlVoRvRT9ggQO90fKqx5g+6HA==} + '@tailwindcss/oxide-darwin-arm64@4.0.0-alpha.19': + resolution: {integrity: sha512-KCdalT+huX2cW9snNmPr+B66V91cSzIobBCXVgYCPCh0NZF4ueKu+X+kQN2gFxurDUm/D+aKW/0rQUIsUmFpdQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.0.0-alpha.18': - resolution: {integrity: sha512-WYIK59UaKWhh1LVV6E3VdfPjrZZl/d1q1B3pBVXSoxOWU2fX1OP1f6pqs78/cADAK3FDJ24MByn2m/rDyfFXvg==} + '@tailwindcss/oxide-darwin-x64@4.0.0-alpha.19': + resolution: {integrity: sha512-ETOWA08loUmOVTEa3zhRhY8HyqdGtR9DNhXdrRZBi67ZwCAmA+jg5B+mZaYeQJ6CjETx07BnhcGmmxGz3/6c8w==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.0.0-alpha.18': - resolution: {integrity: sha512-awG9qCHfVEa7yyEnFziDf0jkbJ/zB7n8CYv6jnDt43nfrdiytTmXOD7+QUt1PEY+mKw5mOJR4yWH1rMVnWIV5w==} + '@tailwindcss/oxide-freebsd-x64@4.0.0-alpha.19': + resolution: {integrity: sha512-uB0rYLpqPnmyqtYSKHu1AtnHeerNcVH+de0sIufGCBDFGYNxmW8feCKNZwo6r7U/Fzg+AF9BOjwvdvd4yLfQ8g==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-alpha.18': - resolution: {integrity: sha512-4aY4B9Ips0KuvCF9vDLKE99APi7GxyueQPzKWkHE1T9CJVFk1JpsPF1hnSzaZewxloM18yLXBFCl7SImoaGh0A==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-alpha.19': + resolution: {integrity: sha512-hIfm6DNh18rkz2PFRsQANINH0tpso6/vaU8p0Qw7rgYqqrxJTRpyLVsnvx3ahMOplJrDT6Z+Nfak8udnZN2C/Q==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-alpha.18': - resolution: {integrity: sha512-Ky9gcVhJZQBaRLL5w35dE+fHoHXVn9PJy7iNAqgauh+kkPeU7L/EoWu70XMBVaxu7jw8J7KLAUgLR9E6Atwldg==} + '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-alpha.19': + resolution: {integrity: sha512-n1Hr+8Hup2GLmeonQy9ydZxMBCs0FR1rcv4K7AHip+6PbD0se8k9LBIZac3OguFNj2hTehiadaiRb18rsVUw0g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.0.0-alpha.18': - resolution: {integrity: sha512-dcq/3wleXfqc9+QAe1eZO00yJEGJRhwA6U80p3C1cgVSg3sZOeKwLPi/69MbkXN1I+VoKqLbYgtv/GzzLY9g2A==} + '@tailwindcss/oxide-linux-arm64-musl@4.0.0-alpha.19': + resolution: {integrity: sha512-KhwthLZh9Js3t5URkuRURw45iU3rSh9vhuHRaV4KQT10ZFiXQMUlFfMKJyxRMcgC2fcL5vsiqwjOaMwp7Y8vsQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.0.0-alpha.18': - resolution: {integrity: sha512-R0NeALYR7vhGNvY5PJpmwPdAfp5X2CyYo8FqR2fMgqVyKHpGhJsj+BR2Hg6AcSFg7t4DA1T+fOOcGUupK9cnkQ==} + '@tailwindcss/oxide-linux-x64-gnu@4.0.0-alpha.19': + resolution: {integrity: sha512-qqEuULSiczyZkdWVzwkiiFyOYqx5RR2De75iwYREzXUuHRHval1ep2qO7tvZdgt37t2vgjoQwaPA6zO+JGUa+Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.0.0-alpha.18': - resolution: {integrity: sha512-dY/j372Jc+MwGoyHnFW8eGBW6Gab1lU1cMIFm+4TcA63HuYAJfnzt+KU1Jai/5huYj+cy6qrHnWAdO9V3yWpdQ==} + '@tailwindcss/oxide-linux-x64-musl@4.0.0-alpha.19': + resolution: {integrity: sha512-JKTYCiNz83sYl2FgKJk3dL11FS4dAj7Rgmuz3TVANmeMYTBtwmFPthoH0qAmF+hjPJgT5Ne7lSwplfuHJAD3MQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-win32-x64-msvc@4.0.0-alpha.18': - resolution: {integrity: sha512-B7eSqgxLfJYZCOX+PZZRWJB5pKTRAVWDRzlMy86cdOIT12TRL4OPT4KQuWnrvfNKVKgcQv6eZhG6CZS6pLi20Q==} + '@tailwindcss/oxide-win32-x64-msvc@4.0.0-alpha.19': + resolution: {integrity: sha512-D43tji14+i/GhJn5YZX8c2FXFmAqlI6DDGX8caUM35dga/uT+sJUfLJ84YigJyvAdSF1gBVdm7MvhYRcVYHOwg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.0.0-alpha.18': - resolution: {integrity: sha512-Pc4c8khaklIqLWWSVqbVX8P3imjmM3SChJaSPkajbzlQRx17iwqZWVQJIhwP/rZh1aGeJDOVb0kduVlw/J5bIA==} + '@tailwindcss/oxide@4.0.0-alpha.19': + resolution: {integrity: sha512-DdkrVz/MKPoe9v7W3c0+SEFKRDIPMSsxgN7gPC+xeTnTL4BGoT5b1EiVGFuXWEyLbDmWztuN6z75Yuze2BwvMQ==} engines: {node: '>= 10'} '@tailwindcss/typography@0.5.7': @@ -3056,48 +3056,48 @@ snapshots: dependencies: tailwindcss: 3.4.4 - '@tailwindcss/oxide-android-arm64@4.0.0-alpha.18': + '@tailwindcss/oxide-android-arm64@4.0.0-alpha.19': optional: true - '@tailwindcss/oxide-darwin-arm64@4.0.0-alpha.18': + '@tailwindcss/oxide-darwin-arm64@4.0.0-alpha.19': optional: true - '@tailwindcss/oxide-darwin-x64@4.0.0-alpha.18': + '@tailwindcss/oxide-darwin-x64@4.0.0-alpha.19': optional: true - '@tailwindcss/oxide-freebsd-x64@4.0.0-alpha.18': + '@tailwindcss/oxide-freebsd-x64@4.0.0-alpha.19': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-alpha.18': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-alpha.19': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-alpha.18': + '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-alpha.19': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.0.0-alpha.18': + '@tailwindcss/oxide-linux-arm64-musl@4.0.0-alpha.19': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.0.0-alpha.18': + '@tailwindcss/oxide-linux-x64-gnu@4.0.0-alpha.19': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.0.0-alpha.18': + '@tailwindcss/oxide-linux-x64-musl@4.0.0-alpha.19': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.0.0-alpha.18': + '@tailwindcss/oxide-win32-x64-msvc@4.0.0-alpha.19': optional: true - '@tailwindcss/oxide@4.0.0-alpha.18': + '@tailwindcss/oxide@4.0.0-alpha.19': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.0.0-alpha.18 - '@tailwindcss/oxide-darwin-arm64': 4.0.0-alpha.18 - '@tailwindcss/oxide-darwin-x64': 4.0.0-alpha.18 - '@tailwindcss/oxide-freebsd-x64': 4.0.0-alpha.18 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.0-alpha.18 - '@tailwindcss/oxide-linux-arm64-gnu': 4.0.0-alpha.18 - '@tailwindcss/oxide-linux-arm64-musl': 4.0.0-alpha.18 - '@tailwindcss/oxide-linux-x64-gnu': 4.0.0-alpha.18 - '@tailwindcss/oxide-linux-x64-musl': 4.0.0-alpha.18 - '@tailwindcss/oxide-win32-x64-msvc': 4.0.0-alpha.18 + '@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/typography@0.5.7(tailwindcss@3.4.4)': dependencies: