diff --git a/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts b/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts index 3dbfb20..3730492 100644 --- a/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts +++ b/packages/tailwindcss-language-service/src/codeActions/provideInvalidApplyCodeActions.ts @@ -17,6 +17,7 @@ import { cssObjToAst } from '../util/cssObjToAst' import { dset } from 'dset' import selectorParser from 'postcss-selector-parser' import { flatten } from '../util/array' +import { getTextWithoutComments } from '../util/doc' export async function provideInvalidApplyCodeActions( state: State, @@ -24,7 +25,7 @@ export async function provideInvalidApplyCodeActions( diagnostic: InvalidApplyDiagnostic ): Promise { let document = state.editor.documents.get(params.textDocument.uri) - let documentText = document.getText() + let documentText = getTextWithoutComments(document, 'css') let cssRange: Range let cssText = documentText const { postcss } = state.modules @@ -47,7 +48,7 @@ export async function provideInvalidApplyCodeActions( .filter((b) => b.type === 'css') .find(({ range }) => isWithinRange(diagnostic.range.start, range))?.range if (!cssRange) return [] - cssText = document.getText(cssRange) + cssText = getTextWithoutComments(document, 'css', cssRange) } try { diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts index 9eac0b2..716ce2c 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts @@ -10,6 +10,7 @@ import { closest } from '../util/closest' import { absoluteRange } from '../util/absoluteRange' import { combinations } from '../util/combinations' import dlv from 'dlv' +import { getTextWithoutComments } from '../util/doc' function pathToString(path: string | string[]): string { if (typeof path === 'string') return path @@ -177,7 +178,7 @@ export function getInvalidConfigPathDiagnostics( } ranges.forEach((range) => { - let text = document.getText(range) + let text = getTextWithoutComments(document, 'css', range) let matches = findAll( /(?\s|^)(?config|theme)\((?['"])(?[^)]+)\k[^)]*\)/g, text diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidScreenDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidScreenDiagnostics.ts index 587f1b1..c9a9a75 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidScreenDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidScreenDiagnostics.ts @@ -7,6 +7,7 @@ import { findAll, indexToPosition } from '../util/find' import { closest } from '../util/closest' import { absoluteRange } from '../util/absoluteRange' import dlv from 'dlv' +import { getTextWithoutComments } from '../util/doc' export function getInvalidScreenDiagnostics( state: State, @@ -28,7 +29,7 @@ export function getInvalidScreenDiagnostics( } ranges.forEach((range) => { - let text = document.getText(range) + let text = getTextWithoutComments(document, 'css', range) let matches = findAll(/(?:\s|^)@screen\s+(?[^\s{]+)/g, text) matches.forEach((match) => { diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts index d6a9c50..59f6de7 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts @@ -7,6 +7,7 @@ import { findAll, indexToPosition } from '../util/find' import * as semver from '../util/semver' import { closest } from '../util/closest' import { absoluteRange } from '../util/absoluteRange' +import { getTextWithoutComments } from '../util/doc' export function getInvalidTailwindDirectiveDiagnostics( state: State, @@ -42,7 +43,7 @@ export function getInvalidTailwindDirectiveDiagnostics( let hasVariantsDirective = state.jit && semver.gte(state.version, '2.1.99') ranges.forEach((range) => { - let text = document.getText(range) + let text = getTextWithoutComments(document, 'css', range) let matches = findAll(regex, text) let valid = [ diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts index 1ede7ec..1b6c997 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidVariantDiagnostics.ts @@ -7,6 +7,7 @@ import { findAll, indexToPosition } from '../util/find' import { closest } from '../util/closest' import { absoluteRange } from '../util/absoluteRange' import * as semver from '../util/semver' +import { getTextWithoutComments } from '../util/doc' export function getInvalidVariantDiagnostics( state: State, @@ -38,7 +39,7 @@ export function getInvalidVariantDiagnostics( } ranges.forEach((range) => { - let text = document.getText(range) + let text = getTextWithoutComments(document, 'css', range) let matches = findAll(/(?:\s|^)@variants\s+(?[^{]+)/g, text) matches.forEach((match) => { diff --git a/packages/tailwindcss-language-service/src/hoverProvider.ts b/packages/tailwindcss-language-service/src/hoverProvider.ts index 836d687..5ea1fcb 100644 --- a/packages/tailwindcss-language-service/src/hoverProvider.ts +++ b/packages/tailwindcss-language-service/src/hoverProvider.ts @@ -8,6 +8,7 @@ import { validateApply } from './util/validateApply' import { getClassNameParts } from './util/getClassNameAtPosition' import * as jit from './util/jit' import { validateConfigPath } from './diagnostics/getInvalidConfigPathDiagnostics' +import { getTextWithoutComments } from './util/doc' export async function doHover( state: State, @@ -23,10 +24,7 @@ export async function doHover( function provideCssHelperHover(state: State, document: TextDocument, position: Position): Hover { if (!isCssContext(state, document, position)) return null - const line = document.getText({ - start: { line: position.line, character: 0 }, - end: { line: position.line + 1, character: 0 }, - }) + const line = getTextWithoutComments(document, 'css').split('\n')[position.line] const match = line.match(/(?theme|config)\((?['"])(?[^)]+)\k[^)]*\)/) diff --git a/packages/tailwindcss-language-service/src/util/doc.ts b/packages/tailwindcss-language-service/src/util/doc.ts new file mode 100644 index 0000000..810f79d --- /dev/null +++ b/packages/tailwindcss-language-service/src/util/doc.ts @@ -0,0 +1,29 @@ +import type { TextDocument, Range } from 'vscode-languageserver' + +export function getTextWithoutComments( + doc: TextDocument, + type: 'html' | 'js' | 'jsx' | 'css', + 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', + range?: Range +): string { + let text = typeof docOrText === 'string' ? docOrText : docOrText.getText(range) + + if (type === 'js' || type === 'jsx') { + return text.replace(/\/\*.*?\*\//gs, replace).replace(/\/\/.*?$/gms, replace) + } + + if (type === 'css') { + return text.replace(/\/\*.*?\*\//gs, replace) + } + + return text.replace(//gs, replace) +} + +function replace(match: string): string { + return match.replace(/./gs, (char) => (char === '\n' ? '\n' : ' ')) +} diff --git a/packages/tailwindcss-language-service/src/util/find.ts b/packages/tailwindcss-language-service/src/util/find.ts index d30ac49..5b70d7a 100644 --- a/packages/tailwindcss-language-service/src/util/find.ts +++ b/packages/tailwindcss-language-service/src/util/find.ts @@ -12,6 +12,7 @@ import { resolveRange } from './resolveRange' import dlv from 'dlv' import { rangesEqual } from './rangesEqual' import Regex from 'becke-ch--regex--s0-0-v1--base--pl--lib' +import { getTextWithoutComments } from './doc' export function findAll(re: RegExp, str: string): RegExpMatchArray[] { let match: RegExpMatchArray @@ -74,7 +75,7 @@ export async function findClassNamesInRange( state: State, doc: TextDocument, range?: Range, - mode?: 'html' | 'css', + mode?: 'html' | 'css' | 'jsx', includeCustom: boolean = true ): Promise { const classLists = await findClassListsInRange(state, doc, range, mode, includeCustom) @@ -90,7 +91,7 @@ export async function findClassNamesInDocument( } export function findClassListsInCssRange(doc: TextDocument, range?: Range): DocumentClassList[] { - const text = doc.getText(range) + const text = getTextWithoutComments(doc, 'css', range) const matches = findAll( /(@apply\s+)(?[^;}]+?)(?\s*!important)?\s*[;}]/g, text @@ -184,9 +185,10 @@ export function matchClassAttributes(text: string, attributes: string[]): RegExp export async function findClassListsInHtmlRange( state: State, doc: TextDocument, + type: 'html' | 'js' | 'jsx', range?: Range ): Promise { - const text = doc.getText(range) + const text = getTextWithoutComments(doc, type, range) const matches = matchClassAttributes( text, @@ -291,14 +293,14 @@ export async function findClassListsInRange( state: State, doc: TextDocument, range?: Range, - mode?: 'html' | 'css', + mode?: 'html' | 'css' | 'jsx', includeCustom: boolean = true ): Promise { let classLists: DocumentClassList[] if (mode === 'css') { classLists = findClassListsInCssRange(doc, range) } else { - classLists = await findClassListsInHtmlRange(state, doc, range) + classLists = await findClassListsInHtmlRange(state, doc, mode, range) } return dedupeClassLists([ ...classLists, @@ -322,7 +324,9 @@ export async function findClassListsInDocument( ...(await Promise.all( boundaries .filter((b) => b.type === 'html' || b.type === 'jsx') - .map(({ range }) => findClassListsInHtmlRange(state, doc, range)) + .map(({ type, range }) => + findClassListsInHtmlRange(state, doc, type === 'html' ? 'html' : 'jsx', range) + ) )), ...boundaries .filter((b) => b.type === 'css') @@ -354,7 +358,7 @@ export function findHelperFunctionsInRange( doc: TextDocument, range?: Range ): DocumentHelperFunction[] { - const text = doc.getText(range) + const text = getTextWithoutComments(doc, 'css', range) const matches = findAll( /(?^|\s)(?theme|config)\((?:(?')([^']+)'|(?")([^"]+)")[^)]*\)/gm, text @@ -408,8 +412,10 @@ export async function findClassNameAtPosition( if (isCssContext(state, doc, position)) { classNames = await findClassNamesInRange(state, doc, searchRange, 'css') - } else if (isHtmlContext(state, doc, position) || isJsxContext(state, doc, position)) { + } else if (isHtmlContext(state, doc, position)) { classNames = await findClassNamesInRange(state, doc, searchRange, 'html') + } else if (isJsxContext(state, doc, position)) { + classNames = await findClassNamesInRange(state, doc, searchRange, 'jsx') } if (classNames.length === 0) { diff --git a/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts b/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts index 1f91fa1..3e972e9 100644 --- a/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts +++ b/packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts @@ -5,6 +5,7 @@ import { indexToPosition } from './find' import { isJsDoc } from './js' import moo from 'moo' import Cache from 'tmp-cache' +import { getTextWithoutComments } from './doc' export type LanguageBoundary = { type: 'html' | 'js' | 'css' | string; range: Range } @@ -113,10 +114,14 @@ export function getLanguageBoundaries( return cachedBoundaries } + let isJs = isJsDoc(state, doc) + let defaultType = isVueDoc(doc) ? 'none' - : isHtmlDoc(state, doc) || isJsDoc(state, doc) || isSvelteDoc(doc) + : isHtmlDoc(state, doc) || isSvelteDoc(doc) ? 'html' + : isJs + ? 'jsx' : null if (defaultType === null) { @@ -124,6 +129,8 @@ export function getLanguageBoundaries( return null } + text = getTextWithoutComments(text, isJs ? 'js' : 'html') + let lexer = defaultType === 'none' ? vueLexer : defaultLexer lexer.reset(text)