@@ -8,6 +8,11 @@ const utils = require('../utils')
88// ------------------------------------------------------------------------------
99// Rule Definition
1010// ------------------------------------------------------------------------------
11+
12+ /**
13+ * @typedef { VDirective & { key: VDirectiveKey & { name: VIdentifier & { name: 'bind' } } } } VBindDirective
14+ */
15+
1116const ATTRS = {
1217 DEFINITION : 'DEFINITION' ,
1318 LIST_RENDERING : 'LIST_RENDERING' ,
@@ -22,13 +27,47 @@ const ATTRS = {
2227 CONTENT : 'CONTENT'
2328}
2429
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+
2564/**
2665 * @param {VAttribute | VDirective } attribute
2766 * @param {SourceCode } sourceCode
2867 */
2968function getAttributeName ( attribute , sourceCode ) {
3069 if ( attribute . directive ) {
31- if ( attribute . key . name . name === 'bind' ) {
70+ if ( isVBind ( attribute ) ) {
3271 return attribute . key . argument
3372 ? sourceCode . getText ( attribute . key . argument )
3473 : ''
@@ -62,7 +101,7 @@ function getDirectiveKeyName(directiveKey, sourceCode) {
62101function getAttributeType ( attribute , sourceCode ) {
63102 let propName
64103 if ( attribute . directive ) {
65- if ( attribute . key . name . name !== 'bind' ) {
104+ if ( ! isVBind ( attribute ) ) {
66105 const name = attribute . key . name . name
67106 if ( name === 'for' ) {
68107 return ATTRS . LIST_RENDERING
@@ -130,24 +169,14 @@ function getPosition(attribute, attributePosition, sourceCode) {
130169 * @param {SourceCode } sourceCode
131170 */
132171function 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
149178 }
150- return true
179+ return prevName < currName
151180}
152181
153182/**
@@ -186,16 +215,6 @@ function create(context) {
186215 } else attributePosition [ item ] = i
187216 } )
188217
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-
199218 /**
200219 * @param {VAttribute | VDirective } node
201220 * @param {VAttribute | VDirective } previousNode
@@ -213,43 +232,112 @@ function create(context) {
213232
214233 fix ( fixer ) {
215234 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 (
217250 attributes . indexOf ( previousNode ) ,
218- attributes . indexOf ( node ) + 1
251+ attributes . indexOf ( node )
219252 )
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 ]
220265
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 )
227269 } )
228270 }
229271 } )
230272 }
231273
232274 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
240290 }
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 )
250301 }
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 )
253341 }
254342 }
255343 } )
0 commit comments