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