Skip to content

Warn when using blocklisted classes #1310

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 6 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion packages/tailwindcss-language-server/src/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@ export async function createProjectService(
)

state.designSystem = designSystem
state.blocklist = Array.from(designSystem.invalidCandidates ?? [])

let deps = designSystem.dependencies()

Expand Down Expand Up @@ -982,7 +983,9 @@ export async function createProjectService(
if (typeof state.separator !== 'string') {
state.separator = ':'
}
state.blocklist = Array.isArray(state.config.blocklist) ? state.config.blocklist : []
if (!state.v4) {
state.blocklist = Array.isArray(state.config.blocklist) ? state.config.blocklist : []
}
delete state.config.blocklist

if (state.v4) {
Expand Down Expand Up @@ -1148,6 +1151,7 @@ export async function createProjectService(
state.designSystem = designSystem
state.classList = classList
state.variants = getVariants(state)
state.blocklist = Array.from(designSystem.invalidCandidates ?? [])

let deps = designSystem.dependencies()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as fs from 'node:fs/promises'
import { expect, test } from 'vitest'
import { withFixture } from '../common'
import * as fs from 'node:fs/promises'
import { css, defineTest } from '../../src/testing'
import { createClient } from '../utils/client'

withFixture('basic', (c) => {
function testFixture(fixture) {
Expand Down Expand Up @@ -383,3 +385,43 @@ withFixture('v4/basic', (c) => {
],
})
})

defineTest({
name: 'Shows warning when using blocklisted classes',
fs: {
'app.css': css`
@import 'tailwindcss';
@source not inline("{,hover:}flex");
`,
},
prepare: async ({ root }) => ({ client: await createClient({ root }) }),
handle: async ({ client }) => {
let doc = await client.open({
lang: 'html',
text: '<div class="flex underline hover:flex">',
})

let diagnostics = await doc.diagnostics()

expect(diagnostics).toEqual([
{
code: 'usedBlocklistedClass',
message: 'The class "flex" will not be generated as it has been blocklisted',
range: {
start: { line: 0, character: 12 },
end: { line: 0, character: 16 },
},
severity: 2,
},
{
code: 'usedBlocklistedClass',
message: 'The class "hover:flex" will not be generated as it has been blocklisted',
range: {
start: { line: 0, character: 27 },
end: { line: 0, character: 37 },
},
severity: 2,
},
])
},
})
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getInvalidConfigPathDiagnostics } from './getInvalidConfigPathDiagnosti
import { getInvalidTailwindDirectiveDiagnostics } from './getInvalidTailwindDirectiveDiagnostics'
import { getRecommendedVariantOrderDiagnostics } from './getRecommendedVariantOrderDiagnostics'
import { getInvalidSourceDiagnostics } from './getInvalidSourceDiagnostics'
import { getUsedBlocklistedClassDiagnostics } from './getUsedBlocklistedClassDiagnostics'

export async function doValidate(
state: State,
Expand All @@ -22,6 +23,7 @@ export async function doValidate(
DiagnosticKind.InvalidTailwindDirective,
DiagnosticKind.InvalidSourceDirective,
DiagnosticKind.RecommendedVariantOrder,
DiagnosticKind.UsedBlocklistedClass,
],
): Promise<AugmentedDiagnostic[]> {
const settings = await state.editor.getConfiguration(document.uri)
Expand Down Expand Up @@ -52,6 +54,9 @@ export async function doValidate(
...(only.includes(DiagnosticKind.RecommendedVariantOrder)
? await getRecommendedVariantOrderDiagnostics(state, document, settings)
: []),
...(only.includes(DiagnosticKind.UsedBlocklistedClass)
? await getUsedBlocklistedClassDiagnostics(state, document, settings)
: []),
]
: []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { TextDocument } from 'vscode-languageserver-textdocument'
import type { State, Settings } from '../util/state'
import { type UsedBlocklistedClassDiagnostic, DiagnosticKind } from './types'
import { findClassListsInDocument, getClassNamesInClassList } from '../util/find'

export async function getUsedBlocklistedClassDiagnostics(
state: State,
document: TextDocument,
settings: Settings,
): Promise<UsedBlocklistedClassDiagnostic[]> {
if (!state.v4) return []
if (!state.blocklist?.length) return []

let severity = settings.tailwindCSS.lint.usedBlocklistedClass
if (severity === 'ignore') return []

let blocklist = new Set(state.blocklist ?? [])
let diagnostics: UsedBlocklistedClassDiagnostic[] = []

let classLists = await findClassListsInDocument(state, document)

for (let classList of classLists) {
let classNames = getClassNamesInClassList(classList, [])

for (let className of classNames) {
if (!blocklist.has(className.className)) continue

diagnostics.push({
code: DiagnosticKind.UsedBlocklistedClass,
range: className.range,
severity:
severity === 'error'
? 1 /* DiagnosticSeverity.Error */
: 2 /* DiagnosticSeverity.Warning */,
message: `The class "${className.className}" will not be generated as it has been blocklisted`,
})
}
}

return diagnostics
}
12 changes: 12 additions & 0 deletions packages/tailwindcss-language-service/src/diagnostics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum DiagnosticKind {
InvalidTailwindDirective = 'invalidTailwindDirective',
InvalidSourceDirective = 'invalidSourceDirective',
RecommendedVariantOrder = 'recommendedVariantOrder',
UsedBlocklistedClass = 'usedBlocklistedClass',
}

export type CssConflictDiagnostic = Diagnostic & {
Expand Down Expand Up @@ -100,6 +101,16 @@ export function isRecommendedVariantOrderDiagnostic(
return diagnostic.code === DiagnosticKind.RecommendedVariantOrder
}

export type UsedBlocklistedClassDiagnostic = Diagnostic & {
code: DiagnosticKind.UsedBlocklistedClass
}

export function isUsedBlocklistedClass(
diagnostic: AugmentedDiagnostic,
): diagnostic is UsedBlocklistedClassDiagnostic {
return diagnostic.code === DiagnosticKind.UsedBlocklistedClass
}

export type AugmentedDiagnostic =
| CssConflictDiagnostic
| InvalidApplyDiagnostic
Expand All @@ -109,3 +120,4 @@ export type AugmentedDiagnostic =
| InvalidTailwindDirectiveDiagnostic
| InvalidSourceDirectiveDiagnostic
| RecommendedVariantOrderDiagnostic
| UsedBlocklistedClassDiagnostic
2 changes: 2 additions & 0 deletions packages/tailwindcss-language-service/src/util/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export type TailwindCssSettings = {
invalidTailwindDirective: DiagnosticSeveritySetting
invalidSourceDirective: DiagnosticSeveritySetting
recommendedVariantOrder: DiagnosticSeveritySetting
usedBlocklistedClass: DiagnosticSeveritySetting
}
experimental: {
classRegex: string[] | [string, string][]
Expand Down Expand Up @@ -203,6 +204,7 @@ export function getDefaultTailwindSettings(): Settings {
invalidTailwindDirective: 'error',
invalidSourceDirective: 'error',
recommendedVariantOrder: 'warning',
usedBlocklistedClass: 'warning',
},
showPixelEquivalents: true,
includeLanguages: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface DesignSystem {

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

export interface DesignSystem {
Expand Down
2 changes: 1 addition & 1 deletion packages/vscode-tailwindcss/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Prerelease

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

# 0.14.15

Expand Down
4 changes: 4 additions & 0 deletions packages/vscode-tailwindcss/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ Class names on the same HTML element which apply the same CSS property or proper

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

#### `tailwindCSS.lint.usedBlocklistedClass`

Usage of class names that have been blocklisted via `@source not inline(…)`. **Default: `warning`**

### `tailwindCSS.inspectPort`

Enable the Node.js inspector agent for the language server and listen on the specified port. **Default: `null`**
Expand Down
11 changes: 11 additions & 0 deletions packages/vscode-tailwindcss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,17 @@
"markdownDescription": "Class variants not in the recommended order (applies in [JIT mode](https://tailwindcss.com/docs/just-in-time-mode) only)",
"scope": "language-overridable"
},
"tailwindCSS.lint.usedBlocklistedClass": {
"type": "string",
"enum": [
"ignore",
"warning",
"error"
],
"default": "warning",
"markdownDescription": "Usage of class names that have been blocklisted via `@source not inline(…)`",
"scope": "language-overridable"
},
"tailwindCSS.experimental.classRegex": {
"type": "array",
"scope": "language-overridable"
Expand Down