Remove duplicate `variant` + `value` pairs from completions (#874)

* Refactor

* Support using multiple fixtures in a single test file

* Add test

* Remove duplicate `variant` + `value` pairs from completions

* Update changelog
master
Jordan Pittman 2023-10-27 13:44:04 -04:00 committed by GitHub
parent a13708b995
commit 2caebd1d48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 99 deletions

View File

@ -3,13 +3,11 @@ import * as cp from 'node:child_process'
import * as rpc from 'vscode-jsonrpc' import * as rpc from 'vscode-jsonrpc'
import { beforeAll } from 'vitest' import { beforeAll } from 'vitest'
let settings = {}
let initPromise
let childProcess
let docSettings = new Map()
async function init(fixture) { 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 = { const capabilities = {
textDocument: { 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 }) => { connection.onRequest(new rpc.RequestType('client/registerCapability'), ({ registrations }) => {
if (registrations.findIndex((r) => r.method === 'textDocument/completion') > -1) { if (registrations.findIndex((r) => r.method === 'textDocument/completion') > -1) {
resolve() resolve()
@ -177,33 +175,18 @@ async function init(fixture) {
} }
export function withFixture(fixture, callback) { export function withFixture(fixture, callback) {
let c let c = {}
beforeAll(async () => { 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() return () => c.connection.end()
}) })
callback({ callback(c)
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
},
})
} }
// let counter = 0 // let counter = 0

View File

@ -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: '<div class="custom-hover"></div>',
position: { line: 0, character: 23 },
})
expect(result.items.filter((item) => item.label.endsWith('custom-hover:')).length).toBe(1)
}
)
})

View File

@ -0,0 +1,8 @@
module.exports = {
plugins: [
function ({ addVariant, matchVariant }) {
matchVariant('custom', (value) => `.custom:${value} &`, { values: { hover: 'hover' } })
addVariant('custom-hover', `.custom:hover &:hover`)
},
],
}

View File

@ -138,6 +138,7 @@ export function completionsFromClassList(
} }
let items: CompletionItem[] = [] let items: CompletionItem[] = []
let seenVariants = new Set<string>()
if (!important) { if (!important) {
let variantOrder = 0 let variantOrder = 0
@ -163,9 +164,16 @@ export function completionsFromClassList(
} }
} }
items.push( for (let variant of state.variants) {
...state.variants.flatMap((variant) => { if (existingVariants.includes(variant.name)) {
let items: CompletionItem[] = [] continue
}
if (seenVariants.has(variant.name)) {
continue
}
seenVariants.add(variant.name)
if (variant.isArbitrary) { if (variant.isArbitrary) {
items.push( items.push(
@ -180,7 +188,7 @@ export function completionsFromClassList(
// }, // },
}) })
) )
} else if (!existingVariants.includes(variant.name)) { } else {
let shouldSortVariants = !semver.gte(state.version, '2.99.0') let shouldSortVariants = !semver.gte(state.version, '2.99.0')
let resultingVariants = [...existingVariants, variant.name] let resultingVariants = [...existingVariants, variant.name]
@ -204,8 +212,7 @@ export function completionsFromClassList(
? [ ? [
{ {
newText: newText:
resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + sep,
sep,
range: { range: {
start: { start: {
...classListRange.start, ...classListRange.start,
@ -223,11 +230,18 @@ export function completionsFromClassList(
) )
} }
if (variant.values.length) { for (let value of variant.values ?? []) {
if (existingVariants.includes(`${variant.name}-${value}`)) {
continue
}
if (seenVariants.has(`${variant.name}-${value}`)) {
continue
}
seenVariants.add(`${variant.name}-${value}`)
items.push( items.push(
...variant.values
.filter((value) => !existingVariants.includes(`${variant.name}-${value}`))
.map((value) =>
variantItem({ variantItem({
label: label:
value === 'DEFAULT' value === 'DEFAULT'
@ -236,12 +250,8 @@ export function completionsFromClassList(
detail: variant.selectors({ value }).join(', '), detail: variant.selectors({ value }).join(', '),
}) })
) )
)
} }
}
return items
})
)
} }
if (state.classList) { if (state.classList) {

View File

@ -3,6 +3,7 @@
## 0.11.x (Pre-Release) ## 0.11.x (Pre-Release)
- Add support for Glimmer (#867) - Add support for Glimmer (#867)
- Ignore duplicate variant + value pairs (#874)
## 0.10.1 ## 0.10.1