From 7247d2e6f9ef4b19b1e05cf0a7ce0ba64a736ec5 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 8 Nov 2024 06:14:06 -0500 Subject: [PATCH] Add class deprecation diagnostics --- .../tailwindcss-language-server/src/config.ts | 1 + .../src/codeActions/codeActionProvider.ts | 4 +- .../provideSuggestionCodeActions.ts | 2 + .../src/diagnostics/diagnosticsProvider.ts | 5 ++ .../getDeprecatedClassDiagnostics.ts | 62 +++++++++++++++++++ .../src/diagnostics/types.ts | 14 +++++ .../src/util/state.ts | 2 + .../src/util/v4/design-system.ts | 4 ++ packages/vscode-tailwindcss/CHANGELOG.md | 1 + packages/vscode-tailwindcss/README.md | 4 ++ packages/vscode-tailwindcss/package.json | 11 ++++ 11 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 packages/tailwindcss-language-service/src/diagnostics/getDeprecatedClassDiagnostics.ts diff --git a/packages/tailwindcss-language-server/src/config.ts b/packages/tailwindcss-language-server/src/config.ts index 2e417fce..ee74975f 100644 --- a/packages/tailwindcss-language-server/src/config.ts +++ b/packages/tailwindcss-language-server/src/config.ts @@ -22,6 +22,7 @@ function getDefaultSettings(): Settings { rootFontSize: 16, lint: { cssConflict: 'warning', + deprecatedClass: 'warning', invalidApply: 'error', invalidScreen: 'error', invalidVariant: 'error', diff --git a/packages/tailwindcss-language-service/src/codeActions/codeActionProvider.ts b/packages/tailwindcss-language-service/src/codeActions/codeActionProvider.ts index 7ebf79cb..f7b55e70 100644 --- a/packages/tailwindcss-language-service/src/codeActions/codeActionProvider.ts +++ b/packages/tailwindcss-language-service/src/codeActions/codeActionProvider.ts @@ -13,6 +13,7 @@ import { isInvalidScreenDiagnostic, isInvalidVariantDiagnostic, isRecommendedVariantOrderDiagnostic, + isDeprecatedClassDiagnostic, } from '../diagnostics/types' import { flatten, dedupeBy } from '../util/array' import { provideCssConflictCodeActions } from './provideCssConflictCodeActions' @@ -74,7 +75,8 @@ export async function doCodeActions( isInvalidTailwindDirectiveDiagnostic(diagnostic) || isInvalidScreenDiagnostic(diagnostic) || isInvalidVariantDiagnostic(diagnostic) || - isRecommendedVariantOrderDiagnostic(diagnostic) + isRecommendedVariantOrderDiagnostic(diagnostic) || + isDeprecatedClassDiagnostic(diagnostic) ) { return provideSuggestionCodeActions(state, params, diagnostic) } diff --git a/packages/tailwindcss-language-service/src/codeActions/provideSuggestionCodeActions.ts b/packages/tailwindcss-language-service/src/codeActions/provideSuggestionCodeActions.ts index a0201699..c8aedde3 100644 --- a/packages/tailwindcss-language-service/src/codeActions/provideSuggestionCodeActions.ts +++ b/packages/tailwindcss-language-service/src/codeActions/provideSuggestionCodeActions.ts @@ -1,6 +1,7 @@ import type { State } from '../util/state' import type { CodeActionParams, CodeAction } from 'vscode-languageserver' import type { + DeprecatedClassDiagnostic, InvalidConfigPathDiagnostic, InvalidTailwindDirectiveDiagnostic, InvalidScreenDiagnostic, @@ -12,6 +13,7 @@ export function provideSuggestionCodeActions( _state: State, params: CodeActionParams, diagnostic: + | DeprecatedClassDiagnostic | InvalidConfigPathDiagnostic | InvalidTailwindDirectiveDiagnostic | InvalidScreenDiagnostic diff --git a/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts b/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts index 34c03b22..361ea485 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/diagnosticsProvider.ts @@ -8,11 +8,13 @@ import { getInvalidVariantDiagnostics } from './getInvalidVariantDiagnostics' import { getInvalidConfigPathDiagnostics } from './getInvalidConfigPathDiagnostics' import { getInvalidTailwindDirectiveDiagnostics } from './getInvalidTailwindDirectiveDiagnostics' import { getRecommendedVariantOrderDiagnostics } from './getRecommendedVariantOrderDiagnostics' +import { getDeprecatedClassDiagnostics } from './getDeprecatedClassDiagnostics' export async function doValidate( state: State, document: TextDocument, only: DiagnosticKind[] = [ + DiagnosticKind.Deprecation, DiagnosticKind.CssConflict, DiagnosticKind.InvalidApply, DiagnosticKind.InvalidScreen, @@ -26,6 +28,9 @@ export async function doValidate( return settings.tailwindCSS.validate ? [ + ...(only.includes(DiagnosticKind.Deprecation) + ? await getDeprecatedClassDiagnostics(state, document, settings) + : []), ...(only.includes(DiagnosticKind.CssConflict) ? await getCssConflictDiagnostics(state, document, settings) : []), diff --git a/packages/tailwindcss-language-service/src/diagnostics/getDeprecatedClassDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getDeprecatedClassDiagnostics.ts new file mode 100644 index 00000000..d753641e --- /dev/null +++ b/packages/tailwindcss-language-service/src/diagnostics/getDeprecatedClassDiagnostics.ts @@ -0,0 +1,62 @@ +import type { State, Settings } from '../util/state' +import { type DeprecatedClassDiagnostic, DiagnosticKind } from './types' +import { findClassListsInDocument, getClassNamesInClassList } from '../util/find' +import type { TextDocument } from 'vscode-languageserver-textdocument' + +export async function getDeprecatedClassDiagnostics( + state: State, + document: TextDocument, + settings: Settings, +): Promise { + // Only v4 projects can report deprecations + if (!state.v4) return [] + + // This is an earlier v4 version that does not support class deprecations + if (!state.designSystem.classMetadata) return [] + + let severity = settings.tailwindCSS.lint.deprecatedClass + if (severity === 'ignore') return [] + + // Fill in the list of statically known deprecated classes + let deprecations = new Map( + state.classList.map(([className, meta]) => [className, meta.deprecated ?? false]), + ) + + function isDeprecated(className: string) { + if (deprecations.has(className)) { + return deprecations.get(className) + } + + let metadata = state.designSystem.classMetadata([className])[0] + let deprecated = metadata?.deprecated ?? false + + deprecations.set(className, deprecated) + + return deprecated + } + + let diagnostics: DeprecatedClassDiagnostic[] = [] + let classLists = await findClassListsInDocument(state, document) + + for (let classList of classLists) { + let classNames = getClassNamesInClassList(classList, state.blocklist) + + for (let className of classNames) { + if (!isDeprecated(className.className)) continue + + diagnostics.push({ + code: DiagnosticKind.DeprecatedClass, + className, + range: className.range, + severity: + severity === 'error' + ? 1 /* DiagnosticSeverity.Error */ + : 2 /* DiagnosticSeverity.Warning */, + message: `'${className.className}' is deprecated.`, + suggestions: [], + }) + } + } + + return diagnostics +} diff --git a/packages/tailwindcss-language-service/src/diagnostics/types.ts b/packages/tailwindcss-language-service/src/diagnostics/types.ts index 115079a2..1fea2180 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/types.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/types.ts @@ -6,11 +6,24 @@ export enum DiagnosticKind { InvalidApply = 'invalidApply', InvalidScreen = 'invalidScreen', InvalidVariant = 'invalidVariant', + DeprecatedClass = 'deprecatedClass', InvalidConfigPath = 'invalidConfigPath', InvalidTailwindDirective = 'invalidTailwindDirective', RecommendedVariantOrder = 'recommendedVariantOrder', } +export type DeprecatedClassDiagnostic = Diagnostic & { + code: DiagnosticKind.DeprecatedClass + className: DocumentClassName + suggestions: string[] +} + +export function isDeprecatedClassDiagnostic( + diagnostic: AugmentedDiagnostic, +): diagnostic is DeprecatedClassDiagnostic { + return diagnostic.code === DiagnosticKind.DeprecatedClass +} + export type CssConflictDiagnostic = Diagnostic & { code: DiagnosticKind.CssConflict className: DocumentClassName @@ -90,6 +103,7 @@ export function isRecommendedVariantOrderDiagnostic( } export type AugmentedDiagnostic = + | DeprecatedClassDiagnostic | CssConflictDiagnostic | InvalidApplyDiagnostic | InvalidScreenDiagnostic diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts index 50452dc0..90e84e8c 100644 --- a/packages/tailwindcss-language-service/src/util/state.ts +++ b/packages/tailwindcss-language-service/src/util/state.ts @@ -54,6 +54,7 @@ export type TailwindCssSettings = { colorDecorators: boolean lint: { cssConflict: DiagnosticSeveritySetting + deprecatedClass: DiagnosticSeveritySetting invalidApply: DiagnosticSeveritySetting invalidScreen: DiagnosticSeveritySetting invalidVariant: DiagnosticSeveritySetting @@ -91,6 +92,7 @@ export interface Variant { export interface ClassMetadata { color: culori.Color | KeywordColor | null modifiers?: string[] + deprecated?: boolean } export interface State { diff --git a/packages/tailwindcss-language-service/src/util/v4/design-system.ts b/packages/tailwindcss-language-service/src/util/v4/design-system.ts index 748d8bb2..7f1e0374 100644 --- a/packages/tailwindcss-language-service/src/util/v4/design-system.ts +++ b/packages/tailwindcss-language-service/src/util/v4/design-system.ts @@ -10,6 +10,7 @@ export interface Theme { export interface ClassMetadata { modifiers: string[] + deprecated?: boolean } export type ClassEntry = [string, ClassMetadata] @@ -32,6 +33,9 @@ export interface DesignSystem { getClassOrder(classes: string[]): [string, bigint | null][] getClassList(): ClassEntry[] getVariants(): VariantEntry[] + + // Only later alpha releases have this method + classMetadata?(classes: string[]): (ClassMetadata | null)[] } export interface DesignSystem { diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index a401356d..84884e85 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix display of color swatches using new v4 oklch color palette ([#1073](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1073)) - Properly validate `theme(…)` function paths in v4 ([#1074](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1074)) - Show all potential class conflicts in v4 projects ([#1077](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1077)) +- Add support for detecting deprecated classes ([#1084](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1084)) ## 0.12.11 diff --git a/packages/vscode-tailwindcss/README.md b/packages/vscode-tailwindcss/README.md index a37a06d7..c476ef5c 100644 --- a/packages/vscode-tailwindcss/README.md +++ b/packages/vscode-tailwindcss/README.md @@ -148,6 +148,10 @@ Unknown or invalid path used with the [`theme` helper](https://tailwindcss.com/d Class names on the same HTML element which apply the same CSS property or properties. **Default: `warning`** +#### `tailwindCSS.lint.deprecatedClass` + +Use of a deprecated class. **Default: `warning`** + #### `tailwindCSS.lint.recommendedVariantOrder` Class variants not in the recommended order (applies in [JIT mode](https://tailwindcss.com/docs/just-in-time-mode) only). **Default: `warning`** diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json index 8c34a830..2f16948c 100644 --- a/packages/vscode-tailwindcss/package.json +++ b/packages/vscode-tailwindcss/package.json @@ -225,6 +225,17 @@ "markdownDescription": "Class names on the same HTML element which apply the same CSS property or properties", "scope": "language-overridable" }, + "tailwindCSS.lint.deprecatedClass": { + "type": "string", + "enum": [ + "ignore", + "warning", + "error" + ], + "default": "warning", + "markdownDescription": "Use of a deprecated utility class", + "scope": "language-overridable" + }, "tailwindCSS.lint.invalidApply": { "type": "string", "enum": [