reuse @apply validation in hover provider

master
Brad Cornes 2020-06-21 01:38:08 +01:00
parent 2df85449a4
commit 8c6ba84417
3 changed files with 58 additions and 33 deletions

View File

@ -2,7 +2,7 @@ import { findClassNamesInRange } from '../../util/find'
import { InvalidApplyDiagnostic, DiagnosticKind } from './types' import { InvalidApplyDiagnostic, DiagnosticKind } from './types'
import { Settings, State } from '../../util/state' import { Settings, State } from '../../util/state'
import { TextDocument, DiagnosticSeverity } from 'vscode-languageserver' import { TextDocument, DiagnosticSeverity } from 'vscode-languageserver'
import { getClassNameMeta } from '../../util/getClassNameMeta' import { validateApply } from '../../util/validateApply'
export function getInvalidApplyDiagnostics( export function getInvalidApplyDiagnostics(
state: State, state: State,
@ -15,38 +15,11 @@ export function getInvalidApplyDiagnostics(
const classNames = findClassNamesInRange(document, undefined, 'css') const classNames = findClassNamesInRange(document, undefined, 'css')
let diagnostics: InvalidApplyDiagnostic[] = classNames.map((className) => { let diagnostics: InvalidApplyDiagnostic[] = classNames.map((className) => {
const meta = getClassNameMeta(state, className.className) let result = validateApply(state, className.className)
if (!meta) return null
let message: string if (result === null || result.isApplyable === true) {
return null
if (Array.isArray(meta)) {
message = `'@apply' cannot be used with '${className.className}' because it is included in multiple rulesets.`
} else if (meta.source !== 'utilities') {
message = `'@apply' cannot be used with '${className.className}' because it is not a utility.`
} else if (meta.context && meta.context.length > 0) {
if (meta.context.length === 1) {
message = `'@apply' cannot be used with '${className.className}' because it is nested inside of an at-rule ('${meta.context[0]}').`
} else {
message = `'@apply' cannot be used with '${
className.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) {
message = `'@apply' cannot be used with '${className.className}' because its definition includes a pseudo-selector ('${meta.pseudo[0]}')`
} else {
message = `'@apply' cannot be used with '${
className.className
}' because its definition includes pseudo-selectors (${meta.pseudo
.map((p) => `'${p}'`)
.join(', ')}).`
}
}
if (!message) return null
return { return {
code: DiagnosticKind.InvalidApply, code: DiagnosticKind.InvalidApply,
@ -55,7 +28,7 @@ export function getInvalidApplyDiagnostics(
? DiagnosticSeverity.Error ? DiagnosticSeverity.Error
: DiagnosticSeverity.Warning, : DiagnosticSeverity.Warning,
range: className.range, range: className.range,
message, message: result.reason,
className, className,
} }
}) })

View File

@ -1,10 +1,11 @@
import { State } from '../util/state' import { State } from '../util/state'
import { Hover, TextDocumentPositionParams } from 'vscode-languageserver' import { Hover, TextDocumentPositionParams } from 'vscode-languageserver'
import { getClassNameParts } from '../util/getClassNameAtPosition'
import { stringifyCss, stringifyConfigValue } from '../util/stringify' import { stringifyCss, stringifyConfigValue } from '../util/stringify'
const dlv = require('dlv') const dlv = require('dlv')
import { isCssContext } from '../util/css' import { isCssContext } from '../util/css'
import { findClassNameAtPosition } from '../util/find' import { findClassNameAtPosition } from '../util/find'
import { validateApply } from '../util/validateApply'
import { getClassNameParts } from '../util/getClassNameAtPosition'
export function provideHover( export function provideHover(
state: State, state: State,
@ -81,6 +82,13 @@ function provideClassNameHover(
const parts = getClassNameParts(state, className.className) const parts = getClassNameParts(state, className.className)
if (!parts) return null if (!parts) return null
if (isCssContext(state, doc, position)) {
let validated = validateApply(state, parts)
if (validated === null || validated.isApplyable === false) {
return null
}
}
return { return {
contents: { contents: {
language: 'css', language: 'css',

View File

@ -0,0 +1,44 @@
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 }
}