Skip to content

Commit 573df46

Browse files
Merge branch 'main' into feat/completions-variants-static-decls
2 parents 96bd074 + e2cfc2c commit 573df46

File tree

8 files changed

+342
-60
lines changed

8 files changed

+342
-60
lines changed

packages/tailwindcss-language-server/src/tw.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { URI } from 'vscode-uri'
4141
import normalizePath from 'normalize-path'
4242
import * as path from 'node:path'
4343
import * as fs from 'node:fs/promises'
44+
import * as fsSync from 'node:fs'
4445
import type * as chokidar from 'chokidar'
4546
import picomatch from 'picomatch'
4647
import * as parcel from './watcher/index.js'
@@ -188,7 +189,8 @@ export class TW {
188189
let base = baseUri.fsPath
189190

190191
try {
191-
await fs.access(base, fs.constants.F_OK | fs.constants.R_OK)
192+
// TODO: Change this to fs.constants after the node version bump
193+
await fs.access(base, fsSync.constants.F_OK | fsSync.constants.R_OK)
192194
} catch (err) {
193195
console.error(
194196
`Unable to access the workspace folder [${base}]. This may happen if the directory does not exist or the current user does not have the necessary permissions to access it.`,

packages/tailwindcss-language-server/tests/completions/completions.test.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,8 @@ withFixture('v4/basic', (c) => {
313313
let result = await completion({ lang, text, position, settings })
314314
let textEdit = expect.objectContaining({ range: { start: position, end: position } })
315315

316-
expect(result.items.length).toBe(19283)
317-
expect(result.items.filter((item) => item.label.endsWith(':')).length).toBe(346)
316+
expect(result.items.length).not.toBe(0)
317+
expect(result.items.filter((item) => item.label.endsWith(':')).length).not.toBe(0)
318318
expect(result).toEqual({
319319
isIncomplete: false,
320320
items: expect.arrayContaining([
@@ -692,7 +692,7 @@ defineTest({
692692
// ^
693693
let completion = await document.completions({ line: 0, character: 23 })
694694

695-
expect(completion?.items.length).toBe(19236)
695+
expect(completion?.items.length).not.toBe(0)
696696
},
697697
})
698698

@@ -714,7 +714,7 @@ defineTest({
714714
// ^
715715
let completion = await document.completions({ line: 0, character: 22 })
716716

717-
expect(completion?.items.length).toBe(19236)
717+
expect(completion?.items.length).not.toBe(0)
718718
},
719719
})
720720

@@ -736,7 +736,7 @@ defineTest({
736736
// ^
737737
let completion = await document.completions({ line: 0, character: 31 })
738738

739-
expect(completion?.items.length).toBe(19236)
739+
expect(completion?.items.length).not.toBe(0)
740740
},
741741
})
742742

@@ -758,7 +758,7 @@ defineTest({
758758
// ^
759759
let completion = await document.completions({ line: 0, character: 26 })
760760

761-
expect(completion?.items.length).toBe(19236)
761+
expect(completion?.items.length).not.toBe(0)
762762
},
763763
})
764764

@@ -780,7 +780,7 @@ defineTest({
780780
// ^
781781
let completion = await document.completions({ line: 0, character: 12 })
782782

783-
expect(completion?.items.length).toBe(19237)
783+
expect(completion?.items.length).not.toBe(0)
784784

785785
// Verify that variants and utilities are all prefixed
786786
let prefixed = completion.items.filter((item) => !item.label.startsWith('tw:'))
@@ -806,7 +806,7 @@ defineTest({
806806
// ^
807807
let completion = await document.completions({ line: 0, character: 15 })
808808

809-
expect(completion?.items.length).toBe(19236)
809+
expect(completion?.items.length).not.toBe(0)
810810

811811
// Verify that no variants and utilities have prefixes
812812
let prefixed = completion.items.filter((item) => item.label.startsWith('tw:'))
@@ -839,7 +839,7 @@ defineTest({
839839
// ^
840840
let completion = await document.completions({ line: 0, character: 20 })
841841

842-
expect(completion?.items.length).toBe(19236)
842+
expect(completion?.items.length).not.toBe(0)
843843
},
844844
})
845845

@@ -870,7 +870,7 @@ defineTest({
870870
// ^
871871
let completion = await document.completions({ line: 1, character: 22 })
872872

873-
expect(completion?.items.length).toBe(19236)
873+
expect(completion?.items.length).not.toBe(0)
874874
},
875875
})
876876

@@ -960,24 +960,24 @@ defineTest({
960960
// ^
961961
let completionA = await document.completions({ line: 0, character: 13 })
962962

963-
expect(completionA?.items.length).toBe(19236)
963+
expect(completionA?.items.length).not.toBe(0)
964964

965965
// return <Test className={cn("")} />;
966966
// ^
967967
let completionB = await document.completions({ line: 3, character: 30 })
968968

969-
expect(completionB?.items.length).toBe(19236)
969+
expect(completionB?.items.length).not.toBe(0)
970970

971971
// return <Test className={cn("")} />;
972972
// ^
973973
let completionC = await document.completions({ line: 7, character: 30 })
974974

975-
expect(completionC?.items.length).toBe(19236)
975+
expect(completionC?.items.length).not.toBe(0)
976976

977977
// let y = cva("");
978978
// ^
979979
let completionD = await document.completions({ line: 10, character: 13 })
980980

981-
expect(completionD?.items.length).toBe(19236)
981+
expect(completionD?.items.length).not.toBe(0)
982982
},
983983
})

packages/tailwindcss-language-server/tests/env/v4.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ defineTest({
4949
},
5050
})
5151

52-
expect(completion?.items.length).toBe(19235)
52+
expect(completion?.items.length).not.toBe(0)
5353
},
5454
})
5555

@@ -233,7 +233,7 @@ defineTest({
233233
},
234234
})
235235

236-
expect(completion?.items.length).toBe(19235)
236+
expect(completion?.items.length).not.toBe(0)
237237
},
238238
})
239239

packages/tailwindcss-language-service/src/completionProvider.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,7 @@ export function completionsFromClassList(
202202
variant,
203203
err,
204204
})
205-
}
206205

207-
if (selectors.length === 0) {
208206
continue
209207
}
210208

packages/tailwindcss-language-service/src/util/color.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function getKeywordColor(value: unknown): KeywordColor | null {
5050

5151
// https://github.com/khalilgharbaoui/coloregex
5252
const colorRegex = new RegExp(
53-
`(?:^|\\s|\\(|,)(#(?:[0-9a-f]{2}){2,4}|(#[0-9a-f]{3})|(rgba?|hsla?|(?:ok)?(?:lab|lch))\\(\\s*(-?[\\d.]+%?(\\s*[,/]\\s*|\\s+)+){2,3}\\s*([\\d.]+%?|var\\([^)]+\\))?\\)|transparent|currentColor|${Object.keys(
53+
`(?:^|\\s|\\(|,)(#(?:[0-9a-f]{2}){2,4}|(#[0-9a-f]{3})|(rgba?|hsla?|(?:ok)?(?:lab|lch))\\(\\s*(-?[\\d.]+(%|deg|rad|grad|turn)?(\\s*[,/]\\s*|\\s+)+){2,3}\\s*([\\d.]+%?|var\\([^)]+\\))?\\)|transparent|currentColor|${Object.keys(
5454
namedColors,
5555
).join('|')})(?:$|\\s|\\)|,)`,
5656
'gi',
@@ -61,7 +61,7 @@ function getColorsInString(state: State, str: string): (culori.Color | KeywordCo
6161

6262
function toColor(match: RegExpMatchArray) {
6363
let color = match[1].replace(/var\([^)]+\)/, '1')
64-
return getKeywordColor(color) ?? culori.parse(color)
64+
return getKeywordColor(color) ?? tryParseColor(color)
6565
}
6666

6767
str = replaceCssVarsWithFallbacks(state, str)
@@ -275,8 +275,8 @@ export function getColorFromValue(value: unknown): culori.Color | KeywordColor |
275275
) {
276276
return null
277277
}
278-
const color = culori.parse(trimmedValue)
279-
return color ?? null
278+
279+
return tryParseColor(trimmedValue)
280280
}
281281

282282
let toRgb = culori.converter('rgb')
@@ -296,11 +296,21 @@ export function formatColor(color: culori.Color): string {
296296

297297
const COLOR_MIX_REGEX = /color-mix\(in [^,]+,\s*(.*?)\s*(\d+|\.\d+|\d+\.\d+)%,\s*transparent\)/g
298298

299+
function tryParseColor(color: string) {
300+
try {
301+
return culori.parse(color) ?? null
302+
} catch (err) {
303+
console.error('Error parsing color', color)
304+
console.error(err)
305+
return null
306+
}
307+
}
308+
299309
function removeColorMixWherePossible(str: string) {
300310
return str.replace(COLOR_MIX_REGEX, (match, color, percentage) => {
301311
if (color.startsWith('var(')) return match
302312

303-
let parsed = culori.parse(color)
313+
let parsed = tryParseColor(color)
304314
if (!parsed) return match
305315

306316
let alpha = Number(percentage) / 100

packages/tailwindcss-language-service/src/util/find.test.ts

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { test } from 'vitest'
2-
import { findClassListsInHtmlRange, findClassNameAtPosition } from './find'
3-
import { js, html, pug, createDocument } from './test-utils'
2+
import {
3+
findClassListsInHtmlRange,
4+
findClassNameAtPosition,
5+
findHelperFunctionsInDocument,
6+
} from './find'
7+
import { js, html, pug, createDocument, css } from './test-utils'
8+
import type { Range } from 'vscode-languageserver-textdocument'
9+
10+
const range = (startLine: number, startCol: number, endLine: number, endCol: number): Range => ({
11+
start: { line: startLine, character: startCol },
12+
end: { line: endLine, character: endCol },
13+
})
414

515
test('class regex works in astro', async ({ expect }) => {
616
let file = createDocument({
@@ -875,3 +885,162 @@ test('Can find class name inside JS/TS functions in <script> tags (Svelte)', asy
875885
},
876886
})
877887
})
888+
889+
test('Can find helper functions in CSS', async ({ expect }) => {
890+
let file = createDocument({
891+
name: 'file.css',
892+
lang: 'css',
893+
settings: {
894+
tailwindCSS: {
895+
classFunctions: ['clsx'],
896+
},
897+
},
898+
content: `
899+
.a { color: theme(foo); }
900+
.a { color: theme(foo, default); }
901+
.a { color: theme("foo"); }
902+
.a { color: theme("foo", default); }
903+
.a { color: theme(foo / 0.5); }
904+
.a { color: theme(foo / 0.5, default); }
905+
.a { color: theme("foo" / 0.5); }
906+
.a { color: theme("foo" / 0.5, default); }
907+
908+
/* nested invocations */
909+
.a { color: from-config(theme(foo)); }
910+
.a { color: from-config(theme(foo, default)); }
911+
.a { color: from-config(theme("foo")); }
912+
.a { color: from-config(theme("foo", default)); }
913+
.a { color: from-config(theme(foo / 0.5)); }
914+
.a { color: from-config(theme(foo / 0.5, default)); }
915+
.a { color: from-config(theme("foo" / 0.5)); }
916+
.a { color: from-config(theme("foo" / 0.5, default)); }
917+
`,
918+
})
919+
920+
let fns = findHelperFunctionsInDocument(file.state, file.doc)
921+
922+
expect(fns).toEqual([
923+
{
924+
helper: 'theme',
925+
path: 'foo',
926+
ranges: { full: range(1, 24, 1, 27), path: range(1, 24, 1, 27) },
927+
},
928+
{
929+
helper: 'theme',
930+
path: 'foo',
931+
ranges: { full: range(2, 24, 2, 36), path: range(2, 24, 2, 27) },
932+
},
933+
{
934+
helper: 'theme',
935+
path: 'foo',
936+
ranges: { full: range(3, 24, 3, 29), path: range(3, 25, 3, 28) },
937+
},
938+
{
939+
helper: 'theme',
940+
path: 'foo',
941+
ranges: { full: range(4, 24, 4, 38), path: range(4, 25, 4, 28) },
942+
},
943+
{
944+
helper: 'theme',
945+
path: 'foo',
946+
ranges: { full: range(5, 24, 5, 33), path: range(5, 24, 5, 27) },
947+
},
948+
{
949+
helper: 'theme',
950+
path: 'foo',
951+
ranges: { full: range(6, 24, 6, 42), path: range(6, 24, 6, 27) },
952+
},
953+
{
954+
helper: 'theme',
955+
path: 'foo',
956+
ranges: { full: range(7, 24, 7, 35), path: range(7, 25, 7, 28) },
957+
},
958+
{
959+
helper: 'theme',
960+
path: 'foo',
961+
ranges: { full: range(8, 24, 8, 44), path: range(8, 25, 8, 28) },
962+
},
963+
964+
// Nested
965+
{
966+
helper: 'config',
967+
path: 'theme(foo)',
968+
ranges: { full: range(11, 30, 11, 40), path: range(11, 30, 11, 40) },
969+
},
970+
{
971+
helper: 'theme',
972+
path: 'foo',
973+
ranges: { full: range(11, 36, 11, 39), path: range(11, 36, 11, 39) },
974+
},
975+
{
976+
helper: 'config',
977+
path: 'theme(foo, default)',
978+
ranges: { full: range(12, 30, 12, 49), path: range(12, 30, 12, 49) },
979+
},
980+
{
981+
helper: 'theme',
982+
path: 'foo',
983+
ranges: { full: range(12, 36, 12, 48), path: range(12, 36, 12, 39) },
984+
},
985+
{
986+
helper: 'config',
987+
path: 'theme("foo")',
988+
ranges: { full: range(13, 30, 13, 42), path: range(13, 30, 13, 42) },
989+
},
990+
{
991+
helper: 'theme',
992+
path: 'foo',
993+
ranges: { full: range(13, 36, 13, 41), path: range(13, 37, 13, 40) },
994+
},
995+
{
996+
helper: 'config',
997+
path: 'theme("foo", default)',
998+
ranges: { full: range(14, 30, 14, 51), path: range(14, 30, 14, 51) },
999+
},
1000+
{
1001+
helper: 'theme',
1002+
path: 'foo',
1003+
ranges: { full: range(14, 36, 14, 50), path: range(14, 37, 14, 40) },
1004+
},
1005+
{
1006+
helper: 'config',
1007+
path: 'theme(foo / 0.5)',
1008+
ranges: { full: range(15, 30, 15, 46), path: range(15, 30, 15, 46) },
1009+
},
1010+
{
1011+
helper: 'theme',
1012+
path: 'foo',
1013+
ranges: { full: range(15, 36, 15, 45), path: range(15, 36, 15, 39) },
1014+
},
1015+
{
1016+
helper: 'config',
1017+
path: 'theme(foo / 0.5, default)',
1018+
ranges: { full: range(16, 30, 16, 55), path: range(16, 30, 16, 55) },
1019+
},
1020+
{
1021+
helper: 'theme',
1022+
path: 'foo',
1023+
ranges: { full: range(16, 36, 16, 54), path: range(16, 36, 16, 39) },
1024+
},
1025+
{
1026+
helper: 'config',
1027+
path: 'theme("foo" / 0.5)',
1028+
ranges: { full: range(17, 30, 17, 48), path: range(17, 30, 17, 48) },
1029+
},
1030+
{
1031+
helper: 'theme',
1032+
path: 'foo',
1033+
ranges: { full: range(17, 36, 17, 47), path: range(17, 37, 17, 40) },
1034+
},
1035+
{
1036+
helper: 'config',
1037+
path: 'theme("foo" / 0.5, default)',
1038+
ranges: { full: range(18, 30, 18, 57), path: range(18, 30, 18, 57) },
1039+
},
1040+
{
1041+
helper: 'theme',
1042+
path: 'foo',
1043+
ranges: { full: range(18, 36, 18, 56), path: range(18, 37, 18, 40) },
1044+
},
1045+
])
1046+
})

0 commit comments

Comments
 (0)