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 changelogmaster
parent
a13708b995
commit
2caebd1d48
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
function ({ addVariant, matchVariant }) {
|
||||||
|
matchVariant('custom', (value) => `.custom:${value} &`, { values: { hover: 'hover' } })
|
||||||
|
addVariant('custom-hover', `.custom:hover &:hover`)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
|
@ -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,85 +164,94 @@ 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 (variant.isArbitrary) {
|
if (seenVariants.has(variant.name)) {
|
||||||
items.push(
|
continue
|
||||||
variantItem({
|
}
|
||||||
label: `${variant.name}${variant.hasDash ? '-' : ''}[]${sep}`,
|
|
||||||
insertTextFormat: 2,
|
seenVariants.add(variant.name)
|
||||||
textEditText: `${variant.name}${variant.hasDash ? '-' : ''}[\${1}]${sep}\${0}`,
|
|
||||||
// command: {
|
if (variant.isArbitrary) {
|
||||||
// title: '',
|
items.push(
|
||||||
// command: 'tailwindCSS.onInsertArbitraryVariantSnippet',
|
variantItem({
|
||||||
// arguments: [variant.name, replacementRange],
|
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) {
|
items.push(
|
||||||
let allVariants = state.variants.map(({ name }) => name)
|
variantItem({
|
||||||
resultingVariants = resultingVariants.sort(
|
label: `${variant.name}${sep}`,
|
||||||
(a, b) => allVariants.indexOf(b) - allVariants.indexOf(a)
|
detail: variant
|
||||||
)
|
.selectors()
|
||||||
}
|
.map((selector) => addPixelEquivalentsToMediaQuery(selector, rootFontSize))
|
||||||
|
.join(', '),
|
||||||
items.push(
|
textEditText: resultingVariants[resultingVariants.length - 1] + sep,
|
||||||
variantItem({
|
additionalTextEdits:
|
||||||
label: `${variant.name}${sep}`,
|
shouldSortVariants && resultingVariants.length > 1
|
||||||
detail: variant
|
? [
|
||||||
.selectors()
|
{
|
||||||
.map((selector) => addPixelEquivalentsToMediaQuery(selector, rootFontSize))
|
newText:
|
||||||
.join(', '),
|
resultingVariants.slice(0, resultingVariants.length - 1).join(sep) + sep,
|
||||||
textEditText: resultingVariants[resultingVariants.length - 1] + sep,
|
range: {
|
||||||
additionalTextEdits:
|
start: {
|
||||||
shouldSortVariants && resultingVariants.length > 1
|
...classListRange.start,
|
||||||
? [
|
character: classListRange.end.character - partialClassName.length,
|
||||||
{
|
},
|
||||||
newText:
|
end: {
|
||||||
resultingVariants.slice(0, resultingVariants.length - 1).join(sep) +
|
...replacementRange.start,
|
||||||
sep,
|
character: replacementRange.start.character,
|
||||||
range: {
|
|
||||||
start: {
|
|
||||||
...classListRange.start,
|
|
||||||
character: classListRange.end.character - partialClassName.length,
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
...replacementRange.start,
|
|
||||||
character: replacementRange.start.character,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
: [],
|
]
|
||||||
})
|
: [],
|
||||||
)
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let value of variant.values ?? []) {
|
||||||
|
if (existingVariants.includes(`${variant.name}-${value}`)) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variant.values.length) {
|
if (seenVariants.has(`${variant.name}-${value}`)) {
|
||||||
items.push(
|
continue
|
||||||
...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(', '),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return items
|
seenVariants.add(`${variant.name}-${value}`)
|
||||||
})
|
|
||||||
)
|
items.push(
|
||||||
|
variantItem({
|
||||||
|
label:
|
||||||
|
value === 'DEFAULT'
|
||||||
|
? `${variant.name}${sep}`
|
||||||
|
: `${variant.name}${variant.hasDash ? '-' : ''}${value}${sep}`,
|
||||||
|
detail: variant.selectors({ value }).join(', '),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.classList) {
|
if (state.classList) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue