consolidate conflict diagnostics and update quick fixes

master
Brad Cornes 2020-06-18 14:22:28 +01:00
parent 01941f967b
commit 7b916ab3c6
6 changed files with 105 additions and 68 deletions

View File

@ -11,7 +11,7 @@ import { isWithinRange } from '../../util/isWithinRange'
import { getClassNameParts } from '../../util/getClassNameAtPosition' import { getClassNameParts } from '../../util/getClassNameAtPosition'
const dlv = require('dlv') const dlv = require('dlv')
import dset from 'dset' import dset from 'dset'
import { removeRangeFromString } from '../../util/removeRangeFromString' import { removeRangesFromString } from '../../util/removeRangesFromString'
import detectIndent from 'detect-indent' import detectIndent from 'detect-indent'
import { cssObjToAst } from '../../util/cssObjToAst' import { cssObjToAst } from '../../util/cssObjToAst'
import isObject from '../../../util/isObject' import isObject from '../../../util/isObject'
@ -26,6 +26,7 @@ import {
UtilityConflictsDiagnostic, UtilityConflictsDiagnostic,
} from '../diagnostics/types' } from '../diagnostics/types'
import { flatten, dedupeBy } from '../../../util/array' import { flatten, dedupeBy } from '../../../util/array'
import { joinWithAnd } from '../../util/joinWithAnd'
async function getDiagnosticsFromCodeActionParams( async function getDiagnosticsFromCodeActionParams(
state: State, state: State,
@ -174,7 +175,11 @@ async function provideUtilityConflictsCodeActions(
): Promise<CodeAction[]> { ): Promise<CodeAction[]> {
return [ return [
{ {
title: `Delete '${diagnostic.className.className}'`, title: `Delete ${joinWithAnd(
diagnostic.otherClassNames.map(
(otherClassName) => `'${otherClassName.className}'`
)
)}`,
kind: CodeActionKind.QuickFix, kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic], diagnostics: [diagnostic],
edit: { edit: {
@ -182,27 +187,11 @@ async function provideUtilityConflictsCodeActions(
[params.textDocument.uri]: [ [params.textDocument.uri]: [
{ {
range: diagnostic.className.classList.range, range: diagnostic.className.classList.range,
newText: removeRangeFromString( newText: removeRangesFromString(
diagnostic.className.classList.classList, diagnostic.className.classList.classList,
diagnostic.className.relativeRange diagnostic.otherClassNames.map(
), (otherClassName) => otherClassName.relativeRange
}, )
],
},
},
},
{
title: `Delete '${diagnostic.otherClassName.className}'`,
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
edit: {
changes: {
[params.textDocument.uri]: [
{
range: diagnostic.className.classList.range,
newText: removeRangeFromString(
diagnostic.className.classList.classList,
diagnostic.otherClassName.relativeRange
), ),
}, },
], ],
@ -323,7 +312,7 @@ async function provideInvalidApplyCodeActions(
? [ ? [
{ {
range: diagnostic.className.classList.range, range: diagnostic.className.classList.range,
newText: removeRangeFromString( newText: removeRangesFromString(
diagnostic.className.classList.classList, diagnostic.className.classList.classList,
diagnostic.className.relativeRange diagnostic.className.relativeRange
), ),

View File

@ -29,6 +29,7 @@ import {
InvalidTailwindDirectiveDiagnostic, InvalidTailwindDirectiveDiagnostic,
AugmentedDiagnostic, AugmentedDiagnostic,
} from './types' } from './types'
import { joinWithAnd } from '../../util/joinWithAnd'
function getInvalidApplyDiagnostics( function getInvalidApplyDiagnostics(
state: State, state: State,
@ -104,45 +105,58 @@ function getUtilityConflictDiagnostics(
const classNames = getClassNamesInClassList(classList) const classNames = getClassNamesInClassList(classList)
classNames.forEach((className, index) => { classNames.forEach((className, index) => {
let otherClassNames = classNames.filter((_className, i) => i !== index)
otherClassNames.forEach((otherClassName) => {
let decls = getClassNameDecls(state, className.className) let decls = getClassNameDecls(state, className.className)
if (!decls) return if (!decls) return
let otherDecls = getClassNameDecls(state, otherClassName.className) let properties = Object.keys(decls)
if (!otherDecls) return
let meta = getClassNameMeta(state, className.className) let meta = getClassNameMeta(state, className.className)
let otherClassNames = classNames.filter((_className, i) => i !== index)
let conflictingClassNames = otherClassNames.filter((otherClassName) => {
let otherDecls = getClassNameDecls(state, otherClassName.className)
if (!otherDecls) return false
let otherMeta = getClassNameMeta(state, otherClassName.className) let otherMeta = getClassNameMeta(state, otherClassName.className)
if ( return (
equal(Object.keys(decls), Object.keys(otherDecls)) && equal(properties, Object.keys(otherDecls)) &&
!Array.isArray(meta) && !Array.isArray(meta) &&
!Array.isArray(otherMeta) && !Array.isArray(otherMeta) &&
equal(meta.context, otherMeta.context) && equal(meta.context, otherMeta.context) &&
equal(meta.pseudo, otherMeta.pseudo) equal(meta.pseudo, otherMeta.pseudo)
) { )
})
if (conflictingClassNames.length === 0) return
diagnostics.push({ diagnostics.push({
code: DiagnosticKind.UtilityConflicts, code: DiagnosticKind.UtilityConflicts,
className, className,
otherClassName, otherClassNames: conflictingClassNames,
range: className.range, range: className.range,
severity: severity:
severity === 'error' severity === 'error'
? DiagnosticSeverity.Error ? DiagnosticSeverity.Error
: DiagnosticSeverity.Warning, : DiagnosticSeverity.Warning,
message: `'${className.className}' and '${otherClassName.className}' apply the same CSS properties.`, message: `'${className.className}' applies the same CSS ${
relatedInformation: [ properties.length === 1 ? 'property' : 'properties'
{ } as ${joinWithAnd(
message: otherClassName.className, conflictingClassNames.map(
(conflictingClassName) => `'${conflictingClassName.className}'`
)
)}.`,
relatedInformation: conflictingClassNames.map(
(conflictingClassName) => {
return {
message: conflictingClassName.className,
location: { location: {
uri: document.uri, uri: document.uri,
range: otherClassName.range, range: conflictingClassName.range,
}, },
},
],
})
} }
}
),
}) })
}) })
}) })

View File

@ -13,7 +13,7 @@ export enum DiagnosticKind {
export type UtilityConflictsDiagnostic = Diagnostic & { export type UtilityConflictsDiagnostic = Diagnostic & {
code: DiagnosticKind.UtilityConflicts code: DiagnosticKind.UtilityConflicts
className: DocumentClassName className: DocumentClassName
otherClassName: DocumentClassName otherClassNames: DocumentClassName[]
} }
export function isUtilityConflictsDiagnostic( export function isUtilityConflictsDiagnostic(

View File

@ -0,0 +1,11 @@
export function joinWithAnd(strings: string[]): string {
return strings.reduce((acc, cur, i) => {
if (i === 0) {
return cur
}
if (strings.length > 1 && i === strings.length - 1) {
return `${acc} and ${cur}`
}
return `${acc}, ${cur}`
}, '')
}

View File

@ -1,16 +0,0 @@
import { Range } from 'vscode-languageserver'
import lineColumn from 'line-column'
export function removeRangeFromString(str: string, range: Range): string {
let finder = lineColumn(str + '\n', { origin: 0 })
let start = finder.toIndex(range.start.line, range.start.character)
let end = finder.toIndex(range.end.line, range.end.character)
for (let i = start - 1; i >= 0; i--) {
if (/\s/.test(str.charAt(i))) {
start = i
} else {
break
}
}
return (str.substr(0, start) + str.substr(end)).trim()
}

View File

@ -0,0 +1,39 @@
import { Range } from 'vscode-languageserver'
import lineColumn from 'line-column'
import { ensureArray } from '../../util/array'
export function removeRangesFromString(
str: string,
rangeOrRanges: Range | Range[]
): string {
let ranges = ensureArray(rangeOrRanges)
let finder = lineColumn(str + '\n', { origin: 0 })
let indexRanges: { start: number; end: number }[] = []
ranges.forEach((range) => {
let start = finder.toIndex(range.start.line, range.start.character)
let end = finder.toIndex(range.end.line, range.end.character)
for (let i = start - 1; i >= 0; i--) {
if (/\s/.test(str.charAt(i))) {
start = i
} else {
break
}
}
indexRanges.push({ start, end })
})
indexRanges.sort((a, b) => a.start - b.start)
let result = ''
let i = 0
indexRanges.forEach((indexRange) => {
result += str.substring(i, indexRange.start)
i = indexRange.end
})
result += str.substring(i)
return result.trim()
}