Skip to content

Commit 92ea52f

Browse files
Warn when using blocklisted classes (#1310)
1 parent d561b61 commit 92ea52f

File tree

10 files changed

+125
-3
lines changed

10 files changed

+125
-3
lines changed

packages/tailwindcss-language-server/src/projects.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,7 @@ export async function createProjectService(
837837
)
838838

839839
state.designSystem = designSystem
840+
state.blocklist = Array.from(designSystem.invalidCandidates ?? [])
840841

841842
let deps = designSystem.dependencies()
842843

@@ -982,7 +983,9 @@ export async function createProjectService(
982983
if (typeof state.separator !== 'string') {
983984
state.separator = ':'
984985
}
985-
state.blocklist = Array.isArray(state.config.blocklist) ? state.config.blocklist : []
986+
if (!state.v4) {
987+
state.blocklist = Array.isArray(state.config.blocklist) ? state.config.blocklist : []
988+
}
986989
delete state.config.blocklist
987990

988991
if (state.v4) {
@@ -1148,6 +1151,7 @@ export async function createProjectService(
11481151
state.designSystem = designSystem
11491152
state.classList = classList
11501153
state.variants = getVariants(state)
1154+
state.blocklist = Array.from(designSystem.invalidCandidates ?? [])
11511155

11521156
let deps = designSystem.dependencies()
11531157

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

+43-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import * as fs from 'node:fs/promises'
12
import { expect, test } from 'vitest'
23
import { withFixture } from '../common'
3-
import * as fs from 'node:fs/promises'
4+
import { css, defineTest } from '../../src/testing'
5+
import { createClient } from '../utils/client'
46

57
withFixture('basic', (c) => {
68
function testFixture(fixture) {
@@ -383,3 +385,43 @@ withFixture('v4/basic', (c) => {
383385
],
384386
})
385387
})
388+
389+
defineTest({
390+
name: 'Shows warning when using blocklisted classes',
391+
fs: {
392+
'app.css': css`
393+
@import 'tailwindcss';
394+
@source not inline("{,hover:}flex");
395+
`,
396+
},
397+
prepare: async ({ root }) => ({ client: await createClient({ root }) }),
398+
handle: async ({ client }) => {
399+
let doc = await client.open({
400+
lang: 'html',
401+
text: '<div class="flex underline hover:flex">',
402+
})
403+
404+
let diagnostics = await doc.diagnostics()
405+
406+
expect(diagnostics).toEqual([
407+
{
408+
code: 'usedBlocklistedClass',
409+
message: 'The class "flex" will not be generated as it has been blocklisted',
410+
range: {
411+
start: { line: 0, character: 12 },
412+
end: { line: 0, character: 16 },
413+
},
414+
severity: 2,
415+
},
416+
{
417+
code: 'usedBlocklistedClass',
418+
message: 'The class "hover:flex" will not be generated as it has been blocklisted',
419+
range: {
420+
start: { line: 0, character: 27 },
421+
end: { line: 0, character: 37 },
422+
},
423+
severity: 2,
424+
},
425+
])
426+
},
427+
})

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

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { getInvalidConfigPathDiagnostics } from './getInvalidConfigPathDiagnosti
99
import { getInvalidTailwindDirectiveDiagnostics } from './getInvalidTailwindDirectiveDiagnostics'
1010
import { getRecommendedVariantOrderDiagnostics } from './getRecommendedVariantOrderDiagnostics'
1111
import { getInvalidSourceDiagnostics } from './getInvalidSourceDiagnostics'
12+
import { getUsedBlocklistedClassDiagnostics } from './getUsedBlocklistedClassDiagnostics'
1213

1314
export async function doValidate(
1415
state: State,
@@ -22,6 +23,7 @@ export async function doValidate(
2223
DiagnosticKind.InvalidTailwindDirective,
2324
DiagnosticKind.InvalidSourceDirective,
2425
DiagnosticKind.RecommendedVariantOrder,
26+
DiagnosticKind.UsedBlocklistedClass,
2527
],
2628
): Promise<AugmentedDiagnostic[]> {
2729
const settings = await state.editor.getConfiguration(document.uri)
@@ -52,6 +54,9 @@ export async function doValidate(
5254
...(only.includes(DiagnosticKind.RecommendedVariantOrder)
5355
? await getRecommendedVariantOrderDiagnostics(state, document, settings)
5456
: []),
57+
...(only.includes(DiagnosticKind.UsedBlocklistedClass)
58+
? await getUsedBlocklistedClassDiagnostics(state, document, settings)
59+
: []),
5560
]
5661
: []
5762
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { TextDocument } from 'vscode-languageserver-textdocument'
2+
import type { State, Settings } from '../util/state'
3+
import { type UsedBlocklistedClassDiagnostic, DiagnosticKind } from './types'
4+
import { findClassListsInDocument, getClassNamesInClassList } from '../util/find'
5+
6+
export async function getUsedBlocklistedClassDiagnostics(
7+
state: State,
8+
document: TextDocument,
9+
settings: Settings,
10+
): Promise<UsedBlocklistedClassDiagnostic[]> {
11+
if (!state.v4) return []
12+
if (!state.blocklist?.length) return []
13+
14+
let severity = settings.tailwindCSS.lint.usedBlocklistedClass
15+
if (severity === 'ignore') return []
16+
17+
let blocklist = new Set(state.blocklist ?? [])
18+
let diagnostics: UsedBlocklistedClassDiagnostic[] = []
19+
20+
let classLists = await findClassListsInDocument(state, document)
21+
22+
for (let classList of classLists) {
23+
let classNames = getClassNamesInClassList(classList, [])
24+
25+
for (let className of classNames) {
26+
if (!blocklist.has(className.className)) continue
27+
28+
diagnostics.push({
29+
code: DiagnosticKind.UsedBlocklistedClass,
30+
range: className.range,
31+
severity:
32+
severity === 'error'
33+
? 1 /* DiagnosticSeverity.Error */
34+
: 2 /* DiagnosticSeverity.Warning */,
35+
message: `The class "${className.className}" will not be generated as it has been blocklisted`,
36+
})
37+
}
38+
}
39+
40+
return diagnostics
41+
}

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

+12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export enum DiagnosticKind {
1010
InvalidTailwindDirective = 'invalidTailwindDirective',
1111
InvalidSourceDirective = 'invalidSourceDirective',
1212
RecommendedVariantOrder = 'recommendedVariantOrder',
13+
UsedBlocklistedClass = 'usedBlocklistedClass',
1314
}
1415

1516
export type CssConflictDiagnostic = Diagnostic & {
@@ -100,6 +101,16 @@ export function isRecommendedVariantOrderDiagnostic(
100101
return diagnostic.code === DiagnosticKind.RecommendedVariantOrder
101102
}
102103

104+
export type UsedBlocklistedClassDiagnostic = Diagnostic & {
105+
code: DiagnosticKind.UsedBlocklistedClass
106+
}
107+
108+
export function isUsedBlocklistedClass(
109+
diagnostic: AugmentedDiagnostic,
110+
): diagnostic is UsedBlocklistedClassDiagnostic {
111+
return diagnostic.code === DiagnosticKind.UsedBlocklistedClass
112+
}
113+
103114
export type AugmentedDiagnostic =
104115
| CssConflictDiagnostic
105116
| InvalidApplyDiagnostic
@@ -109,3 +120,4 @@ export type AugmentedDiagnostic =
109120
| InvalidTailwindDirectiveDiagnostic
110121
| InvalidSourceDirectiveDiagnostic
111122
| RecommendedVariantOrderDiagnostic
123+
| UsedBlocklistedClassDiagnostic

packages/tailwindcss-language-service/src/util/state.ts

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export type TailwindCssSettings = {
6565
invalidTailwindDirective: DiagnosticSeveritySetting
6666
invalidSourceDirective: DiagnosticSeveritySetting
6767
recommendedVariantOrder: DiagnosticSeveritySetting
68+
usedBlocklistedClass: DiagnosticSeveritySetting
6869
}
6970
experimental: {
7071
classRegex: string[] | [string, string][]
@@ -203,6 +204,7 @@ export function getDefaultTailwindSettings(): Settings {
203204
invalidTailwindDirective: 'error',
204205
invalidSourceDirective: 'error',
205206
recommendedVariantOrder: 'warning',
207+
usedBlocklistedClass: 'warning',
206208
},
207209
showPixelEquivalents: true,
208210
includeLanguages: {},

packages/tailwindcss-language-service/src/util/v4/design-system.ts

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export interface DesignSystem {
4040

4141
// Optional because it did not exist in earlier v4 alpha versions
4242
resolveThemeValue?(path: string, forceInline?: boolean): string | undefined
43+
invalidCandidates?: Set<string>
4344
}
4445

4546
export interface DesignSystem {

packages/vscode-tailwindcss/CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Prerelease
44

5-
- Nothing yet!
5+
- Warn when using a blocklisted class in v4 ([#1310](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1310))
66

77
# 0.14.15
88

packages/vscode-tailwindcss/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ Class names on the same HTML element which apply the same CSS property or proper
172172

173173
Class variants not in the recommended order (applies in [JIT mode](https://tailwindcss.com/docs/just-in-time-mode) only). **Default: `warning`**
174174

175+
#### `tailwindCSS.lint.usedBlocklistedClass`
176+
177+
Usage of class names that have been blocklisted via `@source not inline(…)`. **Default: `warning`**
178+
175179
### `tailwindCSS.inspectPort`
176180

177181
Enable the Node.js inspector agent for the language server and listen on the specified port. **Default: `null`**

packages/vscode-tailwindcss/package.json

+11
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,17 @@
305305
"markdownDescription": "Class variants not in the recommended order (applies in [JIT mode](https://tailwindcss.com/docs/just-in-time-mode) only)",
306306
"scope": "language-overridable"
307307
},
308+
"tailwindCSS.lint.usedBlocklistedClass": {
309+
"type": "string",
310+
"enum": [
311+
"ignore",
312+
"warning",
313+
"error"
314+
],
315+
"default": "warning",
316+
"markdownDescription": "Usage of class names that have been blocklisted via `@source not inline(…)`",
317+
"scope": "language-overridable"
318+
},
308319
"tailwindCSS.experimental.classRegex": {
309320
"type": "array",
310321
"scope": "language-overridable"

0 commit comments

Comments
 (0)