Fix CSS conflict diagnostic false negatives (#761)
parent
105b8b86ec
commit
6fa8861efd
|
@ -1,12 +1,43 @@
|
||||||
import { joinWithAnd } from '../util/joinWithAnd'
|
import { joinWithAnd } from '../util/joinWithAnd'
|
||||||
import { State, Settings } from '../util/state'
|
import { State, Settings } from '../util/state'
|
||||||
import type { TextDocument, DiagnosticSeverity } from 'vscode-languageserver'
|
import type { TextDocument } from 'vscode-languageserver'
|
||||||
import { CssConflictDiagnostic, DiagnosticKind } from './types'
|
import { CssConflictDiagnostic, DiagnosticKind } from './types'
|
||||||
import { findClassListsInDocument, getClassNamesInClassList } from '../util/find'
|
import { findClassListsInDocument, getClassNamesInClassList } from '../util/find'
|
||||||
import { getClassNameDecls } from '../util/getClassNameDecls'
|
import { getClassNameDecls } from '../util/getClassNameDecls'
|
||||||
import { getClassNameMeta } from '../util/getClassNameMeta'
|
import { getClassNameMeta } from '../util/getClassNameMeta'
|
||||||
import { equal } from '../util/array'
|
import { equal } from '../util/array'
|
||||||
import * as jit from '../util/jit'
|
import * as jit from '../util/jit'
|
||||||
|
import type { AtRule, Node, Rule } from 'postcss'
|
||||||
|
|
||||||
|
function isCustomProperty(property: string): boolean {
|
||||||
|
return property.startsWith('--')
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAtRule(node: Node): node is AtRule {
|
||||||
|
return node.type === 'atrule'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isKeyframes(rule: Rule): boolean {
|
||||||
|
let parent = rule.parent
|
||||||
|
if (!parent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (isAtRule(parent) && parent.name === 'keyframes') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRuleProperties(rule: Rule): string[] {
|
||||||
|
let properties: string[] = []
|
||||||
|
rule.walkDecls(({ prop }) => {
|
||||||
|
properties.push(prop)
|
||||||
|
})
|
||||||
|
if (properties.findIndex((p) => !isCustomProperty(p)) > -1) {
|
||||||
|
properties = properties.filter((p) => !isCustomProperty(p))
|
||||||
|
}
|
||||||
|
return properties
|
||||||
|
}
|
||||||
|
|
||||||
export async function getCssConflictDiagnostics(
|
export async function getCssConflictDiagnostics(
|
||||||
state: State,
|
state: State,
|
||||||
|
@ -24,16 +55,17 @@ export async function getCssConflictDiagnostics(
|
||||||
|
|
||||||
classNames.forEach((className, index) => {
|
classNames.forEach((className, index) => {
|
||||||
if (state.jit) {
|
if (state.jit) {
|
||||||
let { rules } = jit.generateRules(state, [className.className])
|
let { rules } = jit.generateRules(
|
||||||
|
state,
|
||||||
|
[className.className],
|
||||||
|
(rule) => !isKeyframes(rule)
|
||||||
|
)
|
||||||
if (rules.length === 0) {
|
if (rules.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let info: Array<{ context: string[]; properties: string[] }> = rules.map((rule) => {
|
let info: Array<{ context: string[]; properties: string[] }> = rules.map((rule) => {
|
||||||
let properties: string[] = []
|
let properties = getRuleProperties(rule)
|
||||||
rule.walkDecls(({ prop }) => {
|
|
||||||
properties.push(prop)
|
|
||||||
})
|
|
||||||
let context = jit.getRuleContext(state, rule, className.className)
|
let context = jit.getRuleContext(state, rule, className.className)
|
||||||
return { context, properties }
|
return { context, properties }
|
||||||
})
|
})
|
||||||
|
@ -41,21 +73,22 @@ export async function getCssConflictDiagnostics(
|
||||||
let otherClassNames = classNames.filter((_className, i) => i !== index)
|
let otherClassNames = classNames.filter((_className, i) => i !== index)
|
||||||
|
|
||||||
let conflictingClassNames = otherClassNames.filter((otherClassName) => {
|
let conflictingClassNames = otherClassNames.filter((otherClassName) => {
|
||||||
let { rules: otherRules } = jit.generateRules(state, [otherClassName.className])
|
let { rules: otherRules } = jit.generateRules(
|
||||||
|
state,
|
||||||
|
[otherClassName.className],
|
||||||
|
(rule) => !isKeyframes(rule)
|
||||||
|
)
|
||||||
if (otherRules.length !== rules.length) {
|
if (otherRules.length !== rules.length) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < otherRules.length; i++) {
|
for (let i = 0; i < otherRules.length; i++) {
|
||||||
let rule = otherRules[i]
|
let otherRule = otherRules[i]
|
||||||
let properties: string[] = []
|
let properties = getRuleProperties(otherRule)
|
||||||
rule.walkDecls(({ prop }) => {
|
|
||||||
properties.push(prop)
|
|
||||||
})
|
|
||||||
if (!equal(info[i].properties, properties)) {
|
if (!equal(info[i].properties, properties)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let context = jit.getRuleContext(state, rule, otherClassName.className)
|
let context = jit.getRuleContext(state, otherRule, otherClassName.className)
|
||||||
if (!equal(info[i].context, context)) {
|
if (!equal(info[i].context, context)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { State } from './state'
|
import { State } from './state'
|
||||||
import type { Container, Document, Root, Rule } from 'postcss'
|
import type { Container, Document, Root, Rule, Node, AtRule } from 'postcss'
|
||||||
import dlv from 'dlv'
|
|
||||||
import { remToPx } from './remToPx'
|
import { remToPx } from './remToPx'
|
||||||
|
|
||||||
export function bigSign(bigIntValue) {
|
export function bigSign(bigIntValue) {
|
||||||
|
@ -8,7 +7,11 @@ export function bigSign(bigIntValue) {
|
||||||
return (bigIntValue > 0n) - (bigIntValue < 0n)
|
return (bigIntValue > 0n) - (bigIntValue < 0n)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateRules(state: State, classNames: string[]): { root: Root; rules: Rule[] } {
|
export function generateRules(
|
||||||
|
state: State,
|
||||||
|
classNames: string[],
|
||||||
|
filter: (rule: Rule) => boolean = () => true
|
||||||
|
): { root: Root; rules: Rule[] } {
|
||||||
let rules: [bigint, Rule][] = state.modules.jit.generateRules
|
let rules: [bigint, Rule][] = state.modules.jit.generateRules
|
||||||
.module(new Set(classNames), state.jitContext)
|
.module(new Set(classNames), state.jitContext)
|
||||||
.sort(([a], [z]) => bigSign(a - z))
|
.sort(([a], [z]) => bigSign(a - z))
|
||||||
|
@ -18,7 +21,9 @@ export function generateRules(state: State, classNames: string[]): { root: Root;
|
||||||
|
|
||||||
let actualRules: Rule[] = []
|
let actualRules: Rule[] = []
|
||||||
root.walkRules((subRule) => {
|
root.walkRules((subRule) => {
|
||||||
|
if (filter(subRule)) {
|
||||||
actualRules.push(subRule)
|
actualRules.push(subRule)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -84,14 +89,17 @@ function replaceClassName(state: State, selector: string, find: string, replace:
|
||||||
return state.modules.postcssSelectorParser.module(transform).processSync(selector)
|
return state.modules.postcssSelectorParser.module(transform).processSync(selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAtRule(node: Node): node is AtRule {
|
||||||
|
return node.type === 'atrule'
|
||||||
|
}
|
||||||
|
|
||||||
export function getRuleContext(state: State, rule: Rule, className: string): string[] {
|
export function getRuleContext(state: State, rule: Rule, className: string): string[] {
|
||||||
let context: string[] = [replaceClassName(state, rule.selector, className, '__placeholder__')]
|
let context: string[] = [replaceClassName(state, rule.selector, className, '__placeholder__')]
|
||||||
|
|
||||||
let p: Container | Document = rule
|
let p: Container | Document = rule
|
||||||
while (p.parent && p.parent.type !== 'root') {
|
while (p.parent && p.parent.type !== 'root') {
|
||||||
p = p.parent
|
p = p.parent
|
||||||
if (p.type === 'atrule') {
|
if (isAtRule(p)) {
|
||||||
// @ts-ignore
|
|
||||||
context.unshift(`@${p.name} ${p.params}`)
|
context.unshift(`@${p.name} ${p.params}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue