Skip to content

Improve support for source(…) feature in v4 #1083

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
cb78506
Fix lazy language server creation
thecrypticace Nov 4, 2024
5d06852
Don’t suggest `.d.ts` files for `@config` and `@plugin`
thecrypticace Nov 4, 2024
59d4b90
Refactor
thecrypticace Nov 11, 2024
75cb5d4
Suggest completions for `source(…)`
thecrypticace Nov 4, 2024
741cd18
Don’t show syntax error for `@import … source(…)`
thecrypticace Nov 4, 2024
636fbb5
Fix syntax highlighting for `@tailwind utilities source(…)`
thecrypticace Nov 6, 2024
939c783
Highlight `@source none` as invalid
thecrypticace Nov 7, 2024
8d27fac
Highlight `@import “…” source(…)`
thecrypticace Nov 7, 2024
b93d8da
Don’t show diagnostic for `@tailwind utilities source(…)`
thecrypticace Nov 8, 2024
8ccf4d0
Add diagnostic for `@tailwind base / preflight`
thecrypticace Nov 8, 2024
c2b2c81
Add diagnostic for `@tailwind components / screens / variants`
thecrypticace Nov 8, 2024
c52af63
Add diagnostics for invalid uses of `source(…)` and `@source`
thecrypticace Nov 8, 2024
9f626f8
Detect paths in `source()` directives
thecrypticace Nov 8, 2024
78dd585
Don’t detect document links for glob-style paths
thecrypticace Nov 8, 2024
f432e12
Show expansion of `@source` globs on hover
thecrypticace Nov 8, 2024
3e8d3d4
Update lockfile
thecrypticace Nov 8, 2024
b5a5cd7
wip
thecrypticace Nov 12, 2024
40c66d1
Update packages/tailwindcss-language-server/tests/diagnostics/source-…
thecrypticace Nov 13, 2024
3152177
Update packages/tailwindcss-language-server/tests/diagnostics/source-…
thecrypticace Nov 13, 2024
30b18fd
Fix typo
thecrypticace Nov 13, 2024
565a57e
Cleanup code
thecrypticace Nov 13, 2024
20499c0
Update tests
thecrypticace Nov 13, 2024
8433c8f
Add suggestion to diagnostic
thecrypticace Nov 13, 2024
cdd8029
Update tests
thecrypticace Nov 13, 2024
0b384fc
Fix punctuation
thecrypticace Nov 13, 2024
0d1e473
Tweak diagnostic messages
thecrypticace Nov 13, 2024
6df9b02
Cleanup
thecrypticace Nov 14, 2024
2a2b3a2
Remove source(…) glob diagnostic
thecrypticace Nov 14, 2024
c3ad870
Tweak path check
thecrypticace Nov 14, 2024
c4a660d
Don’t link windows-style paths
thecrypticace Nov 14, 2024
eef8b16
Fix diagnostic
thecrypticace Nov 14, 2024
c629289
Don’t show syntax errors for `@import “…” theme(…)`
thecrypticace Nov 14, 2024
2e22ab4
Highlight theme function on import statements
thecrypticace Nov 14, 2024
4e5d7cb
wip
thecrypticace Nov 14, 2024
4305448
Don’t show syntax error for `@import “…” prefix(…)`
thecrypticace Nov 14, 2024
50631a7
Highlight prefix function on imports
thecrypticace Nov 14, 2024
e26b475
Simplify regexes
thecrypticace Nov 14, 2024
997911e
Ignore theme functions attached to an `@import`
thecrypticace Nov 14, 2024
5482ae2
Provide completions for @theme and `@import theme(…)`
thecrypticace Nov 14, 2024
3b3c202
Tweak pattern
thecrypticace Nov 15, 2024
618779b
Remove log
thecrypticace Nov 15, 2024
3566b90
Add tests
thecrypticace Nov 15, 2024
7a5f001
Update changelog
thecrypticace Nov 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/tailwindcss-language-server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function getDefaultSettings(): Settings {
invalidVariant: 'error',
invalidConfigPath: 'error',
invalidTailwindDirective: 'error',
invalidSourceDirective: 'error',
recommendedVariantOrder: 'warning',
},
showPixelEquivalents: true,
Expand Down
20 changes: 14 additions & 6 deletions packages/tailwindcss-language-server/src/language/cssServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,11 +336,7 @@ function replace(delta = 0) {
}

function createVirtualCssDocument(textDocument: TextDocument): TextDocument {
return TextDocument.create(
textDocument.uri,
textDocument.languageId,
textDocument.version,
textDocument
let content = textDocument
.getText()
.replace(/@screen(\s+[^{]+){/g, replace(-2))
.replace(/@variants(\s+[^{]+){/g, replace())
Expand All @@ -350,7 +346,19 @@ function createVirtualCssDocument(textDocument: TextDocument): TextDocument {
/@media(\s+screen\s*\([^)]+\))/g,
(_match, screen) => `@media (${MEDIA_MARKER})${' '.repeat(screen.length - 4)}`,
)
.replace(/(?<=\b(?:theme|config)\([^)]*)[.[\]]/g, '_'),
// Remove`source(…)`, `theme(…)`, and `prefix(…)` from `@import`s
// otherwise we'll show syntax-error diagnostics which we don't want
.replace(
/@import\s*("(?:[^"]+)"|'(?:[^']+)')\s*((source|theme|prefix)\([^)]+\)\s*)+/g,
(_match, url) => `@import "${url.slice(1, -1)}"`,
)
.replace(/(?<=\b(?:theme|config)\([^)]*)[.[\]]/g, '_')

return TextDocument.create(
textDocument.uri,
textDocument.languageId,
textDocument.version,
content,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,90 @@ withFixture('v4/dependencies', (c) => {
],
})
})

test.concurrent('@import "…" source(…)', async ({ expect }) => {
let result = await completion({
text: '@import "tailwindcss" source("',
lang: 'css',
position: {
line: 0,
character: 30,
},
})

expect(result).toEqual({
isIncomplete: false,
items: [
{
label: 'sub-dir/',
kind: 19,
command: { command: 'editor.action.triggerSuggest', title: '' },
data: expect.anything(),
textEdit: {
newText: 'sub-dir/',
range: { start: { line: 0, character: 30 }, end: { line: 0, character: 30 } },
},
},
],
})
})

test.concurrent('@tailwind utilities source(…)', async ({ expect }) => {
let result = await completion({
text: '@tailwind utilities source("',
lang: 'css',
position: {
line: 0,
character: 28,
},
})

expect(result).toEqual({
isIncomplete: false,
items: [
{
label: 'sub-dir/',
kind: 19,
command: { command: 'editor.action.triggerSuggest', title: '' },
data: expect.anything(),
textEdit: {
newText: 'sub-dir/',
range: { start: { line: 0, character: 28 }, end: { line: 0, character: 28 } },
},
},
],
})
})

test.concurrent('@import "…" source(…) directory', async ({ expect }) => {
let result = await completion({
text: '@import "tailwindcss" source("sub-dir/',
lang: 'css',
position: {
line: 0,
character: 38,
},
})

expect(result).toEqual({
isIncomplete: false,
items: [],
})
})

test.concurrent('@tailwind utilities source(…) directory', async ({ expect }) => {
let result = await completion({
text: '@tailwind utilities source("sub-dir/',
lang: 'css',
position: {
line: 0,
character: 36,
},
})

expect(result).toEqual({
isIncomplete: false,
items: [],
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,40 @@ withFixture('v4/basic', (c) => {
)
})

test.concurrent('@theme suggests options', async ({ expect }) => {
let result = await completion({
lang: 'css',
text: '@theme ',
position: { line: 0, character: 7 },
})

expect(result.items.length).toBe(3)
expect(result.items).toEqual(
expect.arrayContaining([
expect.objectContaining({ label: 'reference' }),
expect.objectContaining({ label: 'inline' }),
expect.objectContaining({ label: 'default' }),
]),
)
})

test.concurrent('@import "…" theme(…) suggests options', async ({ expect }) => {
let result = await completion({
lang: 'css',
text: '@import "tailwindcss/theme" theme()',
position: { line: 0, character: 34 },
})

expect(result.items.length).toBe(3)
expect(result.items).toEqual(
expect.arrayContaining([
expect.objectContaining({ label: 'reference' }),
expect.objectContaining({ label: 'inline' }),
expect.objectContaining({ label: 'default' }),
]),
)
})

test.concurrent('resolve', async ({ expect }) => {
let result = await completion({
text: '<div class="">',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,4 +314,72 @@ withFixture('v4/basic', (c) => {
},
],
})

testMatch('Old Tailwind directives warn when used in a v4 project', {
language: 'css',
code: `
@tailwind base;
@tailwind preflight;
@tailwind components;
@tailwind screens;
@tailwind variants;
`,
expected: [
{
code: 'invalidTailwindDirective',
message:
"'@tailwind base' is no longer available in v4. Use '@import \"tailwindcss/preflight\"' instead.",
suggestions: [],
range: {
start: { line: 1, character: 16 },
end: { line: 1, character: 20 },
},
severity: 1,
},
{
code: 'invalidTailwindDirective',
message:
"'@tailwind preflight' is no longer available in v4. Use '@import \"tailwindcss/preflight\"' instead.",
suggestions: [],
range: {
start: { line: 2, character: 16 },
end: { line: 2, character: 25 },
},
severity: 1,
},
{
code: 'invalidTailwindDirective',
message:
"'@tailwind components' is no longer available in v4. Use '@tailwind utilities' instead.",
suggestions: ['utilities'],
range: {
start: { line: 3, character: 16 },
end: { line: 3, character: 26 },
},
severity: 1,
},
{
code: 'invalidTailwindDirective',
message:
"'@tailwind screens' is no longer available in v4. Use '@tailwind utilities' instead.",
suggestions: ['utilities'],
range: {
start: { line: 4, character: 16 },
end: { line: 4, character: 23 },
},
severity: 1,
},
{
code: 'invalidTailwindDirective',
message:
"'@tailwind variants' is no longer available in v4. Use '@tailwind utilities' instead.",
suggestions: ['utilities'],
range: {
start: { line: 5, character: 16 },
end: { line: 5, character: 24 },
},
severity: 1,
},
],
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { expect, test } from 'vitest'
import { withFixture } from '../common'

withFixture('v4/basic', (c) => {
function runTest(name, { code, expected, language }) {
test(name, async () => {
let promise = new Promise((resolve) => {
c.onNotification('textDocument/publishDiagnostics', ({ diagnostics }) => {
resolve(diagnostics)
})
})

let doc = await c.openDocument({ text: code, lang: language })
let diagnostics = await promise

expected = JSON.parse(JSON.stringify(expected).replaceAll('{{URI}}', doc.uri))

expect(diagnostics).toMatchObject(expected)
})
}

runTest('Source directives require paths', {
language: 'css',
code: `
@import 'tailwindcss' source();
@import 'tailwindcss' source('');
@import 'tailwindcss' source("");
@tailwind utilities source();
@tailwind utilities source('');
@tailwind utilities source("");
`,
expected: [
{
code: 'invalidSourceDirective',
message: 'The source directive requires a path to a directory.',
range: {
start: { line: 1, character: 35 },
end: { line: 1, character: 35 },
},
},
{
code: 'invalidSourceDirective',
message: 'The source directive requires a path to a directory.',
range: {
start: { line: 2, character: 35 },
end: { line: 2, character: 37 },
},
},
{
code: 'invalidSourceDirective',
message: 'The source directive requires a path to a directory.',
range: {
start: { line: 3, character: 35 },
end: { line: 3, character: 37 },
},
},
{
code: 'invalidSourceDirective',
message: 'The source directive requires a path to a directory.',
range: {
start: { line: 4, character: 33 },
end: { line: 4, character: 33 },
},
},
{
code: 'invalidSourceDirective',
message: 'The source directive requires a path to a directory.',
range: {
start: { line: 5, character: 33 },
end: { line: 5, character: 35 },
},
},
{
code: 'invalidSourceDirective',
message: 'The source directive requires a path to a directory.',
range: {
start: { line: 6, character: 33 },
end: { line: 6, character: 35 },
},
},
],
})

runTest('source(none) must not be misspelled', {
language: 'css',
code: `
@import 'tailwindcss' source(no);
@tailwind utilities source(no);
`,
expected: [
{
code: 'invalidSourceDirective',
message: '`source(no)` is invalid. Did you mean `source(none)`?',
range: {
start: { line: 1, character: 35 },
end: { line: 1, character: 37 },
},
},
{
code: 'invalidSourceDirective',
message: '`source(no)` is invalid. Did you mean `source(none)`?',
range: {
start: { line: 2, character: 33 },
end: { line: 2, character: 35 },
},
},
],
})

runTest('source("…") does not produce diagnostics', {
language: 'css',
code: `
@import 'tailwindcss' source('../app');
@tailwind utilities source('../app');
@import 'tailwindcss' source("../app");
@tailwind utilities source("../app");
`,
expected: [],
})

runTest('paths given to source("…") must error when not POSIX', {
language: 'css',
code: String.raw`
@import 'tailwindcss' source('C:\\absolute\\path');
@import 'tailwindcss' source('C:relative.txt');
`,
expected: [
{
code: 'invalidSourceDirective',
message:
'POSIX-style paths are required with `source(…)` but `C:\\absolute\\path` is a Windows-style path.',
range: {
start: { line: 1, character: 35 },
end: { line: 1, character: 55 },
},
},
{
code: 'invalidSourceDirective',
message:
'POSIX-style paths are required with `source(…)` but `C:relative.txt` is a Windows-style path.',
range: {
start: { line: 2, character: 35 },
end: { line: 2, character: 51 },
},
},
],
})
})
Loading