From 1224eb20c67a8557282c7de1b79ab00b01ce592f Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 27 May 2025 10:05:20 -0400 Subject: [PATCH 1/4] Cleanup --- .../src/completionProvider.ts | 2 +- .../src/util/getLanguageBoundaries.ts | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts index 36e7676b..570ca535 100644 --- a/packages/tailwindcss-language-service/src/completionProvider.ts +++ b/packages/tailwindcss-language-service/src/completionProvider.ts @@ -13,7 +13,7 @@ import type { TextDocument } from 'vscode-languageserver-textdocument' import dlv from 'dlv' import removeMeta from './util/removeMeta' import { formatColor, getColor, getColorFromValue } from './util/color' -import { isHtmlContext, isHtmlDoc, isVueDoc } from './util/html' +import { isHtmlContext, isHtmlDoc } from './util/html' import { isCssContext } from './util/css' import { findLast, matchClassAttributes, matchClassFunctions } from './util/find' import { stringifyConfigValue, stringifyCss } from './util/stringify' diff --git a/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts b/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts index 786cc2f7..f323317c 100644 --- a/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts +++ b/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts @@ -157,13 +157,15 @@ export function getLanguageBoundaries( let isJs = isJsDoc(state, doc) - let defaultType = isVueDoc(doc) - ? 'none' - : isHtmlDoc(state, doc) || isSvelteDoc(doc) - ? 'html' - : isJs - ? 'jsx' - : null + let defaultType: string | null = null + + if (isVueDoc(doc)) { + defaultType = 'none' + } else if (isHtmlDoc(state, doc) || isSvelteDoc(doc)) { + defaultType = 'html' + } else if (isJs) { + defaultType = 'jsx' + } if (defaultType === null) { cache.set(cacheKey, null) @@ -172,7 +174,14 @@ export function getLanguageBoundaries( text = getTextWithoutComments(text, isJs ? 'js' : 'html') - let lexer = defaultType === 'none' ? vueLexer : defaultLexer + let lexer = defaultLexer + + if (defaultType === 'none') { + if (isVueDoc(doc)) { + lexer = vueLexer + } + } + lexer.reset(text) let type = defaultType From d895575b9e0f99e7ab8d5d26c24e2665ec04425a Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 16 Jun 2025 17:02:25 -0400 Subject: [PATCH 2/4] Treat astro code fences as JS --- .../src/util/find.test.ts | 39 ++++++++++- .../src/util/getLanguageBoundaries.ts | 26 +++++++- .../src/util/html.ts | 4 ++ .../src/util/language-boundaries.test.ts | 66 ++++++++++++++++++- .../src/util/test-utils.ts | 1 + 5 files changed, 132 insertions(+), 4 deletions(-) diff --git a/packages/tailwindcss-language-service/src/util/find.test.ts b/packages/tailwindcss-language-service/src/util/find.test.ts index 9c65f23d..52fea620 100644 --- a/packages/tailwindcss-language-service/src/util/find.test.ts +++ b/packages/tailwindcss-language-service/src/util/find.test.ts @@ -4,7 +4,7 @@ import { findClassNameAtPosition, findHelperFunctionsInDocument, } from './find' -import { js, html, pug, createDocument, css } from './test-utils' +import { js, html, pug, createDocument, css, astro } from './test-utils' import type { Range } from 'vscode-languageserver-textdocument' const range = (startLine: number, startCol: number, endLine: number, endCol: number): Range => ({ @@ -1044,3 +1044,40 @@ test('Can find helper functions in CSS', async ({ expect }) => { }, ]) }) + +test('class functions work inside astro code fences', async ({ expect }) => { + let file = createDocument({ + name: 'file.astro', + lang: 'astro', + settings: { + tailwindCSS: { + classFunctions: ['clsx'], + }, + }, + content: astro` + --- + let x = clsx("relative flex bg-red-500") + --- +
+ `, + }) + + let classLists = await findClassListsInHtmlRange(file.state, file.doc, 'html') + + expect(classLists).toEqual([ + { + classList: 'relative flex bg-red-500', + range: { + start: { line: 3, character: 12 }, + end: { line: 3, character: 36 }, + }, + }, + { + classList: 'relative flex bg-red-500', + range: { + start: { line: 1, character: 14 }, + end: { line: 1, character: 38 }, + }, + }, + ]) +}) diff --git a/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts b/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts index f323317c..d758c5b3 100644 --- a/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts +++ b/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts @@ -1,6 +1,6 @@ import type { Range } from 'vscode-languageserver' import type { TextDocument } from 'vscode-languageserver-textdocument' -import { isVueDoc, isHtmlDoc, isSvelteDoc } from './html' +import { isVueDoc, isHtmlDoc, isSvelteDoc, isAstroDoc } from './html' import type { State } from './state' import { indexToPosition } from './find' import { isJsDoc } from './js' @@ -134,8 +134,21 @@ let vueStates = { }, } +let astroStates = { + ...states, + main: { + frontmatterBlockStart: { match: /^[\s\n]*---/, push: 'frontmatterScript' }, + ...states.main, + }, + frontmatterScript: { + frontmatterBlockEnd: { match: /\s*---(?=\s)/, pop: 1 }, + ...text, + }, +} + let defaultLexer = moo.states(states) let vueLexer = moo.states(vueStates) +let astroLexer = moo.states(astroStates) let cache = new Cache({ max: 25, maxAge: 1000 }) @@ -161,7 +174,7 @@ export function getLanguageBoundaries( if (isVueDoc(doc)) { defaultType = 'none' - } else if (isHtmlDoc(state, doc) || isSvelteDoc(doc)) { + } else if (isHtmlDoc(state, doc) || isSvelteDoc(doc) || isAstroDoc(doc)) { defaultType = 'html' } else if (isJs) { defaultType = 'jsx' @@ -180,6 +193,10 @@ export function getLanguageBoundaries( if (isVueDoc(doc)) { lexer = vueLexer } + } else if (defaultType === 'html') { + if (isAstroDoc(doc)) { + lexer = astroLexer + } } lexer.reset(text) @@ -199,6 +216,11 @@ export function getLanguageBoundaries( boundaries[boundaries.length - 1].range.end = position } type = token.type.replace(/BlockStart$/, '') + + if (lexer === astroLexer && type === 'frontmatter') { + type = 'js' + } + boundaries.push({ type, range: { start: position, end: undefined } }) } else if (token.type.endsWith('BlockEnd')) { let position = indexToPosition(text, offset) diff --git a/packages/tailwindcss-language-service/src/util/html.ts b/packages/tailwindcss-language-service/src/util/html.ts index 666869bc..9464c452 100644 --- a/packages/tailwindcss-language-service/src/util/html.ts +++ b/packages/tailwindcss-language-service/src/util/html.ts @@ -20,6 +20,10 @@ export function isSvelteDoc(doc: TextDocument): boolean { return doc.languageId === 'svelte' } +export function isAstroDoc(doc: TextDocument): boolean { + return doc.languageId === 'astro' +} + export function isHtmlContext(state: State, doc: TextDocument, position: Position): boolean { let str = doc.getText({ start: { line: 0, character: 0 }, diff --git a/packages/tailwindcss-language-service/src/util/language-boundaries.test.ts b/packages/tailwindcss-language-service/src/util/language-boundaries.test.ts index 483a4ead..04ebeead 100644 --- a/packages/tailwindcss-language-service/src/util/language-boundaries.test.ts +++ b/packages/tailwindcss-language-service/src/util/language-boundaries.test.ts @@ -1,6 +1,6 @@ import { test } from 'vitest' import { getLanguageBoundaries } from './getLanguageBoundaries' -import { jsx, createDocument, html } from './test-utils' +import { jsx, createDocument, html, astro } from './test-utils' test('regex literals are ignored when determining language boundaries', ({ expect }) => { let file = createDocument({ @@ -189,3 +189,67 @@ test('Vue files detect