Skip to content

Commit 6fa8861

Browse files
authored
Fix CSS conflict diagnostic false negatives (#761)
1 parent 105b8b8 commit 6fa8861

File tree

2 files changed

+60
-19
lines changed

2 files changed

+60
-19
lines changed

packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,43 @@
11
import { joinWithAnd } from '../util/joinWithAnd'
22
import { State, Settings } from '../util/state'
3-
import type { TextDocument, DiagnosticSeverity } from 'vscode-languageserver'
3+
import type { TextDocument } from 'vscode-languageserver'
44
import { CssConflictDiagnostic, DiagnosticKind } from './types'
55
import { findClassListsInDocument, getClassNamesInClassList } from '../util/find'
66
import { getClassNameDecls } from '../util/getClassNameDecls'
77
import { getClassNameMeta } from '../util/getClassNameMeta'
88
import { equal } from '../util/array'
99
import * as jit from '../util/jit'
10+
import type { AtRule, Node, Rule } from 'postcss'
11+
12+
function isCustomProperty(property: string): boolean {
13+
return property.startsWith('--')
14+
}
15+
16+
function isAtRule(node: Node): node is AtRule {
17+
return node.type === 'atrule'
18+
}
19+
20+
function isKeyframes(rule: Rule): boolean {
21+
let parent = rule.parent
22+
if (!parent) {
23+
return false
24+
}
25+
if (isAtRule(parent) && parent.name === 'keyframes') {
26+
return true
27+
}
28+
return false
29+
}
30+
31+
function getRuleProperties(rule: Rule): string[] {
32+
let properties: string[] = []
33+
rule.walkDecls(({ prop }) => {
34+
properties.push(prop)
35+
})
36+
if (properties.findIndex((p) => !isCustomProperty(p)) > -1) {
37+
properties = properties.filter((p) => !isCustomProperty(p))
38+
}
39+
return properties
40+
}
1041

1142
export async function getCssConflictDiagnostics(
1243
state: State,
@@ -24,38 +55,40 @@ export async function getCssConflictDiagnostics(
2455

2556
classNames.forEach((className, index) => {
2657
if (state.jit) {
27-
let { rules } = jit.generateRules(state, [className.className])
58+
let { rules } = jit.generateRules(
59+
state,
60+
[className.className],
61+
(rule) => !isKeyframes(rule)
62+
)
2863
if (rules.length === 0) {
2964
return
3065
}
3166

3267
let info: Array<{ context: string[]; properties: string[] }> = rules.map((rule) => {
33-
let properties: string[] = []
34-
rule.walkDecls(({ prop }) => {
35-
properties.push(prop)
36-
})
68+
let properties = getRuleProperties(rule)
3769
let context = jit.getRuleContext(state, rule, className.className)
3870
return { context, properties }
3971
})
4072

4173
let otherClassNames = classNames.filter((_className, i) => i !== index)
4274

4375
let conflictingClassNames = otherClassNames.filter((otherClassName) => {
44-
let { rules: otherRules } = jit.generateRules(state, [otherClassName.className])
76+
let { rules: otherRules } = jit.generateRules(
77+
state,
78+
[otherClassName.className],
79+
(rule) => !isKeyframes(rule)
80+
)
4581
if (otherRules.length !== rules.length) {
4682
return false
4783
}
4884

4985
for (let i = 0; i < otherRules.length; i++) {
50-
let rule = otherRules[i]
51-
let properties: string[] = []
52-
rule.walkDecls(({ prop }) => {
53-
properties.push(prop)
54-
})
86+
let otherRule = otherRules[i]
87+
let properties = getRuleProperties(otherRule)
5588
if (!equal(info[i].properties, properties)) {
5689
return false
5790
}
58-
let context = jit.getRuleContext(state, rule, otherClassName.className)
91+
let context = jit.getRuleContext(state, otherRule, otherClassName.className)
5992
if (!equal(info[i].context, context)) {
6093
return false
6194
}

packages/tailwindcss-language-service/src/util/jit.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { State } from './state'
2-
import type { Container, Document, Root, Rule } from 'postcss'
3-
import dlv from 'dlv'
2+
import type { Container, Document, Root, Rule, Node, AtRule } from 'postcss'
43
import { remToPx } from './remToPx'
54

65
export function bigSign(bigIntValue) {
76
// @ts-ignore
87
return (bigIntValue > 0n) - (bigIntValue < 0n)
98
}
109

11-
export function generateRules(state: State, classNames: string[]): { root: Root; rules: Rule[] } {
10+
export function generateRules(
11+
state: State,
12+
classNames: string[],
13+
filter: (rule: Rule) => boolean = () => true
14+
): { root: Root; rules: Rule[] } {
1215
let rules: [bigint, Rule][] = state.modules.jit.generateRules
1316
.module(new Set(classNames), state.jitContext)
1417
.sort(([a], [z]) => bigSign(a - z))
@@ -18,7 +21,9 @@ export function generateRules(state: State, classNames: string[]): { root: Root;
1821

1922
let actualRules: Rule[] = []
2023
root.walkRules((subRule) => {
21-
actualRules.push(subRule)
24+
if (filter(subRule)) {
25+
actualRules.push(subRule)
26+
}
2227
})
2328

2429
return {
@@ -84,14 +89,17 @@ function replaceClassName(state: State, selector: string, find: string, replace:
8489
return state.modules.postcssSelectorParser.module(transform).processSync(selector)
8590
}
8691

92+
function isAtRule(node: Node): node is AtRule {
93+
return node.type === 'atrule'
94+
}
95+
8796
export function getRuleContext(state: State, rule: Rule, className: string): string[] {
8897
let context: string[] = [replaceClassName(state, rule.selector, className, '__placeholder__')]
8998

9099
let p: Container | Document = rule
91100
while (p.parent && p.parent.type !== 'root') {
92101
p = p.parent
93-
if (p.type === 'atrule') {
94-
// @ts-ignore
102+
if (isAtRule(p)) {
95103
context.unshift(`@${p.name} ${p.params}`)
96104
}
97105
}

0 commit comments

Comments
 (0)