Skip to content

Commit 614154c

Browse files
committed
tidy up code actions
1 parent a0c6229 commit 614154c

File tree

4 files changed

+332
-298
lines changed

4 files changed

+332
-298
lines changed
Lines changed: 29 additions & 298 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,21 @@
1-
import {
2-
CodeAction,
3-
CodeActionParams,
4-
CodeActionKind,
5-
Range,
6-
TextEdit,
7-
} from 'vscode-languageserver'
1+
import { CodeAction, CodeActionParams } from 'vscode-languageserver'
82
import { State } from '../../util/state'
9-
import { isWithinRange } from '../../util/isWithinRange'
10-
import { getClassNameParts } from '../../util/getClassNameAtPosition'
11-
const dlv = require('dlv')
12-
import dset from 'dset'
13-
import { removeRangesFromString } from '../../util/removeRangesFromString'
14-
import detectIndent from 'detect-indent'
15-
import { cssObjToAst } from '../../util/cssObjToAst'
16-
import isObject from '../../../util/isObject'
173
import { getDiagnostics } from '../diagnostics/diagnosticsProvider'
184
import { rangesEqual } from '../../util/rangesEqual'
195
import {
206
DiagnosticKind,
217
isInvalidApplyDiagnostic,
228
AugmentedDiagnostic,
23-
InvalidApplyDiagnostic,
249
isUtilityConflictsDiagnostic,
25-
UtilityConflictsDiagnostic,
2610
isInvalidConfigPathDiagnostic,
2711
isInvalidTailwindDirectiveDiagnostic,
2812
isInvalidScreenDiagnostic,
2913
isInvalidVariantDiagnostic,
3014
} from '../diagnostics/types'
3115
import { flatten, dedupeBy } from '../../../util/array'
32-
import { joinWithAnd } from '../../util/joinWithAnd'
33-
import { getLanguageBoundaries } from '../../util/getLanguageBoundaries'
34-
import { isCssDoc } from '../../util/css'
35-
import { absoluteRange } from '../../util/absoluteRange'
36-
import type { NodeSource, Root } from 'postcss'
16+
import { provideUtilityConflictsCodeActions } from './provideUtilityConflictsCodeActions'
17+
import { provideInvalidApplyCodeActions } from './provideInvalidApplyCodeActions'
18+
import { provideSuggestionCodeActions } from './provideSuggestionCodeActions'
3719

3820
async function getDiagnosticsFromCodeActionParams(
3921
state: State,
@@ -60,287 +42,36 @@ export async function provideCodeActions(
6042
state: State,
6143
params: CodeActionParams
6244
): Promise<CodeAction[]> {
63-
let codes = params.context.diagnostics
64-
.map((diagnostic) => diagnostic.code)
65-
.filter(Boolean) as DiagnosticKind[]
66-
6745
let diagnostics = await getDiagnosticsFromCodeActionParams(
6846
state,
6947
params,
70-
codes
48+
params.context.diagnostics
49+
.map((diagnostic) => diagnostic.code)
50+
.filter(Boolean) as DiagnosticKind[]
7151
)
7252

73-
let actions = diagnostics.map((diagnostic) => {
74-
if (isInvalidApplyDiagnostic(diagnostic)) {
75-
return provideInvalidApplyCodeActions(state, params, diagnostic)
76-
}
77-
78-
if (isUtilityConflictsDiagnostic(diagnostic)) {
79-
return provideUtilityConflictsCodeActions(state, params, diagnostic)
80-
}
81-
82-
if (
83-
isInvalidConfigPathDiagnostic(diagnostic) ||
84-
isInvalidTailwindDirectiveDiagnostic(diagnostic) ||
85-
isInvalidScreenDiagnostic(diagnostic) ||
86-
isInvalidVariantDiagnostic(diagnostic)
87-
) {
88-
return diagnostic.suggestions.map((suggestion) => ({
89-
title: `Replace with '${suggestion}'`,
90-
kind: CodeActionKind.QuickFix,
91-
diagnostics: [diagnostic],
92-
edit: {
93-
changes: {
94-
[params.textDocument.uri]: [
95-
{
96-
range: diagnostic.range,
97-
newText: suggestion,
98-
},
99-
],
100-
},
101-
},
102-
}))
103-
}
104-
105-
return []
106-
})
107-
108-
return Promise.all(actions)
53+
return Promise.all(
54+
diagnostics.map((diagnostic) => {
55+
if (isInvalidApplyDiagnostic(diagnostic)) {
56+
return provideInvalidApplyCodeActions(state, params, diagnostic)
57+
}
58+
59+
if (isUtilityConflictsDiagnostic(diagnostic)) {
60+
return provideUtilityConflictsCodeActions(state, params, diagnostic)
61+
}
62+
63+
if (
64+
isInvalidConfigPathDiagnostic(diagnostic) ||
65+
isInvalidTailwindDirectiveDiagnostic(diagnostic) ||
66+
isInvalidScreenDiagnostic(diagnostic) ||
67+
isInvalidVariantDiagnostic(diagnostic)
68+
) {
69+
return provideSuggestionCodeActions(state, params, diagnostic)
70+
}
71+
72+
return []
73+
})
74+
)
10975
.then(flatten)
11076
.then((x) => dedupeBy(x, (item) => JSON.stringify(item.edit)))
11177
}
112-
113-
function classNameToAst(
114-
state: State,
115-
classNameParts: string[],
116-
selector: string,
117-
important: boolean = false
118-
) {
119-
const baseClassName = dlv(
120-
state.classNames.classNames,
121-
classNameParts[classNameParts.length - 1]
122-
)
123-
if (!baseClassName) {
124-
return null
125-
}
126-
const info = dlv(state.classNames.classNames, classNameParts)
127-
let context = info.__context || []
128-
let pseudo = info.__pseudo || []
129-
const globalContexts = state.classNames.context
130-
let screens = dlv(
131-
state.config,
132-
'theme.screens',
133-
dlv(state.config, 'screens', {})
134-
)
135-
if (!isObject(screens)) screens = {}
136-
screens = Object.keys(screens)
137-
const path = []
138-
139-
for (let i = 0; i < classNameParts.length - 1; i++) {
140-
let part = classNameParts[i]
141-
let common = globalContexts[part]
142-
if (!common) return null
143-
if (screens.includes(part)) {
144-
path.push(`@screen ${part}`)
145-
context = context.filter((con) => !common.includes(con))
146-
}
147-
}
148-
149-
path.push(...context)
150-
151-
let obj = {}
152-
for (let i = 1; i <= path.length; i++) {
153-
dset(obj, path.slice(0, i), {})
154-
}
155-
let rule = {
156-
// TODO: use proper selector parser
157-
[selector + pseudo.join('')]: {
158-
[`@apply ${classNameParts[classNameParts.length - 1]}${
159-
important ? ' !important' : ''
160-
}`]: '',
161-
},
162-
}
163-
if (path.length) {
164-
dset(obj, path, rule)
165-
} else {
166-
obj = rule
167-
}
168-
169-
return cssObjToAst(obj, state.modules.postcss)
170-
}
171-
172-
async function provideUtilityConflictsCodeActions(
173-
state: State,
174-
params: CodeActionParams,
175-
diagnostic: UtilityConflictsDiagnostic
176-
): Promise<CodeAction[]> {
177-
return [
178-
{
179-
title: `Delete ${joinWithAnd(
180-
diagnostic.otherClassNames.map(
181-
(otherClassName) => `'${otherClassName.className}'`
182-
)
183-
)}`,
184-
kind: CodeActionKind.QuickFix,
185-
diagnostics: [diagnostic],
186-
edit: {
187-
changes: {
188-
[params.textDocument.uri]: [
189-
{
190-
range: diagnostic.className.classList.range,
191-
newText: removeRangesFromString(
192-
diagnostic.className.classList.classList,
193-
diagnostic.otherClassNames.map(
194-
(otherClassName) => otherClassName.relativeRange
195-
)
196-
),
197-
},
198-
],
199-
},
200-
},
201-
},
202-
]
203-
}
204-
205-
function postcssSourceToRange(source: NodeSource): Range {
206-
return {
207-
start: {
208-
line: source.start.line - 1,
209-
character: source.start.column - 1,
210-
},
211-
end: {
212-
line: source.end.line - 1,
213-
character: source.end.column,
214-
},
215-
}
216-
}
217-
218-
async function provideInvalidApplyCodeActions(
219-
state: State,
220-
params: CodeActionParams,
221-
diagnostic: InvalidApplyDiagnostic
222-
): Promise<CodeAction[]> {
223-
let document = state.editor.documents.get(params.textDocument.uri)
224-
let documentText = document.getText()
225-
let cssRange: Range
226-
let cssText = documentText
227-
const { postcss } = state.modules
228-
let changes: TextEdit[] = []
229-
230-
let totalClassNamesInClassList = diagnostic.className.classList.classList.split(
231-
/\s+/
232-
).length
233-
234-
let className = diagnostic.className.className
235-
let classNameParts = getClassNameParts(state, className)
236-
let classNameInfo = dlv(state.classNames.classNames, classNameParts)
237-
238-
if (Array.isArray(classNameInfo)) {
239-
return []
240-
}
241-
242-
if (!isCssDoc(state, document)) {
243-
let languageBoundaries = getLanguageBoundaries(state, document)
244-
if (!languageBoundaries) return []
245-
cssRange = languageBoundaries.css.find((range) =>
246-
isWithinRange(diagnostic.range.start, range)
247-
)
248-
if (!cssRange) return []
249-
cssText = document.getText(cssRange)
250-
}
251-
252-
try {
253-
await postcss([
254-
postcss.plugin('', (_options = {}) => {
255-
return (root: Root) => {
256-
root.walkRules((rule) => {
257-
if (changes.length) return false
258-
259-
rule.walkAtRules('apply', (atRule) => {
260-
let atRuleRange = postcssSourceToRange(atRule.source)
261-
if (cssRange) {
262-
atRuleRange = absoluteRange(atRuleRange, cssRange)
263-
}
264-
265-
if (!isWithinRange(diagnostic.range.start, atRuleRange))
266-
return true
267-
268-
let ast = classNameToAst(
269-
state,
270-
classNameParts,
271-
rule.selector,
272-
diagnostic.className.classList.important
273-
)
274-
275-
if (!ast) return false
276-
277-
rule.after(ast.nodes)
278-
let insertedRule = rule.next()
279-
if (!insertedRule) return false
280-
281-
if (totalClassNamesInClassList === 1) {
282-
atRule.remove()
283-
} else {
284-
changes.push({
285-
range: diagnostic.className.classList.range,
286-
newText: removeRangesFromString(
287-
diagnostic.className.classList.classList,
288-
diagnostic.className.relativeRange
289-
),
290-
})
291-
}
292-
293-
let ruleRange = postcssSourceToRange(rule.source)
294-
if (cssRange) {
295-
ruleRange = absoluteRange(ruleRange, cssRange)
296-
}
297-
298-
let outputIndent: string
299-
let documentIndent = detectIndent(documentText)
300-
301-
changes.push({
302-
range: ruleRange,
303-
newText:
304-
rule.toString() +
305-
(insertedRule.raws.before || '\n\n') +
306-
insertedRule
307-
.toString()
308-
.replace(/\n\s*\n/g, '\n')
309-
.replace(/(@apply [^;\n]+)$/gm, '$1;')
310-
.replace(/([^\s^]){$/gm, '$1 {')
311-
.replace(/^\s+/gm, (m: string) => {
312-
if (typeof outputIndent === 'undefined') outputIndent = m
313-
return m.replace(
314-
new RegExp(outputIndent, 'g'),
315-
documentIndent.indent
316-
)
317-
}),
318-
})
319-
320-
return false
321-
})
322-
})
323-
}
324-
}),
325-
]).process(cssText, { from: undefined })
326-
} catch (_) {
327-
return []
328-
}
329-
330-
if (!changes.length) {
331-
return []
332-
}
333-
334-
return [
335-
{
336-
title: 'Extract to new rule',
337-
kind: CodeActionKind.QuickFix,
338-
diagnostics: [diagnostic],
339-
edit: {
340-
changes: {
341-
[params.textDocument.uri]: changes,
342-
},
343-
},
344-
},
345-
]
346-
}

0 commit comments

Comments
 (0)