Skip to content

Commit 5004278

Browse files
committed
Add support for @source not
1 parent 0cddb23 commit 5004278

File tree

10 files changed

+153
-5
lines changed

10 files changed

+153
-5
lines changed

packages/tailwindcss-language-server/tests/completions/at-config.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,51 @@ withFixture('v4/dependencies', (c) => {
271271
})
272272
})
273273

274+
test.concurrent('@source not', async ({ expect }) => {
275+
let result = await completion({
276+
text: '@source not "',
277+
lang: 'css',
278+
position: {
279+
line: 0,
280+
character: 13,
281+
},
282+
})
283+
284+
expect(result).toEqual({
285+
isIncomplete: false,
286+
items: [
287+
{
288+
label: 'index.html',
289+
kind: 17,
290+
data: expect.anything(),
291+
textEdit: {
292+
newText: 'index.html',
293+
range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } },
294+
},
295+
},
296+
{
297+
label: 'sub-dir/',
298+
kind: 19,
299+
command: { command: 'editor.action.triggerSuggest', title: '' },
300+
data: expect.anything(),
301+
textEdit: {
302+
newText: 'sub-dir/',
303+
range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } },
304+
},
305+
},
306+
{
307+
label: 'tailwind.config.js',
308+
kind: 17,
309+
data: expect.anything(),
310+
textEdit: {
311+
newText: 'tailwind.config.js',
312+
range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } },
313+
},
314+
},
315+
],
316+
})
317+
})
318+
274319
test.concurrent('@source directory', async ({ expect }) => {
275320
let result = await completion({
276321
text: '@source "./sub-dir/',
@@ -297,6 +342,32 @@ withFixture('v4/dependencies', (c) => {
297342
})
298343
})
299344

345+
test.concurrent('@source not directory', async ({ expect }) => {
346+
let result = await completion({
347+
text: '@source not "./sub-dir/',
348+
lang: 'css',
349+
position: {
350+
line: 0,
351+
character: 23,
352+
},
353+
})
354+
355+
expect(result).toEqual({
356+
isIncomplete: false,
357+
items: [
358+
{
359+
label: 'colors.js',
360+
kind: 17,
361+
data: expect.anything(),
362+
textEdit: {
363+
newText: 'colors.js',
364+
range: { start: { line: 0, character: 23 }, end: { line: 0, character: 23 } },
365+
},
366+
},
367+
],
368+
})
369+
})
370+
300371
test.concurrent('@import "…" source(…)', async ({ expect }) => {
301372
let result = await completion({
302373
text: '@import "tailwindcss" source("',

packages/tailwindcss-language-server/tests/completions/completions.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ withFixture('v4/basic', (c) => {
485485
})
486486

487487
// Make sure `@slot` is NOT suggested by default
488-
expect(result.items.length).toBe(7)
488+
expect(result.items.length).toBe(8)
489489
expect(result.items).not.toEqual(
490490
expect.arrayContaining([
491491
expect.objectContaining({ kind: 14, label: '@slot', sortText: '-0000000' }),

packages/tailwindcss-language-server/tests/document-links/document-links.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,32 @@ withFixture('v4/basic', (c) => {
131131
],
132132
})
133133

134+
testDocumentLinks('source not: file exists', {
135+
text: '@source not "index.html";',
136+
lang: 'css',
137+
expected: [
138+
{
139+
target: `file://${path
140+
.resolve('./tests/fixtures/v4/basic/index.html')
141+
.replace(/@/g, '%40')}`,
142+
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 24 } },
143+
},
144+
],
145+
})
146+
147+
testDocumentLinks('source not: file does not exist', {
148+
text: '@source not "does-not-exist.html";',
149+
lang: 'css',
150+
expected: [
151+
{
152+
target: `file://${path
153+
.resolve('./tests/fixtures/v4/basic/does-not-exist.html')
154+
.replace(/@/g, '%40')}`,
155+
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 33 } },
156+
},
157+
],
158+
})
159+
134160
testDocumentLinks('Directories in source(…) show links', {
135161
text: `
136162
@import "tailwindcss" source("../../");

packages/tailwindcss-language-server/tests/hover/hover.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,33 @@ withFixture('v4/basic', (c) => {
293293
},
294294
})
295295

296+
testHover('css @source not glob expansion', {
297+
exact: true,
298+
lang: 'css',
299+
text: `@source not "../{app,components}/**/*.jsx"`,
300+
position: { line: 0, character: 23 },
301+
expected: {
302+
contents: {
303+
kind: 'markdown',
304+
value: [
305+
'**Expansion**',
306+
'```plaintext',
307+
'- ../app/**/*.jsx',
308+
'- ../components/**/*.jsx',
309+
'```',
310+
].join('\n'),
311+
},
312+
range: {
313+
start: { line: 0, character: 12 },
314+
end: { line: 0, character: 42 },
315+
},
316+
},
317+
expectedRange: {
318+
start: { line: 2, character: 9 },
319+
end: { line: 2, character: 18 },
320+
},
321+
})
322+
296323
testHover('--theme() works inside @media queries', {
297324
lang: 'tailwindcss',
298325
text: `@media (width>=--theme(--breakpoint-xl)) { .foo { color: red; } }`,

packages/tailwindcss-language-service/src/completionProvider.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1872,6 +1872,17 @@ function provideCssDirectiveCompletions(
18721872
},
18731873
})
18741874

1875+
items.push({
1876+
label: '@source not',
1877+
documentation: {
1878+
kind: 'markdown' as typeof MarkupKind.Markdown,
1879+
value: `Use the \`@source not\` directive to ignore files when scanning.\n\n[Tailwind CSS Documentation](${docsUrl(
1880+
state.version,
1881+
'functions-and-directives/#source',
1882+
)})`,
1883+
},
1884+
})
1885+
18751886
items.push({
18761887
label: '@plugin',
18771888
documentation: {

packages/tailwindcss-language-service/src/completions/file-paths.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ test('Detecting v3 directives that point to files', async () => {
1515
// The following are not supported in v3
1616
await expect(find('@plugin "./')).resolves.toEqual(null)
1717
await expect(find('@source "./')).resolves.toEqual(null)
18+
await expect(find('@source not "./')).resolves.toEqual(null)
1819
await expect(find('@import "tailwindcss" source("./')).resolves.toEqual(null)
1920
await expect(find('@tailwind utilities source("./')).resolves.toEqual(null)
2021
})
@@ -42,6 +43,12 @@ test('Detecting v4 directives that point to files', async () => {
4243
suggest: 'source',
4344
})
4445

46+
await expect(find('@source not "./')).resolves.toEqual({
47+
directive: 'source',
48+
partial: './',
49+
suggest: 'source',
50+
})
51+
4552
await expect(find('@import "tailwindcss" source("./')).resolves.toEqual({
4653
directive: 'import',
4754
partial: './',

packages/tailwindcss-language-service/src/completions/file-paths.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { State } from '../util/state'
22

33
// @config, @plugin, @source
4-
const PATTERN_CUSTOM_V4 = /@(?<directive>config|plugin|source)\s*(?<partial>'[^']*|"[^"]*)$/
4+
const PATTERN_CUSTOM_V4 =
5+
/@(?<directive>config|plugin|source)(?<not>\s+not)?\s*(?<partial>'[^']*|"[^"]*)$/
56
const PATTERN_CUSTOM_V3 = /@(?<directive>config)\s*(?<partial>'[^']*|"[^"]*)$/
67

78
// @import … source('…')
@@ -26,6 +27,7 @@ export async function findFileDirective(state: State, text: string): Promise<Fil
2627

2728
if (!match) return null
2829

30+
let isNot = match.groups.not !== undefined && match.groups.not.length > 0
2931
let directive = match.groups.directive
3032
let partial = match.groups.partial?.slice(1) ?? '' // remove leading quote
3133

@@ -40,6 +42,7 @@ export async function findFileDirective(state: State, text: string): Promise<Fil
4042
// If we're looking at @import … source('…') or @tailwind … source('…') then
4143
// we want to list directories instead of files
4244
else if (directive === 'import' || directive === 'tailwind') {
45+
if (isNot) return null
4346
suggest = 'directory'
4447
}
4548

@@ -49,6 +52,9 @@ export async function findFileDirective(state: State, text: string): Promise<Fil
4952
let match = text.match(PATTERN_CUSTOM_V3)
5053
if (!match) return null
5154

55+
let isNot = match.groups.not !== undefined && match.groups.not.length > 0
56+
if (isNot) return null
57+
5258
let directive = match.groups.directive
5359
let partial = match.groups.partial.slice(1) // remove leading quote
5460

packages/tailwindcss-language-service/src/diagnostics/getInvalidSourceDiagnostics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const PATTERN_UTIL_SOURCE =
1414

1515
// @source
1616
const PATTERN_AT_SOURCE =
17-
/(?:\s|^)@(?<directive>source)\s*(?<source>'[^']*'?|"[^"]*"?|[a-z]*|\)|;)/dg
17+
/(?:\s|^)@(?<directive>source)\s*(?<not>not)?\s*(?<source>'[^']*'?|"[^"]*"?|[a-z]*|\)|;)/dg
1818

1919
const HAS_DRIVE_LETTER = /^[A-Z]:/
2020

packages/tailwindcss-language-service/src/documentLinksProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function getDocumentLinks(
1818
if (state.v4) {
1919
patterns.push(
2020
/@plugin\s*(?<path>'[^']+'|"[^"]+")/g,
21-
/@source\s*(?<path>'[^']+'|"[^"]+")/g,
21+
/@source(?:\s+not)?\s*(?<path>'[^']+'|"[^"]+")/g,
2222
/@import\s*('[^']*'|"[^"]*")\s*(layer\([^)]+\)\s*)?source\((?<path>'[^']*'?|"[^"]*"?)/g,
2323
/@reference\s*('[^']*'|"[^"]*")\s*source\((?<path>'[^']*'?|"[^"]*"?)/g,
2424
/@tailwind\s*utilities\s*source\((?<path>'[^']*'?|"[^"]*"?)/g,

packages/tailwindcss-language-service/src/hoverProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ async function provideSourceGlobHover(
168168

169169
let text = getTextWithoutComments(document, 'css', range)
170170

171-
let pattern = /@source\s*(?<path>'[^']+'|"[^"]+")/dg
171+
let pattern = /@source(?:\s+not)?\s*(?<path>'[^']+'|"[^"]+")/dg
172172

173173
for (let match of findAll(pattern, text)) {
174174
let path = match.groups.path.slice(1, -1)

0 commit comments

Comments
 (0)