Skip to content

Commit

Permalink
Remove duplicate variant + value pairs from completions (#874)
Browse files Browse the repository at this point in the history
* Refactor

* Support using multiple fixtures in a single test file

* Add test

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

* Update changelog
  • Loading branch information
thecrypticace authored Oct 27, 2023
1 parent a13708b commit 2caebd1
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 100 deletions.
41 changes: 12 additions & 29 deletions packages/tailwindcss-language-server/tests/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
plugins: [
function ({ addVariant, matchVariant }) {
matchVariant('custom', (value) => `.custom:${value} &`, { values: { hover: 'hover' } })
addVariant('custom-hover', `.custom:hover &:hover`)
},
],
}
152 changes: 81 additions & 71 deletions packages/tailwindcss-language-service/src/completionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export function completionsFromClassList(
}

let items: CompletionItem[] = []
let seenVariants = new Set<string>()

if (!important) {
let variantOrder = 0
Expand All @@ -163,85 +164,94 @@ 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 (seenVariants.has(variant.name)) {
continue
}

seenVariants.add(variant.name)

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,
},
},
]
: [],
})
)
},
]
: [],
})
)
}

for (let value of variant.values ?? []) {
if (existingVariants.includes(`${variant.name}-${value}`)) {
continue
}

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(', '),
})
)
)
if (seenVariants.has(`${variant.name}-${value}`)) {
continue
}

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) {
Expand Down
1 change: 1 addition & 0 deletions packages/vscode-tailwindcss/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 0.11.x (Pre-Release)

- Add support for Glimmer (#867)
- Ignore duplicate variant + value pairs (#874)

## 0.10.1

Expand Down

0 comments on commit 2caebd1

Please sign in to comment.