diff --git a/.github/autocomplete.png b/.github/autocomplete.png deleted file mode 100644 index 7857187025e47936aa933cb2cfefc187ac4ef10d..0000000000000000000000000000000000000000 Binary files a/.github/autocomplete.png and /dev/null differ diff --git a/.github/banner.png b/.github/banner.png deleted file mode 100644 index 1f927163a27fffb33f9be3bb52928f69bf020242..0000000000000000000000000000000000000000 Binary files a/.github/banner.png and /dev/null differ diff --git a/.github/hover.png b/.github/hover.png deleted file mode 100644 index e46a8efaa5dfdd9aeb52facc08a3a7687b6634b3..0000000000000000000000000000000000000000 Binary files a/.github/hover.png and /dev/null differ diff --git a/.github/linting.png b/.github/linting.png deleted file mode 100644 index 0c118825152d9335690fc59702cb4b842663e652..0000000000000000000000000000000000000000 Binary files a/.github/linting.png and /dev/null differ diff --git a/.vscodeignore b/.vscodeignore index 591602a20e25e0f55f0c73215f8494ca8a21fd2a..23a60828e8f0b03542bc2d6c396023fd6f92343e 100755 --- a/.vscodeignore +++ b/.vscodeignore @@ -9,4 +9,4 @@ node_modules/** src/** tests/** .vscode/** -.vscode/** +**/*.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b3f32a981d6b114a3a3fb93fc044ce328c95b90..f8bf3f3ba96f9cb15680ba52eb982625599c7293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,5 @@ # Changelog -## 0.4.0 - -- Added linting and quick fixes for both CSS and markup -- Updated module resolution for compatibility with pnpm (#128) -- The extension now ignores the `purge` option when extracting class names (#131) -- Fixed hover offsets for class names which appear after interpolations - -## 0.3.1 - -- Fixed class attribute completions not showing when using the following Pug syntax (#125): - ``` - div(class="") - ``` -- Fixed hover previews not showing when using a computed class attribute in Vue templates -- Restore missing readme images -- Update settings descriptions to use markdown - ## 0.3.0 ### General diff --git a/README.md b/README.md index da341e65e5e2eb36d7069a87e9958b35a632318b..27cc6e8c480bc703fa2c9a987bd60aac0b73250a 100644 --- a/README.md +++ b/README.md @@ -1,129 +1,115 @@ - - -Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. - +**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** ## Installation +**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** **[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** +**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. +**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** ## Features +**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** ### Autocomplete +**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** Intelligent suggestions for class names, as well as [CSS functions and directives](https://tailwindcss.com/docs/functions-and-directives/). - +- a `tailwind.config.js` file to be [present in your project folder](https://github.com/bradlc/vscode-tailwindcss/blob/master/package.json#L38). You can create it with `npx tailwind init`. +In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. -### Linting +## Features - +In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. - +In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. - +In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. ## Installation - +In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. **[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** - - +In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. - - +In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. ## Features - - +In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. ### Autocomplete - +In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. Intelligent suggestions for class names, as well as [CSS functions and directives](https://tailwindcss.com/docs/functions-and-directives/). - +## Features - - +## Features +## Features -```json +- [Improves syntax highlighting when using `@apply` and config helpers](#improves-syntax-highlighting-when-using-apply-and-config-helpers) -Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. - +## Features ## Installation +## Features **[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** -In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. - +**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** ## Features -### Autocomplete - +#### CSS preview when hovering over class names -Intelligent suggestions for class names, as well as [CSS functions and directives](https://tailwindcss.com/docs/functions-and-directives/). - -Enable completions when using [Emmet](https://emmet.io/)-style syntax, for example `div.bg-red-500.uppercase`. **Default: `false`** +HTML hover preview -```json +#### Suggestions when using `@apply` and config helpers -Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. - "tailwindCSS.emmetCompletions": true +CSS autocompletion -## Features - ### Autocomplete -### `tailwindCSS.validate` +Before: -Enable linting. Rules can be configured individually using the `tailwindcss.lint` settings: +### Autocomplete -- `ignore`: disable lint rule entirely -- `warning`: rule violations will be considered "warnings," typically represented by a yellow underline -- `error`: rule violations will be considered "errors," typically represented by a red underline +### Autocomplete Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. -## Features -Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. ### Autocomplete +## Installation -Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. + Intelligent suggestions for class names, as well as [CSS functions and directives](https://tailwindcss.com/docs/functions-and-directives/). -## Installation + -## Installation + -## Installation + -## Installation Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. -## Installation ## Installation -## Installation **[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** -## Installation In order for the extension to activate you must have [`tailwindcss` installed](https://tailwindcss.com/docs/installation/#1-install-tailwind-via-npm) and a [Tailwind config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional) named `tailwind.config.js` or `tailwind.js` in your workspace. -## Installation ## Features -## Installation ### Autocomplete -## Troubleshooting +### `tailwindcss.emmetCompletions` -**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** +Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. -- Ensure that you have a Tailwind config file in your workspace and that this is named `tailwind.config.js` or `tailwind.js`. Check out the Tailwind documentation for details on [creating a config file](https://tailwindcss.com/docs/installation/#3-create-your-tailwind-config-file-optional). +```json -**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** +Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. -**[Install via the Visual Studio Code Marketplace →](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)** Tailwind CSS IntelliSense enhances the Tailwind development experience by providing Visual Studio Code users with advanced features such as autocomplete, syntax highlighting, and linting. + +} +``` diff --git a/img/css-highlighting-after.png b/img/css-highlighting-after.png new file mode 100755 index 0000000000000000000000000000000000000000..86a12fb8596e001e2c68d171733cb7950d66335c Binary files /dev/null and b/img/css-highlighting-after.png differ diff --git a/img/css-highlighting-before.png b/img/css-highlighting-before.png new file mode 100755 index 0000000000000000000000000000000000000000..2eeffdc19d7293136e52f9426eb66456d0c1abb4 Binary files /dev/null and b/img/css-highlighting-before.png differ diff --git a/img/css.gif b/img/css.gif new file mode 100755 index 0000000000000000000000000000000000000000..e81904e7863349207daf4e368aae489a2f9374d8 Binary files /dev/null and b/img/css.gif differ diff --git a/img/html-hover.gif b/img/html-hover.gif new file mode 100755 index 0000000000000000000000000000000000000000..94540b86a062f0274f9ae1d3d755898f749fed43 Binary files /dev/null and b/img/html-hover.gif differ diff --git a/img/html.gif b/img/html.gif new file mode 100755 index 0000000000000000000000000000000000000000..8ffc7ffa9962333d28c60136b1f7ad50edf09883 Binary files /dev/null and b/img/html.gif differ diff --git a/package-lock.json b/package-lock.json index 9184c0f6d4d60cb918f95cc528656182719d94a9..91c1aca81ac946c1fd36e4fd59904914710f8008 100755 --- a/package-lock.json +++ b/package-lock.json @@ -2027,13 +2027,6 @@ "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", "dev": true }, "@babel/helper-split-export-declaration": "^7.8.3", - "dependencies": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", - "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==", - "dev": true - }, - "@babel/helper-split-export-declaration": "^7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -6091,12 +6084,6 @@ "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", "dev": true, "optional": true - }, - "sift-string": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/sift-string/-/sift-string-0.0.2.tgz", - "integrity": "sha1-G7ArEhslu4sHRwQr+afh2s+PuJw=", - "dev": true }, "signal-exit": { "version": "3.0.3", diff --git a/package.json b/package.json index c209d4be4eee26ae6b6a3744e74f540a95d5624b..e234600cea927baa23013a2664e31a4b32f7e355 100755 --- a/package.json +++ b/package.json @@ -1,21 +1,23 @@ { "name": "vscode-tailwindcss", "displayName": "Tailwind CSS IntelliSense", +{ "description": "Intelligent Tailwind CSS tooling for VS Code", + "preview": true, "preview": true, "author": "Brad Cornes ", "license": "MIT", "version": "0.3.1", - "homepage": "https://github.com/tailwindcss/intellisense", + "homepage": "https://github.com/bradlc/vscode-tailwindcss", "bugs": { { -{ + "source.vue", "email": "hello@bradley.dev" }, "repository": { "type": "git", { - "author": "Brad Cornes ", + "source.svelte", }, "publisher": "bradlc", "keywords": [ @@ -30,11 +32,10 @@ "engines": { "vscode": "^1.33.0" }, "categories": [ - "Linters", "Other" ], "galleryBanner": { - "color": "#f9fafb" + "color": "#f1f5f8" }, "icon": "media/icon.png", "activationEvents": [ @@ -72,78 +73,6 @@ "type": "string" }, "default": {}, "markdownDescription": "Enable features in languages that are not supported by default. Add a mapping here between the new language and an already supported language.\n E.g.: `{\"plaintext\": \"html\"}`" - }, - "tailwindCSS.validate": { - "type": "boolean", - "default": true, - "markdownDescription": "Enable linting", - "scope": "language-overridable" - }, - "tailwindCSS.lint.cssConflict": { - "type": "string", - "enum": [ - "ignore", - "warning", - "error" - ], - "default": "warning", - "markdownDescription": "Class names on the same HTML element which apply the same CSS property or properties", - "scope": "language-overridable" - }, - "tailwindCSS.lint.invalidApply": { - "type": "string", - "enum": [ - "ignore", - "warning", - "error" - ], - "default": "error", - "markdownDescription": "Unsupported use of the [`@apply` directive](https://tailwindcss.com/docs/functions-and-directives/#apply)", - "scope": "language-overridable" - }, - "tailwindCSS.lint.invalidScreen": { - "type": "string", - "enum": [ - "ignore", - "warning", - "error" - ], - "default": "error", - "markdownDescription": "Unknown screen name used with the [`@screen` directive](https://tailwindcss.com/docs/functions-and-directives/#screen)", - "scope": "language-overridable" - }, - "tailwindCSS.lint.invalidVariant": { - "type": "string", - "enum": [ - "ignore", - "warning", - "error" - ], - "default": "error", - "markdownDescription": "Unknown variant name used with the [`@variants` directive](https://tailwindcss.com/docs/functions-and-directives/#variants)", - "scope": "language-overridable" - }, - "tailwindCSS.lint.invalidConfigPath": { - "type": "string", - "enum": [ - "ignore", - "warning", - "error" - ], - "default": "error", - "markdownDescription": "Unknown or invalid path used with the [`theme` helper](https://tailwindcss.com/docs/functions-and-directives/#theme)", - "scope": "language-overridable" - }, - "tailwindCSS.lint.invalidTailwindDirective": { - "type": "string", - "enum": [ - "ignore", - "warning", - "error" - ], - "default": "error", - "markdownDescription": "Unknown value used with the [`@tailwind` directive](https://tailwindcss.com/docs/functions-and-directives/#tailwind)", - "scope": "language-overridable" } } } @@ -169,8 +98,6 @@ "chokidar": "^3.3.1", "concurrently": "^5.1.0", "css.escape": "^1.5.1", "url": "https://github.com/tailwindcss/intellisense/issues", - "displayName": "Tailwind CSS IntelliSense", - "url": "https://github.com/tailwindcss/intellisense/issues", "description": "Intelligent Tailwind CSS tooling for VS Code", "dset": "^2.0.1", "esm": "^3.2.25", @@ -189,7 +116,6 @@ "postcss-selector-parser": "^6.0.2", "resolve-from": "^5.0.0", "rimraf": "^3.0.2", "semver": "^7.3.2", - "sift-string": "0.0.2", "stack-trace": "0.0.10", "terser": "^4.6.12", "tiny-invariant": "^1.1.0", diff --git a/src/class-names/index.js b/src/class-names/index.js index 0ddd81c9b9ed00ff1a357129203ce973b23911c1..ca8ddec1e8696085fcb79738f7bdb42fe08beb72 100644 --- a/src/class-names/index.js +++ b/src/class-names/index.js @@ -141,10 +141,6 @@ resolvedConfig, postcss, browserslist, }), - modules: { - tailwindcss, - postcss, - }, } } diff --git a/src/extension.ts b/src/extension.ts index 279ec969590ba1e30120474c179722fa450c238d..be1fe479cac88456c5893e28a533d1c3c2ec81ca 100755 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,8 +12,6 @@ OutputChannel, WorkspaceFolder, Uri, /* -------------------------------------------------------------------------------------------- - * ------------------------------------------------------------------------------------------ */ -/* -------------------------------------------------------------------------------------------- import * as path from 'path' import { LanguageClient, @@ -25,7 +23,6 @@ import { DEFAULT_LANGUAGES } from './lib/languages' import isObject from './util/isObject' import { dedupe, equal } from './util/array' import { createEmitter } from './lib/emitter' -import { onMessage } from './lsp/notifications' const CLIENT_ID = 'tailwindcss-intellisense' const CLIENT_NAME = 'Tailwind CSS IntelliSense' @@ -154,9 +151,6 @@ client.onReady().then(() => { let emitter = createEmitter(client) registerConfigErrorHandler(emitter) - onMessage(client, 'getConfiguration', async (scope) => { - return Workspace.getConfiguration('tailwindCSS', scope) - }) }) client.start() diff --git a/src/lib/emitter.ts b/src/lib/emitter.ts index 3ad6f05f010872359a4d0a2ef289456f37d74490..d177c2cf355a60b5c265fb5df12b01e0feb0ffb6 100644 --- a/src/lib/emitter.ts +++ b/src/lib/emitter.ts @@ -1,7 +1,6 @@ import mitt from 'mitt' import { LanguageClient } from 'vscode-languageclient' import crypto from 'crypto' -import { Connection } from 'vscode-languageserver' export interface NotificationEmitter { on: (name: string, handler: (args: any) => void) => void @@ -9,9 +8,7 @@ off: (name: string, handler: (args: any) => void) => void emit: (name: string, args: any) => Promise } -export function createEmitter( - client: LanguageClient | Connection -): NotificationEmitter { +export function createEmitter(client: LanguageClient): NotificationEmitter { const emitter = mitt() const registered: string[] = [] @@ -29,7 +26,7 @@ const off = (name: string, handler: (args: any) => void) => { emitter.off(name, handler) } - const emit = (name: string, params: Record = {}) => { + const emit = (name: string, params: any) => { return new Promise((resolve, _reject) => { const id = crypto.randomBytes(16).toString('hex') on(`${name}Response`, (result) => { diff --git a/src/lsp/notifications.ts b/src/lsp/notifications.ts index e8f4ab856ac0c1a8e4809e78761fb53640488742..bb4e60dc496922fe1221147ff41a690e5f60d344 100644 --- a/src/lsp/notifications.ts +++ b/src/lsp/notifications.ts @@ -1,10 +1,9 @@ import { Connection } from 'vscode-languageserver' -import { LanguageClient } from 'vscode-languageclient' export function onMessage( - connection: LanguageClient | Connection, + connection: Connection, name: string, - handler: (params: any) => Thenable> + handler: (params: any) => any ): void { connection.onNotification(`tailwindcss/${name}`, async (params: any) => { const { _id, ...rest } = params diff --git a/src/lsp/providers/codeActions/codeActionProvider.ts b/src/lsp/providers/codeActions/codeActionProvider.ts deleted file mode 100644 index 19420f68ca37f549bac335634c81d8b5d3119af1..0000000000000000000000000000000000000000 --- a/src/lsp/providers/codeActions/codeActionProvider.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { CodeAction, CodeActionParams } from 'vscode-languageserver' -import { State } from '../../util/state' -import { getDiagnostics } from '../diagnostics/diagnosticsProvider' -import { rangesEqual } from '../../util/rangesEqual' -import { - DiagnosticKind, - isInvalidApplyDiagnostic, - AugmentedDiagnostic, - isCssConflictDiagnostic, - isInvalidConfigPathDiagnostic, - isInvalidTailwindDirectiveDiagnostic, - isInvalidScreenDiagnostic, - isInvalidVariantDiagnostic, -} from '../diagnostics/types' -import { flatten, dedupeBy } from '../../../util/array' -import { provideCssConflictCodeActions } from './provideCssConflictCodeActions' -import { provideInvalidApplyCodeActions } from './provideInvalidApplyCodeActions' -import { provideSuggestionCodeActions } from './provideSuggestionCodeActions' - -async function getDiagnosticsFromCodeActionParams( - state: State, - params: CodeActionParams, - only?: DiagnosticKind[] -): Promise { - let document = state.editor.documents.get(params.textDocument.uri) - let diagnostics = await getDiagnostics(state, document, only) - - return params.context.diagnostics - .map((diagnostic) => { - return diagnostics.find((d) => { - return ( - d.code === diagnostic.code && - d.message === diagnostic.message && - rangesEqual(d.range, diagnostic.range) - ) - }) - }) - .filter(Boolean) -} - -export async function provideCodeActions( - state: State, - params: CodeActionParams -): Promise { - let diagnostics = await getDiagnosticsFromCodeActionParams( - state, - params, - params.context.diagnostics - .map((diagnostic) => diagnostic.code) - .filter(Boolean) as DiagnosticKind[] - ) - - return Promise.all( - diagnostics.map((diagnostic) => { - if (isInvalidApplyDiagnostic(diagnostic)) { - return provideInvalidApplyCodeActions(state, params, diagnostic) - } - - if (isCssConflictDiagnostic(diagnostic)) { - return provideCssConflictCodeActions(state, params, diagnostic) - } - - if ( - isInvalidConfigPathDiagnostic(diagnostic) || - isInvalidTailwindDirectiveDiagnostic(diagnostic) || - isInvalidScreenDiagnostic(diagnostic) || - isInvalidVariantDiagnostic(diagnostic) - ) { - return provideSuggestionCodeActions(state, params, diagnostic) - } - - return [] - }) - ) - .then(flatten) - .then((x) => dedupeBy(x, (item) => JSON.stringify(item.edit))) -} diff --git a/src/lsp/providers/codeActions/provideCssConflictCodeActions.ts b/src/lsp/providers/codeActions/provideCssConflictCodeActions.ts deleted file mode 100644 index f6a6edb159000f7e53aae3c88e6b7fef94d08b70..0000000000000000000000000000000000000000 --- a/src/lsp/providers/codeActions/provideCssConflictCodeActions.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { State } from '../../util/state' -import { - CodeActionParams, - CodeAction, - CodeActionKind, -} from 'vscode-languageserver' -import { CssConflictDiagnostic } from '../diagnostics/types' -import { joinWithAnd } from '../../util/joinWithAnd' -import { removeRangesFromString } from '../../util/removeRangesFromString' - -export async function provideCssConflictCodeActions( - _state: State, - params: CodeActionParams, - diagnostic: CssConflictDiagnostic -): Promise { - return [ - { - title: `Delete ${joinWithAnd( - diagnostic.otherClassNames.map( - (otherClassName) => `'${otherClassName.className}'` - ) - )}`, - kind: CodeActionKind.QuickFix, - diagnostics: [diagnostic], - edit: { - changes: { - [params.textDocument.uri]: [ - { - range: diagnostic.className.classList.range, - newText: removeRangesFromString( - diagnostic.className.classList.classList, - diagnostic.otherClassNames.map( - (otherClassName) => otherClassName.relativeRange - ) - ), - }, - ], - }, - }, - }, - ] -} diff --git a/src/lsp/providers/codeActions/provideInvalidApplyCodeActions.ts b/src/lsp/providers/codeActions/provideInvalidApplyCodeActions.ts deleted file mode 100644 index ec3d483b6234cb1508465dfbdaabf9154173ff2a..0000000000000000000000000000000000000000 --- a/src/lsp/providers/codeActions/provideInvalidApplyCodeActions.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { - CodeAction, - CodeActionParams, - CodeActionKind, - TextEdit, - Range, -} from 'vscode-languageserver' -import { State } from '../../util/state' -import { InvalidApplyDiagnostic } from '../diagnostics/types' -import { getClassNameParts } from '../../util/getClassNameAtPosition' -import { getLanguageBoundaries } from '../../util/getLanguageBoundaries' -import { isCssDoc } from '../../util/css' -import { isWithinRange } from '../../util/isWithinRange' -const dlv = require('dlv') -import type { Root, NodeSource } from 'postcss' -import { absoluteRange } from '../../util/absoluteRange' -import { removeRangesFromString } from '../../util/removeRangesFromString' -import detectIndent from 'detect-indent' -import isObject from '../../../util/isObject' -import { cssObjToAst } from '../../util/cssObjToAst' -import dset from 'dset' -import selectorParser from 'postcss-selector-parser' -import { flatten } from '../../../util/array' -import { getClassNameMeta } from '../../util/getClassNameMeta' -import { validateApply } from '../../util/validateApply' - -export async function provideInvalidApplyCodeActions( - state: State, - params: CodeActionParams, - diagnostic: InvalidApplyDiagnostic -): Promise { - let document = state.editor.documents.get(params.textDocument.uri) - let documentText = document.getText() - let cssRange: Range - let cssText = documentText - const { postcss } = state.modules - let changes: TextEdit[] = [] - - let totalClassNamesInClassList = diagnostic.className.classList.classList.split( - /\s+/ - ).length - - let className = diagnostic.className.className - let classNameParts = getClassNameParts(state, className) - let classNameInfo = dlv(state.classNames.classNames, classNameParts) - - if (Array.isArray(classNameInfo)) { - return [] - } - - if (!isCssDoc(state, document)) { - let languageBoundaries = getLanguageBoundaries(state, document) - if (!languageBoundaries) return [] - cssRange = languageBoundaries.css.find((range) => - isWithinRange(diagnostic.range.start, range) - ) - if (!cssRange) return [] - cssText = document.getText(cssRange) - } - - try { - await postcss([ - postcss.plugin('', (_options = {}) => { - return (root: Root) => { - root.walkRules((rule) => { - if (changes.length) return false - - rule.walkAtRules('apply', (atRule) => { - let atRuleRange = postcssSourceToRange(atRule.source) - if (cssRange) { - atRuleRange = absoluteRange(atRuleRange, cssRange) - } - - if (!isWithinRange(diagnostic.range.start, atRuleRange)) - return true - - let ast = classNameToAst( - state, - classNameParts, - rule.selector, - diagnostic.className.classList.important - ) - - if (!ast) return false - - rule.after(ast.nodes) - let insertedRule = rule.next() - if (!insertedRule) return false - - if (totalClassNamesInClassList === 1) { - atRule.remove() - } else { - changes.push({ - range: diagnostic.className.classList.range, - newText: removeRangesFromString( - diagnostic.className.classList.classList, - diagnostic.className.relativeRange - ), - }) - } - - let ruleRange = postcssSourceToRange(rule.source) - if (cssRange) { - ruleRange = absoluteRange(ruleRange, cssRange) - } - - let outputIndent: string - let documentIndent = detectIndent(cssText) - - changes.push({ - range: ruleRange, - newText: - rule.toString() + - (insertedRule.raws.before || '\n\n') + - insertedRule - .toString() - .replace(/\n\s*\n/g, '\n') - .replace(/(@apply [^;\n]+)$/gm, '$1;') - .replace(/([^\s^]){$/gm, '$1 {') - .replace(/^\s+/gm, (m: string) => { - if (typeof outputIndent === 'undefined') outputIndent = m - return m.replace( - new RegExp(outputIndent, 'g'), - documentIndent.indent - ) - }) - .replace(/^(\s+)(.*?[^{}]\n)([^\s}])/gm, '$1$2$1$3'), - }) - - return false - }) - }) - } - }), - ]).process(cssText, { from: undefined }) - } catch (_) { - return [] - } - - if (!changes.length) { - return [] - } - - return [ - { - title: 'Extract to new rule', - kind: CodeActionKind.QuickFix, - diagnostics: [diagnostic], - edit: { - changes: { - [params.textDocument.uri]: changes, - }, - }, - }, - ] -} - -function postcssSourceToRange(source: NodeSource): Range { - return { - start: { - line: source.start.line - 1, - character: source.start.column - 1, - }, - end: { - line: source.end.line - 1, - character: source.end.column, - }, - } -} - -function classNameToAst( - state: State, - classNameParts: string[], - selector: string, - important: boolean = false -) { - const baseClassName = classNameParts[classNameParts.length - 1] - const validatedBaseClassName = validateApply(state, [baseClassName]) - if ( - validatedBaseClassName === null || - validatedBaseClassName.isApplyable === false - ) { - return null - } - const meta = getClassNameMeta(state, classNameParts) - if (Array.isArray(meta)) return null - let context = meta.context - let pseudo = meta.pseudo - const globalContexts = state.classNames.context - let screens = dlv( - state.config, - 'theme.screens', - dlv(state.config, 'screens', {}) - ) - if (!isObject(screens)) screens = {} - screens = Object.keys(screens) - const path = [] - - for (let i = 0; i < classNameParts.length - 1; i++) { - let part = classNameParts[i] - let common = globalContexts[part] - if (!common) return null - if (screens.includes(part)) { - path.push(`@screen ${part}`) - context = context.filter((con) => !common.includes(con)) - } - } - - path.push(...context) - - let obj = {} - for (let i = 1; i <= path.length; i++) { - dset(obj, path.slice(0, i), {}) - } - - selector = appendPseudosToSelector(selector, pseudo) - if (selector === null) return null - - let rule = { - [selector]: { - [`@apply ${baseClassName}${important ? ' !important' : ''}`]: '', - }, - } - if (path.length) { - dset(obj, path, rule) - } else { - obj = rule - } - - return cssObjToAst(obj, state.modules.postcss) -} - -function appendPseudosToSelector( - selector: string, - pseudos: string[] -): string | null { - if (pseudos.length === 0) return selector - - let canTransform = true - - let transformedSelector = selectorParser((selectors) => { - flatten(selectors.split((_) => true)).forEach((sel) => { - // @ts-ignore - for (let i = sel.nodes.length - 1; i >= 0; i--) { - // @ts-ignore - if (sel.nodes[i].type !== 'pseudo') { - break - // @ts-ignore - } else if (pseudos.includes(sel.nodes[i].value)) { - canTransform = false - break - } - } - if (canTransform) { - pseudos.forEach((p) => { - // @ts-ignore - sel.append(selectorParser.pseudo({ value: p })) - }) - } - }) - }).processSync(selector) - - if (!canTransform) return null - - return transformedSelector -} diff --git a/src/lsp/providers/codeActions/provideSuggestionCodeActions.ts b/src/lsp/providers/codeActions/provideSuggestionCodeActions.ts deleted file mode 100644 index 9da5fcb58f4863d4f6edc2643319263f95c22aa6..0000000000000000000000000000000000000000 --- a/src/lsp/providers/codeActions/provideSuggestionCodeActions.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { State } from '../../util/state' -import { - CodeActionParams, - CodeAction, - CodeActionKind, -} from 'vscode-languageserver' -import { - InvalidConfigPathDiagnostic, - InvalidTailwindDirectiveDiagnostic, - InvalidScreenDiagnostic, - InvalidVariantDiagnostic, -} from '../diagnostics/types' - -export function provideSuggestionCodeActions( - _state: State, - params: CodeActionParams, - diagnostic: - | InvalidConfigPathDiagnostic - | InvalidTailwindDirectiveDiagnostic - | InvalidScreenDiagnostic - | InvalidVariantDiagnostic -): CodeAction[] { - return diagnostic.suggestions.map((suggestion) => ({ - title: `Replace with '${suggestion}'`, - kind: CodeActionKind.QuickFix, - diagnostics: [diagnostic], - edit: { - changes: { - [params.textDocument.uri]: [ - { - range: diagnostic.range, - newText: suggestion, - }, - ], - }, - }, - })) -} diff --git a/src/lsp/providers/completionProvider.ts b/src/lsp/providers/completionProvider.ts index ec93d4449fe796128d72d2e9397e2cbd82ee828b..6dbb1e7a8de391c327c00dee9b74fb0b1fe08c87 100644 --- a/src/lsp/providers/completionProvider.ts +++ b/src/lsp/providers/completionProvider.ts @@ -618,14 +618,13 @@ async function provideEmmetCompletions( state: State, { position, textDocument }: CompletionParams ): Promise { -const dlv = require('dlv') + let subsetKey: string[] = [] } from 'vscode-languageserver' CompletionItem, - CompletionItem, CompletionItemKind, - CompletionItem, + CompletionItemKind, CompletionItem, - let replacementRange = { + let doc = state.editor.documents.get(textDocument.uri) const syntax = isHtmlContext(state, doc, position) ? 'html' diff --git a/src/lsp/providers/diagnostics/diagnosticsProvider.ts b/src/lsp/providers/diagnostics/diagnosticsProvider.ts deleted file mode 100644 index a22f8460e8c1eef13c3db52052b37d051a58797a..0000000000000000000000000000000000000000 --- a/src/lsp/providers/diagnostics/diagnosticsProvider.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { TextDocument } from 'vscode-languageserver' -import { State } from '../../util/state' -import { getDocumentSettings } from '../../util/getDocumentSettings' -import { DiagnosticKind, AugmentedDiagnostic } from './types' -import { getCssConflictDiagnostics } from './getCssConflictDiagnostics' -import { getInvalidApplyDiagnostics } from './getInvalidApplyDiagnostics' -import { getInvalidScreenDiagnostics } from './getInvalidScreenDiagnostics' -import { getInvalidVariantDiagnostics } from './getInvalidVariantDiagnostics' -import { getInvalidConfigPathDiagnostics } from './getInvalidConfigPathDiagnostics' -import { getInvalidTailwindDirectiveDiagnostics } from './getInvalidTailwindDirectiveDiagnostics' - -export async function getDiagnostics( - state: State, - document: TextDocument, - only: DiagnosticKind[] = [ - DiagnosticKind.CssConflict, - DiagnosticKind.InvalidApply, - DiagnosticKind.InvalidScreen, - DiagnosticKind.InvalidVariant, - DiagnosticKind.InvalidConfigPath, - DiagnosticKind.InvalidTailwindDirective, - ] -): Promise { - const settings = await getDocumentSettings(state, document) - - return settings.validate - ? [ - ...(only.includes(DiagnosticKind.CssConflict) - ? getCssConflictDiagnostics(state, document, settings) - : []), - ...(only.includes(DiagnosticKind.InvalidApply) - ? getInvalidApplyDiagnostics(state, document, settings) - : []), - ...(only.includes(DiagnosticKind.InvalidScreen) - ? getInvalidScreenDiagnostics(state, document, settings) - : []), - ...(only.includes(DiagnosticKind.InvalidVariant) - ? getInvalidVariantDiagnostics(state, document, settings) - : []), - ...(only.includes(DiagnosticKind.InvalidConfigPath) - ? getInvalidConfigPathDiagnostics(state, document, settings) - : []), - ...(only.includes(DiagnosticKind.InvalidTailwindDirective) - ? getInvalidTailwindDirectiveDiagnostics(state, document, settings) - : []), - ] - : [] -} - -export async function provideDiagnostics(state: State, document: TextDocument) { - state.editor.connection.sendDiagnostics({ - uri: document.uri, - diagnostics: await getDiagnostics(state, document), - }) -} - -export function clearDiagnostics(state: State, document: TextDocument): void { - state.editor.connection.sendDiagnostics({ - uri: document.uri, - diagnostics: [], - }) -} - -export function clearAllDiagnostics(state: State): void { - state.editor.documents.all().forEach((document) => { - clearDiagnostics(state, document) - }) -} - -export function updateAllDiagnostics(state: State): void { - state.editor.documents.all().forEach((document) => { - provideDiagnostics(state, document) - }) -} diff --git a/src/lsp/providers/diagnostics/getCssConflictDiagnostics.ts b/src/lsp/providers/diagnostics/getCssConflictDiagnostics.ts deleted file mode 100644 index 73da4869da22efd201be30ab434b9f61a0a5b3b6..0000000000000000000000000000000000000000 --- a/src/lsp/providers/diagnostics/getCssConflictDiagnostics.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { joinWithAnd } from '../../util/joinWithAnd' -import { State, Settings } from '../../util/state' -import { TextDocument, DiagnosticSeverity } from 'vscode-languageserver' -import { CssConflictDiagnostic, DiagnosticKind } from './types' -import { - findClassListsInDocument, - getClassNamesInClassList, -} from '../../util/find' -import { getClassNameDecls } from '../../util/getClassNameDecls' -import { getClassNameMeta } from '../../util/getClassNameMeta' -import { equal } from '../../../util/array' - -export function getCssConflictDiagnostics( - state: State, - document: TextDocument, - settings: Settings -): CssConflictDiagnostic[] { - let severity = settings.lint.cssConflict - if (severity === 'ignore') return [] - - let diagnostics: CssConflictDiagnostic[] = [] - const classLists = findClassListsInDocument(state, document) - - classLists.forEach((classList) => { - const classNames = getClassNamesInClassList(classList) - - classNames.forEach((className, index) => { - let decls = getClassNameDecls(state, className.className) - if (!decls) return - - let properties = Object.keys(decls) - let meta = getClassNameMeta(state, className.className) - - let otherClassNames = classNames.filter((_className, i) => i !== index) - - let conflictingClassNames = otherClassNames.filter((otherClassName) => { - let otherDecls = getClassNameDecls(state, otherClassName.className) - if (!otherDecls) return false - - let otherMeta = getClassNameMeta(state, otherClassName.className) - - return ( - equal(properties, Object.keys(otherDecls)) && - !Array.isArray(meta) && - !Array.isArray(otherMeta) && - equal(meta.context, otherMeta.context) && - equal(meta.pseudo, otherMeta.pseudo) - ) - }) - - if (conflictingClassNames.length === 0) return - - diagnostics.push({ - code: DiagnosticKind.CssConflict, - className, - otherClassNames: conflictingClassNames, - range: className.range, - severity: - severity === 'error' - ? DiagnosticSeverity.Error - : DiagnosticSeverity.Warning, - message: `'${className.className}' applies the same CSS ${ - properties.length === 1 ? 'property' : 'properties' - } as ${joinWithAnd( - conflictingClassNames.map( - (conflictingClassName) => `'${conflictingClassName.className}'` - ) - )}.`, - relatedInformation: conflictingClassNames.map( - (conflictingClassName) => { - return { - message: conflictingClassName.className, - location: { - uri: document.uri, - range: conflictingClassName.range, - }, - } - } - ), - }) - }) - }) - - return diagnostics -} diff --git a/src/lsp/providers/diagnostics/getInvalidApplyDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidApplyDiagnostics.ts deleted file mode 100644 index e829b5735e74900f08d5d7161c5d561eebd7b89f..0000000000000000000000000000000000000000 --- a/src/lsp/providers/diagnostics/getInvalidApplyDiagnostics.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { findClassNamesInRange } from '../../util/find' -import { InvalidApplyDiagnostic, DiagnosticKind } from './types' -import { Settings, State } from '../../util/state' -import { TextDocument, DiagnosticSeverity } from 'vscode-languageserver' -import { validateApply } from '../../util/validateApply' - -export function getInvalidApplyDiagnostics( - state: State, - document: TextDocument, - settings: Settings -): InvalidApplyDiagnostic[] { - let severity = settings.lint.invalidApply - if (severity === 'ignore') return [] - - const classNames = findClassNamesInRange(document, undefined, 'css') - - let diagnostics: InvalidApplyDiagnostic[] = classNames.map((className) => { - let result = validateApply(state, className.className) - - if (result === null || result.isApplyable === true) { - return null - } - - return { - code: DiagnosticKind.InvalidApply, - severity: - severity === 'error' - ? DiagnosticSeverity.Error - : DiagnosticSeverity.Warning, - range: className.range, - message: result.reason, - className, - } - }) - - return diagnostics.filter(Boolean) -} diff --git a/src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts deleted file mode 100644 index ccf52fcb1aa3b88bd1b73aa9963e0e7729b29cca..0000000000000000000000000000000000000000 --- a/src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { State, Settings } from '../../util/state' -import { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver' -import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types' -import { isCssDoc } from '../../util/css' -import { getLanguageBoundaries } from '../../util/getLanguageBoundaries' -import { findAll, indexToPosition } from '../../util/find' -import { stringToPath } from '../../util/stringToPath' -import isObject from '../../../util/isObject' -import { closest } from '../../util/closest' -import { absoluteRange } from '../../util/absoluteRange' -import { combinations } from '../../util/combinations' -const dlv = require('dlv') - -function pathToString(path: string | string[]): string { - if (typeof path === 'string') return path - return path.reduce((acc, cur, i) => { - if (i === 0) return cur - if (cur.includes('.')) return `${acc}[${cur}]` - return `${acc}.${cur}` - }, '') -} - -function validateConfigPath( - state: State, - path: string | string[], - base: string[] = [] -): - | { isValid: true; value: any } - | { isValid: false; reason: string; suggestions: string[] } { - let keys = Array.isArray(path) ? path : stringToPath(path) - let value = dlv(state.config, [...base, ...keys]) - let suggestions: string[] = [] - - function findAlternativePath(): string[] { - let points = combinations('123456789'.substr(0, keys.length - 1)).map((x) => - x.split('').map((x) => parseInt(x, 10)) - ) - - let possibilities: string[][] = points - .map((p) => { - let result = [] - let i = 0 - p.forEach((x) => { - result.push(keys.slice(i, x).join('.')) - i = x - }) - result.push(keys.slice(i).join('.')) - return result - }) - .slice(1) // skip original path - - return possibilities.find( - (possibility) => validateConfigPath(state, possibility, base).isValid - ) - } - - if (typeof value === 'undefined') { - let reason = `'${pathToString(path)}' does not exist in your theme config.` - let parentPath = [...base, ...keys.slice(0, keys.length - 1)] - let parentValue = dlv(state.config, parentPath) - - if (isObject(parentValue)) { - let closestValidKey = closest( - keys[keys.length - 1], - Object.keys(parentValue).filter( - (key) => validateConfigPath(state, [...parentPath, key]).isValid - ) - ) - if (closestValidKey) { - suggestions.push( - pathToString([...keys.slice(0, keys.length - 1), closestValidKey]) - ) - reason += ` Did you mean '${suggestions[0]}'?` - } - } else { - let altPath = findAlternativePath() - if (altPath) { - return { - isValid: false, - reason: `${reason} Did you mean '${pathToString(altPath)}'?`, - suggestions: [pathToString(altPath)], - } - } - } - - return { - isValid: false, - reason, - suggestions, - } - } - - if ( - !( - typeof value === 'string' || - typeof value === 'number' || - value instanceof String || - value instanceof Number || - Array.isArray(value) - ) - ) { - let reason = `'${pathToString( - path - )}' was found but does not resolve to a string.` - - if (isObject(value)) { - let validKeys = Object.keys(value).filter( - (key) => validateConfigPath(state, [...keys, key], base).isValid - ) - if (validKeys.length) { - suggestions.push( - ...validKeys.map((validKey) => pathToString([...keys, validKey])) - ) - reason += ` Did you mean something like '${suggestions[0]}'?` - } - } - return { - isValid: false, - reason, - suggestions, - } - } - - // The value resolves successfully, but we need to check that there - // wasn't any funny business. If you have a theme object: - // { msg: 'hello' } and do theme('msg.0') - // this will resolve to 'h', which is probably not intentional, so we - // check that all of the keys are object or array keys (i.e. not string - // indexes) - let isValid = true - for (let i = keys.length - 1; i >= 0; i--) { - let key = keys[i] - let parentValue = dlv(state.config, [...base, ...keys.slice(0, i)]) - if (/^[0-9]+$/.test(key)) { - if (!isObject(parentValue) && !Array.isArray(parentValue)) { - isValid = false - break - } - } else if (!isObject(parentValue)) { - isValid = false - break - } - } - if (!isValid) { - let reason = `'${pathToString(path)}' does not exist in your theme config.` - - let altPath = findAlternativePath() - if (altPath) { - return { - isValid: false, - reason: `${reason} Did you mean '${pathToString(altPath)}'?`, - suggestions: [pathToString(altPath)], - } - } - - return { - isValid: false, - reason, - suggestions: [], - } - } - - return { - isValid: true, - value, - } -} - -export function getInvalidConfigPathDiagnostics( - state: State, - document: TextDocument, - settings: Settings -): InvalidConfigPathDiagnostic[] { - let severity = settings.lint.invalidConfigPath - if (severity === 'ignore') return [] - - let diagnostics: InvalidConfigPathDiagnostic[] = [] - let ranges: Range[] = [] - - if (isCssDoc(state, document)) { - ranges.push(undefined) - } else { - let boundaries = getLanguageBoundaries(state, document) - if (!boundaries) return [] - ranges.push(...boundaries.css) - } - - ranges.forEach((range) => { - let text = document.getText(range) - let matches = findAll( - /(?\s|^)(?config|theme)\((?['"])(?[^)]+)\k\)/g, - text - ) - - matches.forEach((match) => { - let base = match.groups.helper === 'theme' ? ['theme'] : [] - let result = validateConfigPath(state, match.groups.key, base) - - if (result.isValid === true) { - return null - } - - let startIndex = - match.index + - match.groups.prefix.length + - match.groups.helper.length + - 1 + // open paren - match.groups.quote.length - - diagnostics.push({ - code: DiagnosticKind.InvalidConfigPath, - range: absoluteRange( - { - start: indexToPosition(text, startIndex), - end: indexToPosition(text, startIndex + match.groups.key.length), - }, - range - ), - severity: - severity === 'error' - ? DiagnosticSeverity.Error - : DiagnosticSeverity.Warning, - message: result.reason, - suggestions: result.suggestions, - }) - }) - }) - - return diagnostics -} diff --git a/src/lsp/providers/diagnostics/getInvalidScreenDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidScreenDiagnostics.ts deleted file mode 100644 index b0e4252438ea6bb7dd47071189c3881aae219225..0000000000000000000000000000000000000000 --- a/src/lsp/providers/diagnostics/getInvalidScreenDiagnostics.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { State, Settings } from '../../util/state' -import { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver' -import { InvalidScreenDiagnostic, DiagnosticKind } from './types' -import { isCssDoc } from '../../util/css' -import { getLanguageBoundaries } from '../../util/getLanguageBoundaries' -import { findAll, indexToPosition } from '../../util/find' -import { closest } from '../../util/closest' -import { absoluteRange } from '../../util/absoluteRange' -const dlv = require('dlv') - -export function getInvalidScreenDiagnostics( - state: State, - document: TextDocument, - settings: Settings -): InvalidScreenDiagnostic[] { - let severity = settings.lint.invalidScreen - if (severity === 'ignore') return [] - - let diagnostics: InvalidScreenDiagnostic[] = [] - let ranges: Range[] = [] - - if (isCssDoc(state, document)) { - ranges.push(undefined) - } else { - let boundaries = getLanguageBoundaries(state, document) - if (!boundaries) return [] - ranges.push(...boundaries.css) - } - - ranges.forEach((range) => { - let text = document.getText(range) - let matches = findAll(/(?:\s|^)@screen\s+(?[^\s{]+)/g, text) - - let screens = Object.keys( - dlv(state.config, 'theme.screens', dlv(state.config, 'screens', {})) - ) - - matches.forEach((match) => { - if (screens.includes(match.groups.screen)) { - return null - } - - let message = `The screen '${match.groups.screen}' does not exist in your theme config.` - let suggestions: string[] = [] - let suggestion = closest(match.groups.screen, screens) - - if (suggestion) { - suggestions.push(suggestion) - message += ` Did you mean '${suggestion}'?` - } - - diagnostics.push({ - code: DiagnosticKind.InvalidScreen, - range: absoluteRange( - { - start: indexToPosition( - text, - match.index + match[0].length - match.groups.screen.length - ), - end: indexToPosition(text, match.index + match[0].length), - }, - range - ), - severity: - severity === 'error' - ? DiagnosticSeverity.Error - : DiagnosticSeverity.Warning, - message, - suggestions, - }) - }) - }) - - return diagnostics -} diff --git a/src/lsp/providers/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts deleted file mode 100644 index 9b88bdb06e0e5c3754c6c5eecbf23af7c342a21d..0000000000000000000000000000000000000000 --- a/src/lsp/providers/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { State, Settings } from '../../util/state' -import { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver' -import { InvalidTailwindDirectiveDiagnostic, DiagnosticKind } from './types' -import { isCssDoc } from '../../util/css' -import { getLanguageBoundaries } from '../../util/getLanguageBoundaries' -import { findAll, indexToPosition } from '../../util/find' -import semver from 'semver' -import { closest } from '../../util/closest' -import { absoluteRange } from '../../util/absoluteRange' - -export function getInvalidTailwindDirectiveDiagnostics( - state: State, - document: TextDocument, - settings: Settings -): InvalidTailwindDirectiveDiagnostic[] { - let severity = settings.lint.invalidTailwindDirective - if (severity === 'ignore') return [] - - let diagnostics: InvalidTailwindDirectiveDiagnostic[] = [] - let ranges: Range[] = [] - - if (isCssDoc(state, document)) { - ranges.push(undefined) - } else { - let boundaries = getLanguageBoundaries(state, document) - if (!boundaries) return [] - ranges.push(...boundaries.css) - } - - ranges.forEach((range) => { - let text = document.getText(range) - let matches = findAll(/(?:\s|^)@tailwind\s+(?[^;]+)/g, text) - - let valid = [ - 'utilities', - 'components', - 'screens', - semver.gte(state.version, '1.0.0-beta.1') ? 'base' : 'preflight', - ] - - matches.forEach((match) => { - if (valid.includes(match.groups.value)) { - return null - } - - let message = `'${match.groups.value}' is not a valid group.` - let suggestions: string[] = [] - - if (match.groups.value === 'preflight') { - suggestions.push('base') - message += ` Did you mean 'base'?` - } else { - let suggestion = closest(match.groups.value, valid) - if (suggestion) { - suggestions.push(suggestion) - message += ` Did you mean '${suggestion}'?` - } - } - - diagnostics.push({ - code: DiagnosticKind.InvalidTailwindDirective, - range: absoluteRange( - { - start: indexToPosition( - text, - match.index + match[0].length - match.groups.value.length - ), - end: indexToPosition(text, match.index + match[0].length), - }, - range - ), - severity: - severity === 'error' - ? DiagnosticSeverity.Error - : DiagnosticSeverity.Warning, - message, - suggestions, - }) - }) - }) - - return diagnostics -} diff --git a/src/lsp/providers/diagnostics/getInvalidVariantDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidVariantDiagnostics.ts deleted file mode 100644 index 006740103c2776d4d68d6452dd5f808b8100a5d2..0000000000000000000000000000000000000000 --- a/src/lsp/providers/diagnostics/getInvalidVariantDiagnostics.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { State, Settings } from '../../util/state' -import { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver' -import { InvalidVariantDiagnostic, DiagnosticKind } from './types' -import { isCssDoc } from '../../util/css' -import { getLanguageBoundaries } from '../../util/getLanguageBoundaries' -import { findAll, indexToPosition } from '../../util/find' -import { closest } from '../../util/closest' -import { absoluteRange } from '../../util/absoluteRange' - -export function getInvalidVariantDiagnostics( - state: State, - document: TextDocument, - settings: Settings -): InvalidVariantDiagnostic[] { - let severity = settings.lint.invalidVariant - if (severity === 'ignore') return [] - - let diagnostics: InvalidVariantDiagnostic[] = [] - let ranges: Range[] = [] - - if (isCssDoc(state, document)) { - ranges.push(undefined) - } else { - let boundaries = getLanguageBoundaries(state, document) - if (!boundaries) return [] - ranges.push(...boundaries.css) - } - - ranges.forEach((range) => { - let text = document.getText(range) - let matches = findAll(/(?:\s|^)@variants\s+(?[^{]+)/g, text) - - matches.forEach((match) => { - let variants = match.groups.variants.split(/(\s*,\s*)/) - let listStartIndex = - match.index + match[0].length - match.groups.variants.length - - for (let i = 0; i < variants.length; i += 2) { - let variant = variants[i].trim() - if (state.variants.includes(variant)) { - continue - } - - let message = `The variant '${variant}' does not exist.` - let suggestions: string[] = [] - let suggestion = closest(variant, state.variants) - - if (suggestion) { - suggestions.push(suggestion) - message += ` Did you mean '${suggestion}'?` - } - - let variantStartIndex = - listStartIndex + variants.slice(0, i).join('').length - - diagnostics.push({ - code: DiagnosticKind.InvalidVariant, - range: absoluteRange( - { - start: indexToPosition(text, variantStartIndex), - end: indexToPosition(text, variantStartIndex + variant.length), - }, - range - ), - severity: - severity === 'error' - ? DiagnosticSeverity.Error - : DiagnosticSeverity.Warning, - message, - suggestions, - }) - } - }) - }) - - return diagnostics -} diff --git a/src/lsp/providers/diagnostics/types.ts b/src/lsp/providers/diagnostics/types.ts deleted file mode 100644 index 1cfd0e2b4d4b26b543776be2c4810661b799f6c2..0000000000000000000000000000000000000000 --- a/src/lsp/providers/diagnostics/types.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Diagnostic } from 'vscode-languageserver' -import { DocumentClassName, DocumentClassList } from '../../util/state' - -export enum DiagnosticKind { - CssConflict = 'cssConflict', - InvalidApply = 'invalidApply', - InvalidScreen = 'invalidScreen', - InvalidVariant = 'invalidVariant', - InvalidConfigPath = 'invalidConfigPath', - InvalidTailwindDirective = 'invalidTailwindDirective', -} - -export type CssConflictDiagnostic = Diagnostic & { - code: DiagnosticKind.CssConflict - className: DocumentClassName - otherClassNames: DocumentClassName[] -} - -export function isCssConflictDiagnostic( - diagnostic: AugmentedDiagnostic -): diagnostic is CssConflictDiagnostic { - return diagnostic.code === DiagnosticKind.CssConflict -} - -export type InvalidApplyDiagnostic = Diagnostic & { - code: DiagnosticKind.InvalidApply - className: DocumentClassName -} - -export function isInvalidApplyDiagnostic( - diagnostic: AugmentedDiagnostic -): diagnostic is InvalidApplyDiagnostic { - return diagnostic.code === DiagnosticKind.InvalidApply -} - -export type InvalidScreenDiagnostic = Diagnostic & { - code: DiagnosticKind.InvalidScreen - suggestions: string[] -} - -export function isInvalidScreenDiagnostic( - diagnostic: AugmentedDiagnostic -): diagnostic is InvalidScreenDiagnostic { - return diagnostic.code === DiagnosticKind.InvalidScreen -} - -export type InvalidVariantDiagnostic = Diagnostic & { - code: DiagnosticKind.InvalidVariant - suggestions: string[] -} - -export function isInvalidVariantDiagnostic( - diagnostic: AugmentedDiagnostic -): diagnostic is InvalidVariantDiagnostic { - return diagnostic.code === DiagnosticKind.InvalidVariant -} - -export type InvalidConfigPathDiagnostic = Diagnostic & { - code: DiagnosticKind.InvalidConfigPath - suggestions: string[] -} - -export function isInvalidConfigPathDiagnostic( - diagnostic: AugmentedDiagnostic -): diagnostic is InvalidConfigPathDiagnostic { - return diagnostic.code === DiagnosticKind.InvalidConfigPath -} - -export type InvalidTailwindDirectiveDiagnostic = Diagnostic & { - code: DiagnosticKind.InvalidTailwindDirective - suggestions: string[] -} - -export function isInvalidTailwindDirectiveDiagnostic( - diagnostic: AugmentedDiagnostic -): diagnostic is InvalidTailwindDirectiveDiagnostic { - return diagnostic.code === DiagnosticKind.InvalidTailwindDirective -} - -export type AugmentedDiagnostic = - | CssConflictDiagnostic - | InvalidApplyDiagnostic - | InvalidScreenDiagnostic - | InvalidVariantDiagnostic - | InvalidConfigPathDiagnostic - | InvalidTailwindDirectiveDiagnostic diff --git a/src/lsp/providers/hoverProvider.ts b/src/lsp/providers/hoverProvider.ts index 2084ec42933cbf2cfc02ead3b476698727a2d89e..a9010a3142cb4e9700f63e3bd06426a144b9afa0 100644 --- a/src/lsp/providers/hoverProvider.ts +++ b/src/lsp/providers/hoverProvider.ts @@ -1,11 +1,10 @@ import { State } from '../util/state' import { Hover, TextDocumentPositionParams } from 'vscode-languageserver' +import { getClassNameParts } from '../util/getClassNameAtPosition' import { stringifyCss, stringifyConfigValue } from '../util/stringify' const dlv = require('dlv') import { isCssContext } from '../util/css' import { findClassNameAtPosition } from '../util/find' -import { validateApply } from '../util/validateApply' -import { getClassNameParts } from '../util/getClassNameAtPosition' export function provideHover( state: State, @@ -81,13 +80,6 @@ if (className === null) return null const parts = getClassNameParts(state, className.className) if (!parts) return null - - if (isCssContext(state, doc, position)) { - let validated = validateApply(state, parts) - if (validated === null || validated.isApplyable === false) { - return null - } - } return { contents: { diff --git a/src/lsp/server.ts b/src/lsp/server.ts index cc85ae4662a1e6cbc4a8af47df121dd7acdb8f31..b28b1bad9d226222f5d9e24472c9d8d698df6187 100644 --- a/src/lsp/server.ts +++ b/src/lsp/server.ts @@ -16,8 +16,6 @@ CompletionList, Hover, TextDocumentPositionParams, DidChangeConfigurationNotification, - CodeActionParams, - CodeAction, } from 'vscode-languageserver' import getTailwindState from '../class-names/index' import { State, Settings, EditorState } from './util/state' @@ -28,48 +26,28 @@ } from './providers/completionProvider' import { provideHover } from './providers/hoverProvider' import { URI } from 'vscode-uri' import { getDocumentSettings } from './util/getDocumentSettings' -import { - provideDiagnostics, + - * Licensed under the MIT License. See License.txt in the project root for license information. /* -------------------------------------------------------------------------------------------- - clearAllDiagnostics, -} from './providers/diagnostics/diagnosticsProvider' - * Licensed under the MIT License. See License.txt in the project root for license information. + ProposedFeatures, * ------------------------------------------------------------------------------------------ */ * Licensed under the MIT License. See License.txt in the project root for license information. - - - * Licensed under the MIT License. See License.txt in the project root for license information. import { -let state: State = { enabled: false, emitter: createEmitter(connection) } let documents = new TextDocuments() let workspaceFolder: string | null const defaultSettings: Settings = { emmetCompletions: false, includeLanguages: {}, - validate: true, - lint: { - cssConflict: 'warning', - invalidApply: 'error', - invalidScreen: 'error', - invalidVariant: 'error', - invalidConfigPath: 'error', - invalidTailwindDirective: 'error', - }, } let globalSettings: Settings = defaultSettings let documentSettings: Map = new Map() documents.onDidOpen((event) => { + CodeActionParams, -import { }) documents.onDidClose((event) => { documentSettings.delete(event.document.uri) -}) -documents.onDidChangeContent((change) => { - provideDiagnostics(state, change.document) }) documents.listen(connection) @@ -90,10 +68,6 @@ : {}, capabilities: { configuration: capabilities.workspace && !!capabilities.workspace.configuration, - diagnosticRelatedInformation: - capabilities.textDocument && - capabilities.textDocument.publishDiagnostics && - capabilities.textDocument.publishDiagnostics.relatedInformation, }, } @@ -103,12 +77,7 @@ { // @ts-ignore onChange: (newState: State): void => { if (newState && !newState.error) { - ProposedFeatures, /* -------------------------------------------------------------------------------------------- - ...newState, - enabled: true, - emitter: state.emitter, - editor: editorState, } connection.sendNotification('tailwindcss/configUpdated', [ state.configPath, @@ -116,16 +85,10 @@ state.config, state.plugins, ]) TextDocumentSyncKind, - * Copyright (c) Microsoft Corporation. All rights reserved. - TextDocumentSyncKind, * Licensed under the MIT License. See License.txt in the project root for license information. - ProposedFeatures, /* -------------------------------------------------------------------------------------------- - enabled: false, - emitter: state.emitter, ProposedFeatures, - - } + createConnection, if (newState && newState.error) { const payload: { message: string @@ -141,8 +104,6 @@ } connection.sendNotification('tailwindcss/configError', [payload]) } CompletionItem, - TextDocuments, - CompletionItem, ProposedFeatures, // connection.sendNotification('tailwindcss/configUpdated', [null]) } @@ -152,16 +113,11 @@ ) if (tailwindState) { /* -------------------------------------------------------------------------------------------- -} from './providers/completionProvider' - enabled: true, - emitter: state.emitter, - InitializeParams, ProposedFeatures, - ...tailwindState, - } + TextDocuments, } else { /* -------------------------------------------------------------------------------------------- - clearAllDiagnostics, + state.config, } return { @@ -189,7 +145,6 @@ typeof state.separator === 'undefined' ? ':' : state.separator, ], }, hoverProvider: true, - codeActionProvider: true, }, } } @@ -221,8 +176,10 @@ (change.settings.tailwindCSS || defaultSettings) ) } + state.editor.documents + CodeAction, /* -------------------------------------------------------------------------------------------- - diagnosticRelatedInformation: + .forEach((doc) => getDocumentSettings(state, doc.uri)) }) connection.onCompletion( @@ -243,13 +200,6 @@ connection.onHover( (params: TextDocumentPositionParams): Hover => { if (!state.enabled) return null return provideHover(state, params) - } -) - -connection.onCodeAction( - (params: CodeActionParams): Promise => { - if (!state.enabled) return null - return provideCodeActions(state, params) } ) diff --git a/src/lsp/util/absoluteRange.ts b/src/lsp/util/absoluteRange.ts deleted file mode 100644 index 9250e4fb9ec9b13ff3124072808f2013ef5009a0..0000000000000000000000000000000000000000 --- a/src/lsp/util/absoluteRange.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Range } from 'vscode-languageserver' - -export function absoluteRange(range: Range, reference?: Range) { - return { - start: { - line: (reference?.start.line || 0) + range.start.line, - character: - (range.end.line === 0 ? reference?.start.character || 0 : 0) + - range.start.character, - }, - end: { - line: (reference?.start.line || 0) + range.end.line, - character: - (range.end.line === 0 ? reference?.start.character || 0 : 0) + - range.end.character, - }, - } -} diff --git a/src/lsp/util/closest.ts b/src/lsp/util/closest.ts deleted file mode 100644 index ebdfacc270c2c5467d501b4ff4642aef04f6277e..0000000000000000000000000000000000000000 --- a/src/lsp/util/closest.ts +++ /dev/null @@ -1,5 +0,0 @@ -import sift from 'sift-string' - -export function closest(input: string, options: string[]): string | undefined { - return options.concat([]).sort((a, b) => sift(input, a) - sift(input, b))[0] -} diff --git a/src/lsp/util/combinations.ts b/src/lsp/util/combinations.ts deleted file mode 100644 index 2c9868b207b01b60cc0789b800058285257e187a..0000000000000000000000000000000000000000 --- a/src/lsp/util/combinations.ts +++ /dev/null @@ -1,13 +0,0 @@ -export function combinations(str: string): string[] { - let fn = function (active: string, rest: string, a: string[]) { - if (!active && !rest) return - if (!rest) { - a.push(active) - } else { - fn(active + rest[0], rest.slice(1), a) - fn(active, rest.slice(1), a) - } - return a - } - return fn('', str, []) -} diff --git a/src/lsp/util/cssObjToAst.ts b/src/lsp/util/cssObjToAst.ts deleted file mode 100644 index 42826f7526c514588519a63a8d2e6723a9ebfdd2..0000000000000000000000000000000000000000 --- a/src/lsp/util/cssObjToAst.ts +++ /dev/null @@ -1,127 +0,0 @@ -/* -This is a modified version of the postcss-js 'parse' function which accepts the -postcss module as an argument. License below: - -The MIT License (MIT) - -Copyright 2015 Andrey Sitnik - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -var IMPORTANT = /\s*!important\s*$/i - -var unitless = { - 'box-flex': true, - 'box-flex-group': true, - 'column-count': true, - flex: true, - 'flex-grow': true, - 'flex-positive': true, - 'flex-shrink': true, - 'flex-negative': true, - 'font-weight': true, - 'line-clamp': true, - 'line-height': true, - opacity: true, - order: true, - orphans: true, - 'tab-size': true, - widows: true, - 'z-index': true, - zoom: true, - 'fill-opacity': true, - 'stroke-dashoffset': true, - 'stroke-opacity': true, - 'stroke-width': true, -} - -function dashify(str) { - return str - .replace(/([A-Z])/g, '-$1') - .replace(/^ms-/, '-ms-') - .toLowerCase() -} - -function decl(parent, name, value, postcss) { - if (value === false || value === null) return - - name = dashify(name) - if (typeof value === 'number') { - if (value === 0 || unitless[name]) { - value = value.toString() - } else { - value = value.toString() + 'px' - } - } - - if (name === 'css-float') name = 'float' - - if (IMPORTANT.test(value)) { - value = value.replace(IMPORTANT, '') - parent.push(postcss.decl({ prop: name, value: value, important: true })) - } else { - parent.push(postcss.decl({ prop: name, value: value })) - } -} - -function atRule(parent, parts, value, postcss) { - var node = postcss.atRule({ name: parts[1], params: parts[3] || '' }) - if (typeof value === 'object') { - node.nodes = [] - parse(value, node, postcss) - } - parent.push(node) -} - -function parse(obj, parent, postcss) { - var name, value, node, i - for (name in obj) { - if (obj.hasOwnProperty(name)) { - value = obj[name] - if (value === null || typeof value === 'undefined') { - continue - } else if (name[0] === '@') { - var parts = name.match(/@([^\s]+)(\s+([\w\W]*)\s*)?/) - if (Array.isArray(value)) { - for (i = 0; i < value.length; i++) { - atRule(parent, parts, value[i], postcss) - } - } else { - atRule(parent, parts, value, postcss) - } - } else if (Array.isArray(value)) { - for (i = 0; i < value.length; i++) { - decl(parent, name, value[i], postcss) - } - } else if (typeof value === 'object') { - node = postcss.rule({ selector: name }) - parse(value, node, postcss) - parent.push(node) - } else { - decl(parent, name, value, postcss) - } - } - } -} - -export function cssObjToAst(obj, postcss) { - var root = postcss.root() - parse(obj, root, postcss) - return root -} diff --git a/src/lsp/util/find.ts b/src/lsp/util/find.ts index b642534d012dc3816516f0284de71b1ecbf31954..5eaef42ef1b5c41b8eeef529d00fb4109cd481bd 100644 --- a/src/lsp/util/find.ts +++ b/src/lsp/util/find.ts @@ -5,12 +5,10 @@ import { isCssContext, isCssDoc } from './css' import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' import { isWithinRange } from './isWithinRange' import { isJsContext, isJsDoc } from './js' -import { flatten } from '../../util/array' import { getClassAttributeLexer, getComputedClassAttributeLexer, } from './lexers' -import { getLanguageBoundaries } from './getLanguageBoundaries' export function findAll(re: RegExp, str: string): RegExpMatchArray[] { let match: RegExpMatchArray @@ -29,87 +27,78 @@ } return matches[matches.length - 1] } -export function getClassNamesInClassList({ - classList, - range, +import { isWithinRange } from './isWithinRange' import lineColumn from 'line-column' -import { TextDocument, Range, Position } from 'vscode-languageserver' -}: DocumentClassList): DocumentClassName[] { - const parts = classList.split(/(\s+)/) -import lineColumn from 'line-column' +import { isWithinRange } from './isWithinRange' import { isCssContext, isCssDoc } from './css' -import lineColumn from 'line-column' +import { isWithinRange } from './isWithinRange' import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' -import lineColumn from 'line-column' +import { isWithinRange } from './isWithinRange' import { isWithinRange } from './isWithinRange' -import lineColumn from 'line-column' +import { isWithinRange } from './isWithinRange' import { isJsContext, isJsDoc } from './js' -import lineColumn from 'line-column' +import { isWithinRange } from './isWithinRange' import { flatten } from '../../util/array' - const end = indexToPosition(classList, index + parts[i].length) -import { isCssContext, isCssDoc } from './css' + return matches -import { isCssContext, isCssDoc } from './css' + return matches import { TextDocument, Range, Position } from 'vscode-languageserver' -import { isCssContext, isCssDoc } from './css' import { DocumentClassName, DocumentClassList, State } from './state' - classList, - range, - important, - }, - relativeRange: { - start, - end, - }, - range: { -import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' import { TextDocument, Range, Position } from 'vscode-languageserver' +import { DocumentClassName, DocumentClassList, State } from './state' -import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' import { DocumentClassName, DocumentClassList, State } from './state' -import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' +import { TextDocument, Range, Position } from 'vscode-languageserver' import lineColumn from 'line-column' -import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' + return matches import { isCssContext, isCssDoc } from './css' -import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' + return matches import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' -import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' + return matches import { isWithinRange } from './isWithinRange' -import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' + return matches import { isJsContext, isJsDoc } from './js' - character: -import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' + return matches import { flatten } from '../../util/array' - }, - }, -import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' + return matches import { -import { isWithinRange } from './isWithinRange' +} -import { isWithinRange } from './isWithinRange' +} import { TextDocument, Range, Position } from 'vscode-languageserver' - } + range: { +import { TextDocument, Range, Position } from 'vscode-languageserver' return names } +import { DocumentClassName, DocumentClassList, State } from './state' import { TextDocument, Range, Position } from 'vscode-languageserver' -import { isCssContext, isCssDoc } from './css' -export function findClassNamesInRange( doc: TextDocument, - range?: Range, + (end.line === 0 ? range.start.character : 0) + +import { TextDocument, Range, Position } from 'vscode-languageserver' mode?: 'html' | 'css' +import { TextDocument, Range, Position } from 'vscode-languageserver' ): DocumentClassName[] { +import { TextDocument, Range, Position } from 'vscode-languageserver' const classLists = findClassListsInRange(doc, range, mode) - return flatten(classLists.map(getClassNamesInClassList)) } +import { isCssContext, isCssDoc } from './css' import { TextDocument, Range, Position } from 'vscode-languageserver' +import { isWithinRange } from './isWithinRange' import { isCssContext, isCssDoc } from './css' + (end.line === 0 ? range.start.character : 0) + end.character, + let match: RegExpMatchArray import { isJsContext, isJsDoc } from './js' +import { TextDocument, Range, Position } from 'vscode-languageserver' state: State, -import { isJsContext, isJsDoc } from './js' +import { DocumentClassName, DocumentClassList, State } from './state' import { DocumentClassName, DocumentClassList, State } from './state' import { isWithinRange } from './isWithinRange' -import { isJsContext, isJsDoc } from './js' + } +} import { isJsContext, isJsDoc } from './js' +import { getLanguageBoundaries } from './getLanguageBoundaries' import lineColumn from 'line-column' -import { isWithinRange } from './isWithinRange' + return names +} import { + ) } export function findClassListsInCssRange( @@ -115,10 +106,7 @@ doc: TextDocument, range?: Range ): DocumentClassList[] { const text = doc.getText(range) - const matches = findAll( - /(@apply\s+)(?[^;}]+?)(?\s*!important)?\s*[;}]/g, -import { flatten } from '../../util/array' +export function findLast(re: RegExp, str: string): RegExpMatchArray { - ) const globalStart: Position = range ? range.start : { line: 0, character: 0 } return matches.map((match) => { @@ -129,7 +117,6 @@ match.index + match[1].length + match.groups.classList.length ) return { classList: match.groups.classList, - important: Boolean(match.groups.important), range: { start: { line: globalStart.line + start.line, @@ -148,7 +135,7 @@ } export function findClassListsInHtmlRange( doc: TextDocument, - range?: Range + range: Range ): DocumentClassList[] { const text = doc.getText(range) const matches = findAll(/(?:\b|:)class(?:Name)?=['"`{]/g, text) @@ -227,17 +214,16 @@ return { classList: value.substr(beforeOffset, value.length + afterOffset), range: { start: { - line: (range?.start.line || 0) + start.line, + line: range.start.line + start.line, character: - (end.line === 0 ? range?.start.character || 0 : 0) + + (end.line === 0 ? range.start.character : 0) + start.character, }, end: { - line: (range?.start.line || 0) + end.line, + line: range.start.line + end.line, character: - let match: RegExpMatchArray +} import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' - end.character, }, }, } @@ -251,8 +237,8 @@ } export function findClassListsInRange( doc: TextDocument, - range?: Range, + range: Range, - mode?: 'html' | 'css' + mode: 'html' | 'css' ): DocumentClassList[] { if (mode === 'css') { return findClassListsInCssRange(doc, range) @@ -268,30 +254,94 @@ if (isCssDoc(state, doc)) { return findClassListsInCssRange(doc) } -import { TextDocument, Range, Position } from 'vscode-languageserver' + if (isVueDoc(doc)) { + let text = doc.getText() + let blocks = findAll( + /<(?template|style|script)\b[^>]*>.*?(<\/\k>|$)/gis, + text +import { flatten } from '../../util/array' import { flatten } from '../../util/array' import { DocumentClassName, DocumentClassList, State } from './state' + const end = indexToPosition(classList, index + parts[i].length) + let cssRanges: Range[] = [] + const matches = findAll(re, str) import { TextDocument, Range, Position } from 'vscode-languageserver' -import { flatten } from '../../util/array' + let range = { + const matches = findAll(re, str) import lineColumn from 'line-column' + end: indexToPosition(text, blocks[i].index + blocks[i][0].length), import { TextDocument, Range, Position } from 'vscode-languageserver' + const parts = classList.split(/(\s+)/) +import { DocumentClassName, DocumentClassList, State } from './state' import { isCssContext, isCssDoc } from './css' +import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' + cssRanges.push(range) + } else { + htmlRanges.push(range) import { TextDocument, Range, Position } from 'vscode-languageserver' -import { flatten } from '../../util/array' + const parts = classList.split(/(\s+)/) + } +import { DocumentClassName, DocumentClassList, State } from './state' import { isCssContext, isCssDoc } from './css' +import { + [], + if (matches.length === 0) { import { TextDocument, Range, Position } from 'vscode-languageserver' + ...htmlRanges.map((range) => findClassListsInHtmlRange(doc, range)), + ...cssRanges.map((range) => findClassListsInCssRange(doc, range)), + ] import { flatten } from '../../util/array' +import { flatten } from '../../util/array' + } + + if (matches.length === 0) { import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' + let text = doc.getText() + let styleBlocks = findAll(/]*>|>).*?(<\/style>|$)/gis, text) + let htmlRanges: Range[] = [] + let cssRanges: Range[] = [] + let currentIndex = 0 import { TextDocument, Range, Position } from 'vscode-languageserver' +import { isCssContext, isCssDoc } from './css' + if (matches.length === 0) { import { flatten } from '../../util/array' + htmlRanges.push({ +import { DocumentClassName, DocumentClassList, State } from './state' import { isWithinRange } from './isWithinRange' + return null import { TextDocument, Range, Position } from 'vscode-languageserver' + }) + cssRanges.push({ + start: indexToPosition(text, styleBlocks[i].index), + end: indexToPosition( + text, + styleBlocks[i].index + styleBlocks[i][0].length + ), + }) + return null import { flatten } from '../../util/array' + } + htmlRanges.push({ +import { DocumentClassName, DocumentClassList, State } from './state' import { isJsContext, isJsDoc } from './js' + end: indexToPosition(text, text.length), } +import { + return [].concat.apply( + [], + if (matches.length === 0) { import { TextDocument, Range, Position } from 'vscode-languageserver' + ...htmlRanges.map((range) => findClassListsInHtmlRange(doc, range)), + ...cssRanges.map((range) => findClassListsInCssRange(doc, range)), + ] ) + } + + return [] +} + +function indexToPosition(str: string, index: number): Position { const { line, col } = lineColumn(str + '\n', index) return { line: line - 1, character: col - 1 } } diff --git a/src/lsp/util/getClassNameAtPosition.ts b/src/lsp/util/getClassNameAtPosition.ts index 7418b2f157723967380453978a082e42fdb9ce49..95de79a0392a9cfcf0de45d184a266748a77a1a3 100644 --- a/src/lsp/util/getClassNameAtPosition.ts +++ b/src/lsp/util/getClassNameAtPosition.ts @@ -1,7 +1,50 @@ + import { State } from './state' + import { combinations } from './combinations' const dlv = require('dlv') +export function getClassNameAtPosition( + document: TextDocument, + position: Position +): DocumentClassName { + const range1: Range = { + start: { line: Math.max(position.line - 5, 0), character: 0 }, + end: position, + } + const text1: string = document.getText(range1) + + if (!/\bclass(Name)?=['"][^'"]*$/.test(text1)) return null + + const range2: Range = { + start: { line: Math.max(position.line - 5, 0), character: 0 }, + end: { line: position.line + 1, character: position.character }, + } + const text2: string = document.getText(range2) + + let str: string = text1 + text2.substr(text1.length).match(/^([^"' ]*)/)[0] + let matches: RegExpMatchArray = str.match(/\bclass(Name)?=["']([^"']+)$/) + + if (!matches) return null + + let className: string = matches[2].split(' ').pop() + if (!className) return null + + let range: Range = { + start: { + line: position.line, + character: + position.character + str.length - text1.length - className.length, + }, + end: { + line: position.line, + character: position.character + str.length - text1.length, + }, + } + + return { className, range } +} + export function getClassNameParts(state: State, className: string): string[] { let separator = state.separator className = className.replace(/^\./, '') @@ -42,3 +85,17 @@ } return false }) } + +function combinations(str: string): string[] { + let fn = function (active: string, rest: string, a: string[]) { + if (!active && !rest) return + if (!rest) { + a.push(active) + } else { + fn(active + rest[0], rest.slice(1), a) + fn(active, rest.slice(1), a) + } + return a + } + return fn('', str, []) +} diff --git a/src/lsp/util/getClassNameDecls.ts b/src/lsp/util/getClassNameDecls.ts deleted file mode 100644 index a04ce94f186e0999b7546021378aeef7a6db2498..0000000000000000000000000000000000000000 --- a/src/lsp/util/getClassNameDecls.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { State } from './state' -import { getClassNameParts } from './getClassNameAtPosition' -import removeMeta from './removeMeta' -const dlv = require('dlv') - -export function getClassNameDecls( - state: State, - className: string -): Record | Record[] | null { - const parts = getClassNameParts(state, className) - if (!parts) return null - - const info = dlv(state.classNames.classNames, parts) - - if (Array.isArray(info)) { - return info.map(removeMeta) - } - - return removeMeta(info) -} diff --git a/src/lsp/util/getClassNameMeta.ts b/src/lsp/util/getClassNameMeta.ts deleted file mode 100644 index 1099a1ab85825f114ad20874f36c5991dfb2866b..0000000000000000000000000000000000000000 --- a/src/lsp/util/getClassNameMeta.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { State, ClassNameMeta } from './state' -import { getClassNameParts } from './getClassNameAtPosition' -const dlv = require('dlv') - -export function getClassNameMeta( - state: State, - classNameOrParts: string | string[] -): ClassNameMeta | ClassNameMeta[] { - const parts = Array.isArray(classNameOrParts) - ? classNameOrParts - : getClassNameParts(state, classNameOrParts) - if (!parts) return null - const info = dlv(state.classNames.classNames, parts) - - if (Array.isArray(info)) { - return info.map((i) => ({ - source: i.__source, - pseudo: i.__pseudo, - scope: i.__scope, - context: i.__context, - })) - } - - return { - source: info.__source, - pseudo: info.__pseudo, - scope: info.__scope, - context: info.__context, - } -} diff --git a/src/lsp/util/getDocumentSettings.ts b/src/lsp/util/getDocumentSettings.ts index 2f127de4cdbe2bdc3df6778c26e283ad1771cd01..3ee25bd837e498fad81958abf76ff085f54325fa 100644 --- a/src/lsp/util/getDocumentSettings.ts +++ b/src/lsp/util/getDocumentSettings.ts @@ -1,20 +1,20 @@ import { State, Settings } from './state' -import { TextDocument } from 'vscode-languageserver' export async function getDocumentSettings( state: State, - document: TextDocument + resource: string ): Promise { if (!state.editor.capabilities.configuration) { return Promise.resolve(state.editor.globalSettings) } - let result = state.editor.documentSettings.get(document.uri) + let result = state.editor.documentSettings.get(resource) if (!result) { +import { TextDocument } from 'vscode-languageserver' import { State, Settings } from './state' - - languageId: document.languageId, + scopeUri: resource, + section: 'tailwindCSS', }) - state.editor.documentSettings.set(document.uri, result) + state.editor.documentSettings.set(resource, result) } return result } diff --git a/src/lsp/util/getLanguageBoundaries.ts b/src/lsp/util/getLanguageBoundaries.ts deleted file mode 100644 index c309aaeca3846b7f2f0fc98af7cf80a95e6cd817..0000000000000000000000000000000000000000 --- a/src/lsp/util/getLanguageBoundaries.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { TextDocument, Range } from 'vscode-languageserver' -import { isVueDoc, isHtmlDoc, isSvelteDoc } from './html' -import { State } from './state' -import { findAll, indexToPosition } from './find' -import { isJsDoc } from './js' - -export interface LanguageBoundaries { - html: Range[] - css: Range[] -} - -export function getLanguageBoundaries( - state: State, - doc: TextDocument -): LanguageBoundaries | null { - if (isVueDoc(doc)) { - let text = doc.getText() - let blocks = findAll( - /(?<(?template|style|script)\b[^>]*>).*?(?<\/\k>|$)/gis, - text - ) - let htmlRanges: Range[] = [] - let cssRanges: Range[] = [] - for (let i = 0; i < blocks.length; i++) { - let range = { - start: indexToPosition( - text, - blocks[i].index + blocks[i].groups.open.length - ), - end: indexToPosition( - text, - blocks[i].index + blocks[i][0].length - blocks[i].groups.close.length - ), - } - if (blocks[i].groups.type === 'style') { - cssRanges.push(range) - } else { - htmlRanges.push(range) - } - } - - return { - html: htmlRanges, - css: cssRanges, - } - } - - if (isHtmlDoc(state, doc) || isJsDoc(state, doc) || isSvelteDoc(doc)) { - let text = doc.getText() - let styleBlocks = findAll( - /(?]*>|>)).*?(?<\/style>|$)/gis, - text - ) - let htmlRanges: Range[] = [] - let cssRanges: Range[] = [] - let currentIndex = 0 - - for (let i = 0; i < styleBlocks.length; i++) { - htmlRanges.push({ - start: indexToPosition(text, currentIndex), - end: indexToPosition(text, styleBlocks[i].index), - }) - cssRanges.push({ - start: indexToPosition( - text, - styleBlocks[i].index + styleBlocks[i].groups.open.length - ), - end: indexToPosition( - text, - styleBlocks[i].index + - styleBlocks[i][0].length - - styleBlocks[i].groups.close.length - ), - }) - currentIndex = styleBlocks[i].index + styleBlocks[i][0].length - } - htmlRanges.push({ - start: indexToPosition(text, currentIndex), - end: indexToPosition(text, text.length), - }) - - return { - html: htmlRanges, - css: cssRanges, - } - } - - return null -} diff --git a/src/lsp/util/joinWithAnd.ts b/src/lsp/util/joinWithAnd.ts deleted file mode 100644 index 2b2efb716afd1394a91dd44fc6f16c05d053b438..0000000000000000000000000000000000000000 --- a/src/lsp/util/joinWithAnd.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function joinWithAnd(strings: string[]): string { - return strings.reduce((acc, cur, i) => { - if (i === 0) { - return cur - } - if (strings.length > 1 && i === strings.length - 1) { - return `${acc} and ${cur}` - } - return `${acc}, ${cur}` - }, '') -} diff --git a/src/lsp/util/logFull.ts b/src/lsp/util/logFull.ts deleted file mode 100644 index c05fc1b824a56b3fe33f1f3e4e91b24d8d6074a7..0000000000000000000000000000000000000000 --- a/src/lsp/util/logFull.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as util from 'util' - -export function logFull(object: any): void { - console.log(util.inspect(object, { showHidden: false, depth: null })) -} diff --git a/src/lsp/util/rangesEqual.ts b/src/lsp/util/rangesEqual.ts deleted file mode 100644 index 220cebd50e242933d666ee7581d68e4030dd321b..0000000000000000000000000000000000000000 --- a/src/lsp/util/rangesEqual.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Range } from 'vscode-languageserver' - -export function rangesEqual(a: Range, b: Range): boolean { - return ( - a.start.line === b.start.line && - a.start.character === b.start.character && - a.end.line === b.end.line && - a.end.character === b.end.character - ) -} diff --git a/src/lsp/util/removeRangesFromString.ts b/src/lsp/util/removeRangesFromString.ts deleted file mode 100644 index f97d62b95b2aecae61c0e4195fba475af541a17e..0000000000000000000000000000000000000000 --- a/src/lsp/util/removeRangesFromString.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Range } from 'vscode-languageserver' -import lineColumn from 'line-column' -import { ensureArray } from '../../util/array' - -export function removeRangesFromString( - str: string, - rangeOrRanges: Range | Range[] -): string { - let ranges = ensureArray(rangeOrRanges) - let finder = lineColumn(str + '\n', { origin: 0 }) - let indexRanges: { start: number; end: number }[] = [] - - ranges.forEach((range) => { - let start = finder.toIndex(range.start.line, range.start.character) - let end = finder.toIndex(range.end.line, range.end.character) - for (let i = start - 1; i >= 0; i--) { - if (/\s/.test(str.charAt(i))) { - start = i - } else { - break - } - } - indexRanges.push({ start, end }) - }) - - indexRanges.sort((a, b) => a.start - b.start) - - let result = '' - let i = 0 - - indexRanges.forEach((indexRange) => { - result += str.substring(i, indexRange.start) - i = indexRange.end - }) - - result += str.substring(i) - - return result.trim() -} diff --git a/src/lsp/util/state.ts b/src/lsp/util/state.ts index 09a02006721913e72c3a576ee1c30f997476c6b5..daaa0501c05f20e0e88dc85c4357533d26e4a74d 100644 --- a/src/lsp/util/state.ts +++ b/src/lsp/util/state.ts @@ -1,5 +1,4 @@ import { TextDocuments, Connection, Range } from 'vscode-languageserver' -import { NotificationEmitter } from '../../lib/emitter' export type ClassNamesTree = { [key: string]: ClassNamesTree @@ -23,37 +22,20 @@ userLanguages: Record capabilities: { configuration: boolean import { NotificationEmitter } from '../../lib/emitter' -import { NotificationEmitter } from '../../lib/emitter' import { TextDocuments, Connection, Range } from 'vscode-languageserver' } - -type DiagnosticSeveritySetting = 'ignore' | 'warning' | 'error' export type Settings = { emmetCompletions: boolean includeLanguages: Record - validate: boolean - lint: { - cssConflict: DiagnosticSeveritySetting - invalidApply: DiagnosticSeveritySetting - invalidScreen: DiagnosticSeveritySetting - invalidVariant: DiagnosticSeveritySetting - invalidConfigPath: DiagnosticSeveritySetting - invalidTailwindDirective: DiagnosticSeveritySetting - } } export type State = null | { enabled: boolean - emitter: NotificationEmitter version?: string configPath?: string config?: any export type ClassNamesTree = { - tailwindcss: any - postcss: any - } -export type ClassNamesTree = { plugins?: any[] variants?: string[] @@ -66,21 +48,10 @@ export type DocumentClassList = { classList: string range: Range - important?: boolean } export type DocumentClassName = { className: string [key: string]: ClassNamesTree -import { NotificationEmitter } from '../../lib/emitter' - relativeRange: Range - classList: DocumentClassList -} - -export type ClassNameMeta = { - source: 'base' | 'components' | 'utilities' - pseudo: string[] - scope: string[] -} import { NotificationEmitter } from '../../lib/emitter' } diff --git a/src/lsp/util/stringToPath.ts b/src/lsp/util/stringToPath.ts deleted file mode 100644 index b06e1532e1903abb99361f1df64c01ad8bf408cf..0000000000000000000000000000000000000000 --- a/src/lsp/util/stringToPath.ts +++ /dev/null @@ -1,15 +0,0 @@ -// https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L6735-L6744 -let rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g -let reEscapeChar = /\\(\\)?/g - -export function stringToPath(string: string): string[] { - let result: string[] = [] - if (string.charCodeAt(0) === 46 /* . */) { - result.push('') - } - // @ts-ignore - string.replace(rePropName, (match, number, quote, subString) => { - result.push(quote ? subString.replace(reEscapeChar, '$1') : number || match) - }) - return result -} diff --git a/src/lsp/util/validateApply.ts b/src/lsp/util/validateApply.ts deleted file mode 100644 index 52f2b2c432fc5d85f448a1e9560683dc75a5bfed..0000000000000000000000000000000000000000 --- a/src/lsp/util/validateApply.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { State } from './state' -import { getClassNameMeta } from './getClassNameMeta' - -export function validateApply( - state: State, - classNameOrParts: string | string[] -): { isApplyable: true } | { isApplyable: false; reason: string } | null { - const meta = getClassNameMeta(state, classNameOrParts) - if (!meta) return null - - const className = Array.isArray(classNameOrParts) - ? classNameOrParts.join(state.separator) - : classNameOrParts - - let reason: string - - if (Array.isArray(meta)) { - reason = `'@apply' cannot be used with '${className}' because it is included in multiple rulesets.` - } else if (meta.source !== 'utilities') { - reason = `'@apply' cannot be used with '${className}' because it is not a utility.` - } else if (meta.context && meta.context.length > 0) { - if (meta.context.length === 1) { - reason = `'@apply' cannot be used with '${className}' because it is nested inside of an at-rule ('${meta.context[0]}').` - } else { - reason = `'@apply' cannot be used with '${className}' because it is nested inside of at-rules (${meta.context - .map((c) => `'${c}'`) - .join(', ')}).` - } - } else if (meta.pseudo && meta.pseudo.length > 0) { - if (meta.pseudo.length === 1) { - reason = `'@apply' cannot be used with '${className}' because its definition includes a pseudo-selector ('${meta.pseudo[0]}')` - } else { - reason = `'@apply' cannot be used with '${className}' because its definition includes pseudo-selectors (${meta.pseudo - .map((p) => `'${p}'`) - .join(', ')}).` - } - } - - if (reason) { - return { isApplyable: false, reason } - } - - return { isApplyable: true } -} diff --git a/src/util/array.ts b/src/util/array.ts index 869eb9f6cd346528cd554b115bfb374952032e48..b40dd245fe57a3f1e1ddeb24fc75343041b530db 100644 --- a/src/util/array.ts +++ b/src/util/array.ts @@ -2,16 +2,6 @@ export function dedupe(arr: Array): Array { return arr.filter((value, index, self) => self.indexOf(value) === index) } -export function dedupeBy( - arr: Array, - transform: (item: T) => any -): Array { - return arr.filter( - (value, index, self) => - self.map(transform).indexOf(transform(value)) === index - ) -} - export function ensureArray(value: T | T[]): T[] { return Array.isArray(value) ? value : [value] }