@@ -8,6 +8,11 @@ const utils = require('../utils')
8
8
// ------------------------------------------------------------------------------
9
9
// Rule Definition
10
10
// ------------------------------------------------------------------------------
11
+
12
+ /**
13
+ * @typedef { VDirective & { key: VDirectiveKey & { name: VIdentifier & { name: 'bind' } } } } VBindDirective
14
+ */
15
+
11
16
const ATTRS = {
12
17
DEFINITION : 'DEFINITION' ,
13
18
LIST_RENDERING : 'LIST_RENDERING' ,
@@ -22,13 +27,47 @@ const ATTRS = {
22
27
CONTENT : 'CONTENT'
23
28
}
24
29
30
+ /**
31
+ * Check whether the given attribute is `v-bind` directive.
32
+ * @param {VAttribute | VDirective | undefined | null } node
33
+ * @returns { node is VBindDirective }
34
+ */
35
+ function isVBind ( node ) {
36
+ return Boolean ( node && node . directive && node . key . name . name === 'bind' )
37
+ }
38
+ /**
39
+ * Check whether the given attribute is plain attribute.
40
+ * @param {VAttribute | VDirective | undefined | null } node
41
+ * @returns { node is VAttribute }
42
+ */
43
+ function isVAttribute ( node ) {
44
+ return Boolean ( node && ! node . directive )
45
+ }
46
+ /**
47
+ * Check whether the given attribute is plain attribute or `v-bind` directive.
48
+ * @param {VAttribute | VDirective | undefined | null } node
49
+ * @returns { node is VAttribute }
50
+ */
51
+ function isVAttributeOrVBind ( node ) {
52
+ return isVAttribute ( node ) || isVBind ( node )
53
+ }
54
+
55
+ /**
56
+ * Check whether the given attribute is `v-bind="..."` directive.
57
+ * @param {VAttribute | VDirective | undefined | null } node
58
+ * @returns { node is VBindDirective }
59
+ */
60
+ function isVBindObject ( node ) {
61
+ return isVBind ( node ) && node . key . argument == null
62
+ }
63
+
25
64
/**
26
65
* @param {VAttribute | VDirective } attribute
27
66
* @param {SourceCode } sourceCode
28
67
*/
29
68
function getAttributeName ( attribute , sourceCode ) {
30
69
if ( attribute . directive ) {
31
- if ( attribute . key . name . name === 'bind' ) {
70
+ if ( isVBind ( attribute ) ) {
32
71
return attribute . key . argument
33
72
? sourceCode . getText ( attribute . key . argument )
34
73
: ''
@@ -62,7 +101,7 @@ function getDirectiveKeyName(directiveKey, sourceCode) {
62
101
function getAttributeType ( attribute , sourceCode ) {
63
102
let propName
64
103
if ( attribute . directive ) {
65
- if ( attribute . key . name . name !== 'bind' ) {
104
+ if ( ! isVBind ( attribute ) ) {
66
105
const name = attribute . key . name . name
67
106
if ( name === 'for' ) {
68
107
return ATTRS . LIST_RENDERING
@@ -130,24 +169,14 @@ function getPosition(attribute, attributePosition, sourceCode) {
130
169
* @param {SourceCode } sourceCode
131
170
*/
132
171
function isAlphabetical ( prevNode , currNode , sourceCode ) {
133
- const isSameType =
134
- getAttributeType ( prevNode , sourceCode ) ===
135
- getAttributeType ( currNode , sourceCode )
136
- if ( isSameType ) {
137
- const prevName = getAttributeName ( prevNode , sourceCode )
138
- const currName = getAttributeName ( currNode , sourceCode )
139
- if ( prevName === currName ) {
140
- const prevIsBind = Boolean (
141
- prevNode . directive && prevNode . key . name . name === 'bind'
142
- )
143
- const currIsBind = Boolean (
144
- currNode . directive && currNode . key . name . name === 'bind'
145
- )
146
- return prevIsBind <= currIsBind
147
- }
148
- return prevName < currName
172
+ const prevName = getAttributeName ( prevNode , sourceCode )
173
+ const currName = getAttributeName ( currNode , sourceCode )
174
+ if ( prevName === currName ) {
175
+ const prevIsBind = isVBind ( prevNode )
176
+ const currIsBind = isVBind ( currNode )
177
+ return prevIsBind <= currIsBind
149
178
}
150
- return true
179
+ return prevName < currName
151
180
}
152
181
153
182
/**
@@ -186,16 +215,6 @@ function create(context) {
186
215
} else attributePosition [ item ] = i
187
216
} )
188
217
189
- /**
190
- * @typedef {object } State
191
- * @property {number } currentPosition
192
- * @property {VAttribute | VDirective } previousNode
193
- */
194
- /**
195
- * @type {State | null }
196
- */
197
- let state
198
-
199
218
/**
200
219
* @param {VAttribute | VDirective } node
201
220
* @param {VAttribute | VDirective } previousNode
@@ -213,43 +232,112 @@ function create(context) {
213
232
214
233
fix ( fixer ) {
215
234
const attributes = node . parent . attributes
216
- const shiftAttrs = attributes . slice (
235
+
236
+ /** @type { (node: VAttribute | VDirective | undefined) => boolean } */
237
+ let isMoveUp
238
+
239
+ if ( isVBindObject ( node ) ) {
240
+ // prev, v-bind:foo, v-bind -> v-bind:foo, v-bind, prev
241
+ isMoveUp = isVAttributeOrVBind
242
+ } else if ( isVAttributeOrVBind ( node ) ) {
243
+ // prev, v-bind, v-bind:foo -> v-bind, v-bind:foo, prev
244
+ isMoveUp = isVBindObject
245
+ } else {
246
+ isMoveUp = ( ) => false
247
+ }
248
+
249
+ const previousNodes = attributes . slice (
217
250
attributes . indexOf ( previousNode ) ,
218
- attributes . indexOf ( node ) + 1
251
+ attributes . indexOf ( node )
219
252
)
253
+ const moveUpNodes = [ node ]
254
+ const moveDownNodes = [ ]
255
+ let index = 0
256
+ while ( previousNodes [ index ] ) {
257
+ const node = previousNodes [ index ++ ]
258
+ if ( isMoveUp ( node ) ) {
259
+ moveUpNodes . unshift ( node )
260
+ } else {
261
+ moveDownNodes . push ( node )
262
+ }
263
+ }
264
+ const moveNodes = [ ...moveUpNodes , ...moveDownNodes ]
220
265
221
- return shiftAttrs . map ( ( attr , i ) => {
222
- const text =
223
- attr === previousNode
224
- ? sourceCode . getText ( node )
225
- : sourceCode . getText ( shiftAttrs [ i - 1 ] )
226
- return fixer . replaceText ( attr , text )
266
+ return moveNodes . map ( ( moveNode , index ) => {
267
+ const text = sourceCode . getText ( moveNode )
268
+ return fixer . replaceText ( previousNodes [ index ] || node , text )
227
269
} )
228
270
}
229
271
} )
230
272
}
231
273
232
274
return utils . defineTemplateBodyVisitor ( context , {
233
- VStartTag ( ) {
234
- state = null
235
- } ,
236
- VAttribute ( node ) {
237
- let inAlphaOrder = true
238
- if ( state && alphabetical ) {
239
- inAlphaOrder = isAlphabetical ( state . previousNode , node , sourceCode )
275
+ VStartTag ( node ) {
276
+ const attributes = node . attributes . filter ( ( node , index , attributes ) => {
277
+ if (
278
+ isVBindObject ( node ) &&
279
+ ( isVAttributeOrVBind ( attributes [ index - 1 ] ) ||
280
+ isVAttributeOrVBind ( attributes [ index + 1 ] ) )
281
+ ) {
282
+ // In Vue 3, ignore the `v-bind:foo=" ... "` and `v-bind ="object"` syntax
283
+ // as they behave differently if you change the order.
284
+ return false
285
+ }
286
+ return true
287
+ } )
288
+ if ( attributes . length <= 1 ) {
289
+ return
240
290
}
241
- if (
242
- ! state ||
243
- ( state . currentPosition <=
244
- getPosition ( node , attributePosition , sourceCode ) &&
245
- inAlphaOrder )
246
- ) {
247
- state = {
248
- currentPosition : getPosition ( node , attributePosition , sourceCode ) ,
249
- previousNode : node
291
+
292
+ let previousNode = attributes [ 0 ]
293
+ let previousPosition = getPositionFromAttrIndex ( 0 )
294
+ for ( let index = 1 ; index < attributes . length ; index ++ ) {
295
+ const node = attributes [ index ]
296
+ const position = getPositionFromAttrIndex ( index )
297
+
298
+ let valid = previousPosition <= position
299
+ if ( valid && alphabetical && previousPosition === position ) {
300
+ valid = isAlphabetical ( previousNode , node , sourceCode )
250
301
}
251
- } else {
252
- reportIssue ( node , state . previousNode )
302
+ if ( valid ) {
303
+ previousNode = node
304
+ previousPosition = position
305
+ } else {
306
+ reportIssue ( node , previousNode )
307
+ }
308
+ }
309
+
310
+ /**
311
+ * @param {number } index
312
+ * @returns {number }
313
+ */
314
+ function getPositionFromAttrIndex ( index ) {
315
+ const node = attributes [ index ]
316
+ if ( isVBindObject ( node ) ) {
317
+ // node is `v-bind ="object"` syntax
318
+
319
+ // In Vue 3, if change the order of `v-bind:foo=" ... "` and `v-bind ="object"`,
320
+ // the behavior will be different, so adjust so that there is no change in behavior.
321
+
322
+ const len = attributes . length
323
+ for ( let nextIndex = index + 1 ; nextIndex < len ; nextIndex ++ ) {
324
+ const next = attributes [ nextIndex ]
325
+
326
+ if ( isVAttributeOrVBind ( next ) && ! isVBindObject ( next ) ) {
327
+ // It is considered to be in the same order as the next bind prop node.
328
+ return getPositionFromAttrIndex ( nextIndex )
329
+ }
330
+ }
331
+ for ( let prevIndex = index - 1 ; prevIndex >= 0 ; prevIndex -- ) {
332
+ const prev = attributes [ prevIndex ]
333
+
334
+ if ( isVAttributeOrVBind ( prev ) && ! isVBindObject ( prev ) ) {
335
+ // It is considered to be in the same order as the prev bind prop node.
336
+ return getPositionFromAttrIndex ( prevIndex )
337
+ }
338
+ }
339
+ }
340
+ return getPosition ( node , attributePosition , sourceCode )
253
341
}
254
342
}
255
343
} )
0 commit comments