Skip to content

Commit 7b916ab

Browse files
committed
consolidate conflict diagnostics and update quick fixes
1 parent 01941f9 commit 7b916ab

File tree

6 files changed

+105
-68
lines changed

6 files changed

+105
-68
lines changed

src/lsp/providers/codeActionProvider/index.ts

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { isWithinRange } from '../../util/isWithinRange'
1111
import { getClassNameParts } from '../../util/getClassNameAtPosition'
1212
const dlv = require('dlv')
1313
import dset from 'dset'
14-
import { removeRangeFromString } from '../../util/removeRangeFromString'
14+
import { removeRangesFromString } from '../../util/removeRangesFromString'
1515
import detectIndent from 'detect-indent'
1616
import { cssObjToAst } from '../../util/cssObjToAst'
1717
import isObject from '../../../util/isObject'
@@ -26,6 +26,7 @@ import {
2626
UtilityConflictsDiagnostic,
2727
} from '../diagnostics/types'
2828
import { flatten, dedupeBy } from '../../../util/array'
29+
import { joinWithAnd } from '../../util/joinWithAnd'
2930

3031
async function getDiagnosticsFromCodeActionParams(
3132
state: State,
@@ -174,35 +175,23 @@ async function provideUtilityConflictsCodeActions(
174175
): Promise<CodeAction[]> {
175176
return [
176177
{
177-
title: `Delete '${diagnostic.className.className}'`,
178-
kind: CodeActionKind.QuickFix,
179-
diagnostics: [diagnostic],
180-
edit: {
181-
changes: {
182-
[params.textDocument.uri]: [
183-
{
184-
range: diagnostic.className.classList.range,
185-
newText: removeRangeFromString(
186-
diagnostic.className.classList.classList,
187-
diagnostic.className.relativeRange
188-
),
189-
},
190-
],
191-
},
192-
},
193-
},
194-
{
195-
title: `Delete '${diagnostic.otherClassName.className}'`,
178+
title: `Delete ${joinWithAnd(
179+
diagnostic.otherClassNames.map(
180+
(otherClassName) => `'${otherClassName.className}'`
181+
)
182+
)}`,
196183
kind: CodeActionKind.QuickFix,
197184
diagnostics: [diagnostic],
198185
edit: {
199186
changes: {
200187
[params.textDocument.uri]: [
201188
{
202189
range: diagnostic.className.classList.range,
203-
newText: removeRangeFromString(
190+
newText: removeRangesFromString(
204191
diagnostic.className.classList.classList,
205-
diagnostic.otherClassName.relativeRange
192+
diagnostic.otherClassNames.map(
193+
(otherClassName) => otherClassName.relativeRange
194+
)
206195
),
207196
},
208197
],
@@ -323,7 +312,7 @@ async function provideInvalidApplyCodeActions(
323312
? [
324313
{
325314
range: diagnostic.className.classList.range,
326-
newText: removeRangeFromString(
315+
newText: removeRangesFromString(
327316
diagnostic.className.classList.classList,
328317
diagnostic.className.relativeRange
329318
),

src/lsp/providers/diagnostics/diagnosticsProvider.ts

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
InvalidTailwindDirectiveDiagnostic,
3030
AugmentedDiagnostic,
3131
} from './types'
32+
import { joinWithAnd } from '../../util/joinWithAnd'
3233

3334
function getInvalidApplyDiagnostics(
3435
state: State,
@@ -104,45 +105,58 @@ function getUtilityConflictDiagnostics(
104105
const classNames = getClassNamesInClassList(classList)
105106

106107
classNames.forEach((className, index) => {
108+
let decls = getClassNameDecls(state, className.className)
109+
if (!decls) return
110+
111+
let properties = Object.keys(decls)
112+
let meta = getClassNameMeta(state, className.className)
113+
107114
let otherClassNames = classNames.filter((_className, i) => i !== index)
108-
otherClassNames.forEach((otherClassName) => {
109-
let decls = getClassNameDecls(state, className.className)
110-
if (!decls) return
111115

116+
let conflictingClassNames = otherClassNames.filter((otherClassName) => {
112117
let otherDecls = getClassNameDecls(state, otherClassName.className)
113-
if (!otherDecls) return
118+
if (!otherDecls) return false
114119

115-
let meta = getClassNameMeta(state, className.className)
116120
let otherMeta = getClassNameMeta(state, otherClassName.className)
117121

118-
if (
119-
equal(Object.keys(decls), Object.keys(otherDecls)) &&
122+
return (
123+
equal(properties, Object.keys(otherDecls)) &&
120124
!Array.isArray(meta) &&
121125
!Array.isArray(otherMeta) &&
122126
equal(meta.context, otherMeta.context) &&
123127
equal(meta.pseudo, otherMeta.pseudo)
124-
) {
125-
diagnostics.push({
126-
code: DiagnosticKind.UtilityConflicts,
127-
className,
128-
otherClassName,
129-
range: className.range,
130-
severity:
131-
severity === 'error'
132-
? DiagnosticSeverity.Error
133-
: DiagnosticSeverity.Warning,
134-
message: `'${className.className}' and '${otherClassName.className}' apply the same CSS properties.`,
135-
relatedInformation: [
136-
{
137-
message: otherClassName.className,
138-
location: {
139-
uri: document.uri,
140-
range: otherClassName.range,
141-
},
128+
)
129+
})
130+
131+
if (conflictingClassNames.length === 0) return
132+
133+
diagnostics.push({
134+
code: DiagnosticKind.UtilityConflicts,
135+
className,
136+
otherClassNames: conflictingClassNames,
137+
range: className.range,
138+
severity:
139+
severity === 'error'
140+
? DiagnosticSeverity.Error
141+
: DiagnosticSeverity.Warning,
142+
message: `'${className.className}' applies the same CSS ${
143+
properties.length === 1 ? 'property' : 'properties'
144+
} as ${joinWithAnd(
145+
conflictingClassNames.map(
146+
(conflictingClassName) => `'${conflictingClassName.className}'`
147+
)
148+
)}.`,
149+
relatedInformation: conflictingClassNames.map(
150+
(conflictingClassName) => {
151+
return {
152+
message: conflictingClassName.className,
153+
location: {
154+
uri: document.uri,
155+
range: conflictingClassName.range,
142156
},
143-
],
144-
})
145-
}
157+
}
158+
}
159+
),
146160
})
147161
})
148162
})

src/lsp/providers/diagnostics/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export enum DiagnosticKind {
1313
export type UtilityConflictsDiagnostic = Diagnostic & {
1414
code: DiagnosticKind.UtilityConflicts
1515
className: DocumentClassName
16-
otherClassName: DocumentClassName
16+
otherClassNames: DocumentClassName[]
1717
}
1818

1919
export function isUtilityConflictsDiagnostic(

src/lsp/util/joinWithAnd.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export function joinWithAnd(strings: string[]): string {
2+
return strings.reduce((acc, cur, i) => {
3+
if (i === 0) {
4+
return cur
5+
}
6+
if (strings.length > 1 && i === strings.length - 1) {
7+
return `${acc} and ${cur}`
8+
}
9+
return `${acc}, ${cur}`
10+
}, '')
11+
}

src/lsp/util/removeRangeFromString.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Range } from 'vscode-languageserver'
2+
import lineColumn from 'line-column'
3+
import { ensureArray } from '../../util/array'
4+
5+
export function removeRangesFromString(
6+
str: string,
7+
rangeOrRanges: Range | Range[]
8+
): string {
9+
let ranges = ensureArray(rangeOrRanges)
10+
let finder = lineColumn(str + '\n', { origin: 0 })
11+
let indexRanges: { start: number; end: number }[] = []
12+
13+
ranges.forEach((range) => {
14+
let start = finder.toIndex(range.start.line, range.start.character)
15+
let end = finder.toIndex(range.end.line, range.end.character)
16+
for (let i = start - 1; i >= 0; i--) {
17+
if (/\s/.test(str.charAt(i))) {
18+
start = i
19+
} else {
20+
break
21+
}
22+
}
23+
indexRanges.push({ start, end })
24+
})
25+
26+
indexRanges.sort((a, b) => a.start - b.start)
27+
28+
let result = ''
29+
let i = 0
30+
31+
indexRanges.forEach((indexRange) => {
32+
result += str.substring(i, indexRange.start)
33+
i = indexRange.end
34+
})
35+
36+
result += str.substring(i)
37+
38+
return result.trim()
39+
}

0 commit comments

Comments
 (0)