From 800e11a39645138f839e74504c0ec341cf61c48a Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 26 Oct 2023 19:27:21 -0400 Subject: [PATCH 1/5] Refactor --- .../src/completionProvider.ts | 142 +++++++++--------- 1 file changed, 70 insertions(+), 72 deletions(-) diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts index 62ab9e5a..ab152999 100644 --- a/packages/tailwindcss-language-service/src/completionProvider.ts +++ b/packages/tailwindcss-language-service/src/completionProvider.ts @@ -138,6 +138,7 @@ export function completionsFromClassList( } let items: CompletionItem[] = [] + let seenVariants = new Set() if (!important) { let variantOrder = 0 @@ -163,85 +164,82 @@ export function completionsFromClassList( } } - items.push( - ...state.variants.flatMap((variant) => { - let items: CompletionItem[] = [] - - if (variant.isArbitrary) { - items.push( - variantItem({ - label: `${variant.name}${variant.hasDash ? '-' : ''}[]${sep}`, - insertTextFormat: 2, - textEditText: `${variant.name}${variant.hasDash ? '-' : ''}[\${1}]${sep}\${0}`, - // command: { - // title: '', - // command: 'tailwindCSS.onInsertArbitraryVariantSnippet', - // arguments: [variant.name, replacementRange], - // }, - }) + for (let variant of state.variants) { + if (existingVariants.includes(variant.name)) { + continue + } + + if (variant.isArbitrary) { + items.push( + variantItem({ + label: `${variant.name}${variant.hasDash ? '-' : ''}[]${sep}`, + insertTextFormat: 2, + textEditText: `${variant.name}${variant.hasDash ? '-' : ''}[\${1}]${sep}\${0}`, + // command: { + // title: '', + // command: 'tailwindCSS.onInsertArbitraryVariantSnippet', + // arguments: [variant.name, replacementRange], + // }, + }) + ) + } else { + let shouldSortVariants = !semver.gte(state.version, '2.99.0') + let resultingVariants = [...existingVariants, variant.name] + + if (shouldSortVariants) { + let allVariants = state.variants.map(({ name }) => name) + resultingVariants = resultingVariants.sort( + (a, b) => allVariants.indexOf(b) - allVariants.indexOf(a) ) - } else if (!existingVariants.includes(variant.name)) { - let shouldSortVariants = !semver.gte(state.version, '2.99.0') - let resultingVariants = [...existingVariants, variant.name] - - if (shouldSortVariants) { - let allVariants = state.variants.map(({ name }) => name) - resultingVariants = resultingVariants.sort( - (a, b) => allVariants.indexOf(b) - allVariants.indexOf(a) - ) - } + } - items.push( - variantItem({ - label: `${variant.name}${sep}`, - detail: variant - .selectors() - .map((selector) => addPixelEquivalentsToMediaQuery(selector, rootFontSize)) - .join(', '), - textEditText: resultingVariants[resultingVariants.length - 1] + sep, - additionalTextEdits: - shouldSortVariants && resultingVariants.length > 1 - ? [ - { - newText: - resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + - sep, - range: { - start: { - ...classListRange.start, - character: classListRange.end.character - partialClassName.length, - }, - end: { - ...replacementRange.start, - character: replacementRange.start.character, - }, + items.push( + variantItem({ + label: `${variant.name}${sep}`, + detail: variant + .selectors() + .map((selector) => addPixelEquivalentsToMediaQuery(selector, rootFontSize)) + .join(', '), + textEditText: resultingVariants[resultingVariants.length - 1] + sep, + additionalTextEdits: + shouldSortVariants && resultingVariants.length > 1 + ? [ + { + newText: + resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + sep, + range: { + start: { + ...classListRange.start, + character: classListRange.end.character - partialClassName.length, + }, + end: { + ...replacementRange.start, + character: replacementRange.start.character, }, }, - ] - : [], - }) - ) - } + }, + ] + : [], + }) + ) + } - if (variant.values.length) { - items.push( - ...variant.values - .filter((value) => !existingVariants.includes(`${variant.name}-${value}`)) - .map((value) => - variantItem({ - label: - value === 'DEFAULT' - ? `${variant.name}${sep}` - : `${variant.name}${variant.hasDash ? '-' : ''}${value}${sep}`, - detail: variant.selectors({ value }).join(', '), - }) - ) - ) + for (let value of variant.values ?? []) { + if (existingVariants.includes(`${variant.name}-${value}`)) { + continue } - return items - }) - ) + items.push( + variantItem({ + label: + value === 'DEFAULT' + ? `${variant.name}${sep}` + : `${variant.name}${variant.hasDash ? '-' : ''}${value}${sep}`, + detail: variant.selectors({ value }).join(', '), + }) + ) + } + } } if (state.classList) { From 8b62b5f43c1035e3e24fc6bfc04745c609182ed3 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 26 Oct 2023 22:16:27 -0400 Subject: [PATCH 2/5] Support using multiple fixtures in a single test file --- .../tests/common.js | 41 ++++++------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/packages/tailwindcss-language-server/tests/common.js b/packages/tailwindcss-language-server/tests/common.js index 7202e736..abdc8cca 100644 --- a/packages/tailwindcss-language-server/tests/common.js +++ b/packages/tailwindcss-language-server/tests/common.js @@ -3,13 +3,11 @@ import * as cp from 'node:child_process' import * as rpc from 'vscode-jsonrpc' import { beforeAll } from 'vitest' -let settings = {} -let initPromise -let childProcess -let docSettings = new Map() - async function init(fixture) { - childProcess = cp.fork('./bin/tailwindcss-language-server', { silent: true }) + let settings = {} + let docSettings = new Map() + + let childProcess = cp.fork('./bin/tailwindcss-language-server', { silent: true }) const capabilities = { textDocument: { @@ -116,7 +114,7 @@ async function init(fixture) { }) }) - initPromise = new Promise((resolve) => { + let initPromise = new Promise((resolve) => { connection.onRequest(new rpc.RequestType('client/registerCapability'), ({ registrations }) => { if (registrations.findIndex((r) => r.method === 'textDocument/completion') > -1) { resolve() @@ -177,33 +175,18 @@ async function init(fixture) { } export function withFixture(fixture, callback) { - let c + let c = {} beforeAll(async () => { - c = await init(fixture) + // Using the connection object as the prototype lets us access the connection + // without defining getters for all the methods and also lets us add helpers + // to the connection object without having to resort to using a Proxy + Object.setPrototypeOf(c, await init(fixture)) + return () => c.connection.end() }) - callback({ - get connection() { - return c.connection - }, - get sendRequest() { - return c.sendRequest - }, - get onNotification() { - return c.onNotification - }, - get openDocument() { - return c.openDocument - }, - get updateSettings() { - return c.updateSettings - }, - get updateFile() { - return c.updateFile - }, - }) + callback(c) } // let counter = 0 From 2c866ce18214305a6d8fa997a6aa86573855c85d Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 26 Oct 2023 21:51:25 -0400 Subject: [PATCH 3/5] Add test --- .../tests/completions/completions.test.js | 32 +++++++++++++++++++ .../overrides-variants/tailwind.config.js | 8 +++++ 2 files changed, 40 insertions(+) create mode 100644 packages/tailwindcss-language-server/tests/fixtures/overrides-variants/tailwind.config.js diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js index df64d38d..996518a9 100644 --- a/packages/tailwindcss-language-server/tests/completions/completions.test.js +++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js @@ -119,3 +119,35 @@ withFixture('basic', (c) => { }) }) }) + +withFixture('overrides-variants', (c) => { + async function completion({ + lang, + text, + position, + context = { + triggerKind: 1, + }, + settings, + }) { + let textDocument = await c.openDocument({ text, lang, settings }) + + return c.sendRequest('textDocument/completion', { + textDocument, + position, + context, + }) + } + + test.concurrent( + 'duplicate variant + value pairs do not produce multiple completions', + async () => { + let result = await completion({ + text: '
', + position: { line: 0, character: 23 }, + }) + + expect(result.items.filter((item) => item.label.endsWith('custom-hover:')).length).toBe(1) + } + ) +}) diff --git a/packages/tailwindcss-language-server/tests/fixtures/overrides-variants/tailwind.config.js b/packages/tailwindcss-language-server/tests/fixtures/overrides-variants/tailwind.config.js new file mode 100644 index 00000000..22e28cd6 --- /dev/null +++ b/packages/tailwindcss-language-server/tests/fixtures/overrides-variants/tailwind.config.js @@ -0,0 +1,8 @@ +module.exports = { + plugins: [ + function ({ addVariant, matchVariant }) { + matchVariant('custom', (value) => `.custom:${value} &`, { values: { hover: 'hover' } }) + addVariant('custom-hover', `.custom:hover &:hover`) + }, + ], +} From de9b5a9db0074101ab77d8d0a23451782e9eabbf Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 26 Oct 2023 19:28:02 -0400 Subject: [PATCH 4/5] Remove duplicate `variant` + `value` pairs from completions --- .../src/completionProvider.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/tailwindcss-language-service/src/completionProvider.ts b/packages/tailwindcss-language-service/src/completionProvider.ts index ab152999..6468b93a 100644 --- a/packages/tailwindcss-language-service/src/completionProvider.ts +++ b/packages/tailwindcss-language-service/src/completionProvider.ts @@ -169,6 +169,12 @@ export function completionsFromClassList( continue } + if (seenVariants.has(variant.name)) { + continue + } + + seenVariants.add(variant.name) + if (variant.isArbitrary) { items.push( variantItem({ @@ -229,6 +235,12 @@ export function completionsFromClassList( continue } + if (seenVariants.has(`${variant.name}-${value}`)) { + continue + } + + seenVariants.add(`${variant.name}-${value}`) + items.push( variantItem({ label: From c6364ceab8c5f24e9c91c5673bcfcf30b0f96e3a Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 26 Oct 2023 21:55:26 -0400 Subject: [PATCH 5/5] 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 176d09b7..b5a9adb6 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -3,6 +3,7 @@ ## 0.11.x (Pre-Release) - Add support for Glimmer (#867) +- Ignore duplicate variant + value pairs (#874) ## 0.10.1