1
- import {
2
- CodeAction ,
3
- CodeActionParams ,
4
- CodeActionKind ,
5
- Range ,
6
- TextEdit ,
7
- } from 'vscode-languageserver'
1
+ import { CodeAction , CodeActionParams } from 'vscode-languageserver'
8
2
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'
17
3
import { getDiagnostics } from '../diagnostics/diagnosticsProvider'
18
4
import { rangesEqual } from '../../util/rangesEqual'
19
5
import {
20
6
DiagnosticKind ,
21
7
isInvalidApplyDiagnostic ,
22
8
AugmentedDiagnostic ,
23
- InvalidApplyDiagnostic ,
24
9
isUtilityConflictsDiagnostic ,
25
- UtilityConflictsDiagnostic ,
26
10
isInvalidConfigPathDiagnostic ,
27
11
isInvalidTailwindDirectiveDiagnostic ,
28
12
isInvalidScreenDiagnostic ,
29
13
isInvalidVariantDiagnostic ,
30
14
} from '../diagnostics/types'
31
15
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'
37
19
38
20
async function getDiagnosticsFromCodeActionParams (
39
21
state : State ,
@@ -60,287 +42,36 @@ export async function provideCodeActions(
60
42
state : State ,
61
43
params : CodeActionParams
62
44
) : Promise < CodeAction [ ] > {
63
- let codes = params . context . diagnostics
64
- . map ( ( diagnostic ) => diagnostic . code )
65
- . filter ( Boolean ) as DiagnosticKind [ ]
66
-
67
45
let diagnostics = await getDiagnosticsFromCodeActionParams (
68
46
state ,
69
47
params ,
70
- codes
48
+ params . context . diagnostics
49
+ . map ( ( diagnostic ) => diagnostic . code )
50
+ . filter ( Boolean ) as DiagnosticKind [ ]
71
51
)
72
52
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
+ )
109
75
. then ( flatten )
110
76
. then ( ( x ) => dedupeBy ( x , ( item ) => JSON . stringify ( item . edit ) ) )
111
77
}
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 ( / ( @ a p p l y [ ^ ; \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