From 7570c9d1ecf1cc5ffce8d73b9ab2f4fade9c4745 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 25 Mar 2024 17:27:41 -0400 Subject: [PATCH 1/6] Refactor --- .../tests/hover/hover.test.js | 24 +++++++------- .../getInvalidConfigPathDiagnostics.ts | 3 +- .../src/hoverProvider.ts | 33 ++++++++++--------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/tailwindcss-language-server/tests/hover/hover.test.js b/packages/tailwindcss-language-server/tests/hover/hover.test.js index f1b847c3..2c1bf681 100644 --- a/packages/tailwindcss-language-server/tests/hover/hover.test.js +++ b/packages/tailwindcss-language-server/tests/hover/hover.test.js @@ -2,7 +2,7 @@ import { test } from 'vitest' import { withFixture } from '../common' withFixture('basic', (c) => { - async function testHover(name, { text, lang, position, expected, expectedRange, settings }) { + async function testHover(name, { text, lang, position, exact = false, expected, expectedRange, settings }) { test.concurrent(name, async ({ expect }) => { let textDocument = await c.openDocument({ text, lang, settings }) let res = await c.sendRequest('textDocument/hover', { @@ -10,17 +10,17 @@ withFixture('basic', (c) => { position, }) - expect(res).toEqual( - expected - ? { - contents: { - language: 'css', - value: expected, - }, - range: expectedRange, - } - : expected, - ) + if (!exact && expected) { + expected = { + contents: { + language: 'css', + value: expected, + }, + range: expectedRange, + } + } + + expect(res).toEqual(expected) }) } diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts index 1f8941ae..6b67a814 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts @@ -23,7 +23,8 @@ export function validateConfigPath( base: string[] = [], ): { isValid: true; value: any } | { isValid: false; reason: string; suggestions: string[] } { let keys = Array.isArray(path) ? path : stringToPath(path) - let value = dlv(state.config, [...base, ...keys]) + let fullPath = [...base, ...keys] + let value = dlv(state.config, fullPath) let suggestions: string[] = [] function findAlternativePath(): string[] { diff --git a/packages/tailwindcss-language-service/src/hoverProvider.ts b/packages/tailwindcss-language-service/src/hoverProvider.ts index d3869337..8793a73e 100644 --- a/packages/tailwindcss-language-service/src/hoverProvider.ts +++ b/packages/tailwindcss-language-service/src/hoverProvider.ts @@ -18,11 +18,11 @@ export async function doHover( ): Promise { return ( (await provideClassNameHover(state, document, position)) || - provideCssHelperHover(state, document, position) + (await provideCssHelperHover(state, document, position)) ) } -function provideCssHelperHover(state: State, document: TextDocument, position: Position): Hover { +async function provideCssHelperHover(state: State, document: TextDocument, position: Position): Promise { if (!isCssContext(state, document, position)) { return null } @@ -33,20 +33,21 @@ function provideCssHelperHover(state: State, document: TextDocument, position: P }) for (let helperFn of helperFns) { - if (isWithinRange(position, helperFn.ranges.path)) { - let validated = validateConfigPath( - state, - helperFn.path, - helperFn.helper === 'theme' ? ['theme'] : [], - ) - let value = validated.isValid ? stringifyConfigValue(validated.value) : null - if (value === null) { - return null - } - return { - contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') }, - range: helperFn.ranges.path, - } + if (!isWithinRange(position, helperFn.ranges.path)) continue + + let validated = validateConfigPath( + state, + helperFn.path, + helperFn.helper === 'theme' ? ['theme'] : [], + ) + + // This property may not exist in the state object because of compatability with Tailwind Play + let value = validated.isValid ? stringifyConfigValue(validated.value) : null + if (value === null) return null + + return { + contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') }, + range: helperFn.ranges.path, } } From c0fa8b3b42e892b88d51426786d697a6e8728a98 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 25 Mar 2024 17:31:23 -0400 Subject: [PATCH 2/6] =?UTF-8?q?Don=E2=80=99t=20wrap=20string=20theme=20val?= =?UTF-8?q?ues=20in=20quotes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/tailwindcss-language-service/src/util/stringify.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tailwindcss-language-service/src/util/stringify.ts b/packages/tailwindcss-language-service/src/util/stringify.ts index eaa70c2e..725d224d 100644 --- a/packages/tailwindcss-language-service/src/util/stringify.ts +++ b/packages/tailwindcss-language-service/src/util/stringify.ts @@ -11,6 +11,7 @@ import { addEquivalents } from './equivalents' export function stringifyConfigValue(x: any): string { if (isObject(x)) return `${Object.keys(x).length} values` if (typeof x === 'function') return 'ƒ' + if (typeof x === 'string') return x return stringifyObject(x, { inlineCharacterLimit: Infinity, singleQuotes: false, From 49d0dd6bdbcfaa5c8024795c04884f8ed6e00d93 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 25 Mar 2024 17:33:38 -0400 Subject: [PATCH 3/6] Transform shown theme values when possible This internal helper is what Tailwind uses to emit the value into the CSS. --- packages/tailwindcss-language-server/src/projects.ts | 10 ++++++++++ .../src/diagnostics/getInvalidConfigPathDiagnostics.ts | 7 +++++++ .../tailwindcss-language-service/src/util/state.ts | 1 + 3 files changed, 18 insertions(+) diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts index fc15964f..0d923154 100644 --- a/packages/tailwindcss-language-server/src/projects.ts +++ b/packages/tailwindcss-language-server/src/projects.ts @@ -415,6 +415,7 @@ export async function createProjectService( let pluginVersions: string | undefined let browserslist: string[] | undefined let resolveConfigFn: (config: any) => any + let transformThemeValueFn: (section: any) => (value: any) => any let loadConfigFn: (path: string) => any let featureFlags: FeatureFlags = { future: [], experimental: [] } let applyComplexClasses: any @@ -513,6 +514,13 @@ export async function createProjectService( } } + try { + let fn = require(resolveFrom(tailwindDir, './lib/util/transformThemeValue.js')) + transformThemeValueFn = fn.default ?? fn + } catch { + // + } + try { loadConfigFn = require(resolveFrom(tailwindDir, './loadConfig.js')) } catch {} @@ -633,6 +641,7 @@ export async function createProjectService( console.error(util.format(error)) tailwindcss = require('tailwindcss') resolveConfigFn = require('tailwindcss/resolveConfig') + transformThemeValueFn = require('tailwindcss/lib/util/transformThemeValue').default loadConfigFn = require('tailwindcss/loadConfig') postcss = require('postcss') tailwindcssVersion = require('tailwindcss/package.json').version @@ -660,6 +669,7 @@ export async function createProjectService( postcssSelectorParser: { module: postcssSelectorParser }, resolveConfig: { module: resolveConfigFn }, loadConfig: { module: loadConfigFn }, + transformThemeValue: { module: transformThemeValueFn }, jit: jitModules, } state.browserslist = browserslist diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts index 6b67a814..c3e79308 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts @@ -27,6 +27,13 @@ export function validateConfigPath( let value = dlv(state.config, fullPath) let suggestions: string[] = [] + // This property may not exist in the state object because of compatability with Tailwind Play + let transformThemeValue = state.modules?.transformThemeValue?.module ?? ((_: any) => (value: any) => value) + + if (fullPath[0] === 'theme' && fullPath[1]) { + value = transformThemeValue(fullPath[1])(value) + } + function findAlternativePath(): string[] { let points = combinations('123456789'.substr(0, keys.length - 1)).map((x) => x.split('').map((x) => parseInt(x, 10)), diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index 3f94f259..abe8863c 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -107,6 +107,7 @@ export interface State { postcss?: { version: string; module: Postcss } postcssSelectorParser?: { module: any } resolveConfig?: { module: any } + transformThemeValue?: { module: any } loadConfig?: { module: any } jit?: { generateRules: { module: any } From e93f702e500f42cdfe221ed63e7517f9ec0e6961 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 25 Mar 2024 17:33:50 -0400 Subject: [PATCH 4/6] Show pixel equivlents for theme values --- .../tests/hover/hover.test.js | 22 +++++++++++++++++++ .../src/hoverProvider.ts | 7 ++++++ 2 files changed, 29 insertions(+) diff --git a/packages/tailwindcss-language-server/tests/hover/hover.test.js b/packages/tailwindcss-language-server/tests/hover/hover.test.js index 2c1bf681..3aea22ac 100644 --- a/packages/tailwindcss-language-server/tests/hover/hover.test.js +++ b/packages/tailwindcss-language-server/tests/hover/hover.test.js @@ -89,6 +89,28 @@ withFixture('basic', (c) => { end: { line: 2, character: 18 }, }, }) + + testHover('showPixelEquivalents works with theme()', { + lang: 'tailwindcss', + text: `.foo { font-size: theme(fontSize.xl) }`, + position: { line: 0, character: 32 }, + + exact: true, + expected: { + contents: { + kind: 'markdown', + value: [ + '```plaintext', + '1.25rem/* 20px */', + '```', + ].join('\n'), + }, + range: { + start: { line: 0, character: 24 }, + end: { line: 0, character: 35 }, + } + }, + }) }) withFixture('v4/basic', (c) => { diff --git a/packages/tailwindcss-language-service/src/hoverProvider.ts b/packages/tailwindcss-language-service/src/hoverProvider.ts index 8793a73e..6ed47ec5 100644 --- a/packages/tailwindcss-language-service/src/hoverProvider.ts +++ b/packages/tailwindcss-language-service/src/hoverProvider.ts @@ -10,6 +10,7 @@ import * as jit from './util/jit' import { validateConfigPath } from './diagnostics/getInvalidConfigPathDiagnostics' import { isWithinRange } from './util/isWithinRange' import type { TextDocument } from 'vscode-languageserver-textdocument' +import { addPixelEquivalentsToValue } from './util/pixelEquivalents' export async function doHover( state: State, @@ -27,6 +28,8 @@ async function provideCssHelperHover(state: State, document: TextDocument, posit return null } + const settings = await state.editor.getConfiguration(document.uri) + let helperFns = findHelperFunctionsInRange(document, { start: { line: position.line, character: 0 }, end: { line: position.line + 1, character: 0 }, @@ -45,6 +48,10 @@ async function provideCssHelperHover(state: State, document: TextDocument, posit let value = validated.isValid ? stringifyConfigValue(validated.value) : null if (value === null) return null + if (settings.tailwindCSS.showPixelEquivalents) { + value = addPixelEquivalentsToValue(value, settings.tailwindCSS.rootFontSize) + } + return { contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') }, range: helperFn.ranges.path, From a9aba5555b13ca89d8e5dea3078c226b60e8aec1 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 26 Mar 2024 10:39:31 -0400 Subject: [PATCH 5/6] Add spaces to equivalents comments --- .../tests/completions/completions.test.js | 12 ++++++------ .../tests/env/multi-config-content.test.js | 4 ++-- .../tests/env/multi-config.test.js | 4 ++-- .../tests/hover/hover.test.js | 8 ++++---- .../src/util/comments.ts | 2 +- .../src/util/pixelEquivalents.ts | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js index 2e3de946..656841a5 100644 --- a/packages/tailwindcss-language-server/tests/completions/completions.test.js +++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js @@ -222,11 +222,11 @@ withFixture('basic', (c) => { expect(resolved).toEqual({ ...item, - detail: 'font-size: 0.875rem/* 14px */; line-height: 1.25rem/* 20px */;', + detail: 'font-size: 0.875rem /* 14px */; line-height: 1.25rem /* 20px */;', documentation: { kind: 'markdown', value: - '```css\n.text-sm {\n font-size: 0.875rem/* 14px */;\n line-height: 1.25rem/* 20px */;\n}\n```', + '```css\n.text-sm {\n font-size: 0.875rem /* 14px */;\n line-height: 1.25rem /* 20px */;\n}\n```', }, }) }) @@ -254,11 +254,11 @@ withFixture('basic', (c) => { expect(resolved).toEqual({ ...item, - detail: 'font-size: 0.875rem/* 8.75px */; line-height: 1.25rem/* 12.5px */;', + detail: 'font-size: 0.875rem /* 8.75px */; line-height: 1.25rem /* 12.5px */;', documentation: { kind: 'markdown', value: - '```css\n.text-sm {\n font-size: 0.875rem/* 8.75px */;\n line-height: 1.25rem/* 12.5px */;\n}\n```', + '```css\n.text-sm {\n font-size: 0.875rem /* 8.75px */;\n line-height: 1.25rem /* 12.5px */;\n}\n```', }, }) }) @@ -521,11 +521,11 @@ withFixture('v4/basic', (c) => { expect(resolved).toEqual({ ...item, - detail: 'font-size: 0.875rem/* 8.75px */; line-height: 1.25rem/* 12.5px */;', + detail: 'font-size: 0.875rem /* 8.75px */; line-height: 1.25rem /* 12.5px */;', documentation: { kind: 'markdown', value: - '```css\n.text-sm {\n font-size: 0.875rem/* 8.75px */;\n line-height: 1.25rem/* 12.5px */;\n}\n```', + '```css\n.text-sm {\n font-size: 0.875rem /* 8.75px */;\n line-height: 1.25rem /* 12.5px */;\n}\n```', }, }) }) 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 591a3325..0bdccd13 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)) /* #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)) /* #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 9d34eb62..5050314c 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)) /* #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)) /* #0000ff */;\n}', }, range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } }, }) diff --git a/packages/tailwindcss-language-server/tests/hover/hover.test.js b/packages/tailwindcss-language-server/tests/hover/hover.test.js index 3aea22ac..0e8289c6 100644 --- a/packages/tailwindcss-language-server/tests/hover/hover.test.js +++ b/packages/tailwindcss-language-server/tests/hover/hover.test.js @@ -38,7 +38,7 @@ withFixture('basic', (c) => { 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)) /* #ef4444 */;\n' + '}', expectedRange: { start: { line: 0, character: 12 }, @@ -59,7 +59,7 @@ withFixture('basic', (c) => { testHover('arbitrary value with theme function', { text: '
', position: { line: 0, character: 13 }, - expected: '.p-\\[theme\\(spacing\\.4\\)\\] {\n' + ' padding: 1rem/* 16px */;\n' + '}', + expected: '.p-\\[theme\\(spacing\\.4\\)\\] {\n' + ' padding: 1rem /* 16px */;\n' + '}', expectedRange: { start: { line: 0, character: 12 }, end: { line: 0, character: 32 }, @@ -101,7 +101,7 @@ withFixture('basic', (c) => { kind: 'markdown', value: [ '```plaintext', - '1.25rem/* 20px */', + '1.25rem /* 20px */', '```', ].join('\n'), }, @@ -168,7 +168,7 @@ withFixture('v4/basic', (c) => { // testHover('arbitrary value with theme function', { // text: '
', // position: { line: 0, character: 13 }, - // expected: '.p-\\[theme\\(spacing\\.4\\)\\] {\n' + ' padding: 1rem/* 16px */;\n' + '}', + // expected: '.p-\\[theme\\(spacing\\.4\\)\\] {\n' + ' padding: 1rem /* 16px */;\n' + '}', // expectedRange: { // start: { line: 0, character: 12 }, // end: { line: 0, character: 32 }, diff --git a/packages/tailwindcss-language-service/src/util/comments.ts b/packages/tailwindcss-language-service/src/util/comments.ts index fef04267..1a989643 100644 --- a/packages/tailwindcss-language-service/src/util/comments.ts +++ b/packages/tailwindcss-language-service/src/util/comments.ts @@ -5,7 +5,7 @@ export function applyComments(str: string, comments: Comment[]): string { for (let comment of comments) { let index = comment.index + offset - let commentStr = `/* ${comment.value} */` + let commentStr = ` /* ${comment.value} */` str = str.slice(0, index) + commentStr + str.slice(index) offset += commentStr.length } diff --git a/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts b/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts index b562c33d..1901923c 100644 --- a/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts +++ b/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts @@ -20,7 +20,7 @@ export function addPixelEquivalentsToValue(value: string, rootFontSize: number): return false } - let commentStr = `/* ${parseFloat(unit.number) * rootFontSize}px */` + let commentStr = ` /* ${parseFloat(unit.number) * rootFontSize}px */` value = value.slice(0, node.sourceEndIndex) + commentStr + value.slice(node.sourceEndIndex) return false From 3c547f3fe8c07bdb3a0792800b8a2cf0f4002361 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 26 Mar 2024 10:42:12 -0400 Subject: [PATCH 6/6] Update changelog --- packages/vscode-tailwindcss/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index 8773adba..75602d67 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -6,6 +6,7 @@ - Support Astro's `class:list` attribute by default (#890) - Fix hovers and CSS conflict detection in Vue `