add initial quick fix for utility conflict diagnostic

master
Brad Cornes 2020-06-18 12:23:31 +01:00
parent b79dbfc9f9
commit 01941f967b
2 changed files with 125 additions and 55 deletions

View File

@ -22,7 +22,10 @@ import {
isInvalidApplyDiagnostic, isInvalidApplyDiagnostic,
AugmentedDiagnostic, AugmentedDiagnostic,
InvalidApplyDiagnostic, InvalidApplyDiagnostic,
isUtilityConflictsDiagnostic,
UtilityConflictsDiagnostic,
} from '../diagnostics/types' } from '../diagnostics/types'
import { flatten, dedupeBy } from '../../../util/array'
async function getDiagnosticsFromCodeActionParams( async function getDiagnosticsFromCodeActionParams(
state: State, state: State,
@ -35,7 +38,11 @@ async function getDiagnosticsFromCodeActionParams(
return params.context.diagnostics return params.context.diagnostics
.map((diagnostic) => { .map((diagnostic) => {
return diagnostics.find((d) => { return diagnostics.find((d) => {
return rangesEqual(d.range, diagnostic.range) return (
d.code === diagnostic.code &&
d.message === diagnostic.message &&
rangesEqual(d.range, diagnostic.range)
)
}) })
}) })
.filter(Boolean) .filter(Boolean)
@ -55,40 +62,46 @@ export async function provideCodeActions(
codes codes
) )
return Promise.all( let actions = diagnostics.map((diagnostic) => {
diagnostics if (isInvalidApplyDiagnostic(diagnostic)) {
.map((diagnostic) => { return provideInvalidApplyCodeActions(state, params, diagnostic)
if (isInvalidApplyDiagnostic(diagnostic)) { }
return provideInvalidApplyCodeAction(state, params, diagnostic)
}
let match = findLast( if (isUtilityConflictsDiagnostic(diagnostic)) {
/ Did you mean (?:something like )?'(?<replacement>[^']+)'\?$/g, return provideUtilityConflictsCodeActions(state, params, diagnostic)
diagnostic.message }
)
if (!match) { let match = findLast(
return null / Did you mean (?:something like )?'(?<replacement>[^']+)'\?$/g,
} diagnostic.message
)
return { if (!match) {
title: `Replace with '${match.groups.replacement}'`, return []
kind: CodeActionKind.QuickFix, }
diagnostics: [diagnostic],
edit: { return [
changes: { {
[params.textDocument.uri]: [ title: `Replace with '${match.groups.replacement}'`,
{ kind: CodeActionKind.QuickFix,
range: diagnostic.range, diagnostics: [diagnostic],
newText: match.groups.replacement, edit: {
}, changes: {
], [params.textDocument.uri]: [
}, {
range: diagnostic.range,
newText: match.groups.replacement,
},
],
}, },
} },
}) },
.filter(Boolean) ]
) })
return Promise.all(actions)
.then(flatten)
.then((x) => dedupeBy(x, (item) => JSON.stringify(item.edit)))
} }
function classNameToAst( function classNameToAst(
@ -154,11 +167,56 @@ function classNameToAst(
return cssObjToAst(obj, state.modules.postcss) return cssObjToAst(obj, state.modules.postcss)
} }
async function provideInvalidApplyCodeAction( async function provideUtilityConflictsCodeActions(
state: State,
params: CodeActionParams,
diagnostic: UtilityConflictsDiagnostic
): Promise<CodeAction[]> {
return [
{
title: `Delete '${diagnostic.className.className}'`,
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
edit: {
changes: {
[params.textDocument.uri]: [
{
range: diagnostic.className.classList.range,
newText: removeRangeFromString(
diagnostic.className.classList.classList,
diagnostic.className.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
),
},
],
},
},
},
]
}
async function provideInvalidApplyCodeActions(
state: State, state: State,
params: CodeActionParams, params: CodeActionParams,
diagnostic: InvalidApplyDiagnostic diagnostic: InvalidApplyDiagnostic
): Promise<CodeAction> { ): Promise<CodeAction[]> {
let document = state.editor.documents.get(params.textDocument.uri) let document = state.editor.documents.get(params.textDocument.uri)
let documentText = document.getText() let documentText = document.getText()
const { postcss } = state.modules const { postcss } = state.modules
@ -250,30 +308,32 @@ async function provideInvalidApplyCodeAction(
]).process(documentText, { from: undefined }) ]).process(documentText, { from: undefined })
if (!change) { if (!change) {
return null return []
} }
return { return [
title: 'Extract to new rule.', {
kind: CodeActionKind.QuickFix, title: 'Extract to new rule',
diagnostics: [diagnostic], kind: CodeActionKind.QuickFix,
edit: { diagnostics: [diagnostic],
changes: { edit: {
[params.textDocument.uri]: [ changes: {
...(totalClassNamesInClassList > 1 [params.textDocument.uri]: [
? [ ...(totalClassNamesInClassList > 1
{ ? [
range: diagnostic.className.classList.range, {
newText: removeRangeFromString( range: diagnostic.className.classList.range,
diagnostic.className.classList.classList, newText: removeRangeFromString(
diagnostic.className.relativeRange diagnostic.className.classList.classList,
), diagnostic.className.relativeRange
}, ),
] },
: []), ]
change, : []),
], change,
],
},
}, },
}, },
} ]
} }

View File

@ -2,6 +2,16 @@ export function dedupe<T>(arr: Array<T>): Array<T> {
return arr.filter((value, index, self) => self.indexOf(value) === index) return arr.filter((value, index, self) => self.indexOf(value) === index)
} }
export function dedupeBy<T>(
arr: Array<T>,
transform: (item: T) => any
): Array<T> {
return arr.filter(
(value, index, self) =>
self.map(transform).indexOf(transform(value)) === index
)
}
export function ensureArray<T>(value: T | T[]): T[] { export function ensureArray<T>(value: T | T[]): T[] {
return Array.isArray(value) ? value : [value] return Array.isArray(value) ? value : [value]
} }