@@ -9,7 +9,7 @@ module.exports = {
99 url : 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-sort-invocations.md' ,
1010 templateMode : 'both' ,
1111 } ,
12- fixable : null ,
12+ fixable : 'code' ,
1313 schema : [ ] ,
1414 messages : {
1515 attributeOrder : '`{{attributeName}}` must appear after `{{expectedAfter}}`' ,
@@ -26,6 +26,24 @@ module.exports = {
2626 } ,
2727
2828 create ( context ) {
29+ const sourceCode = context . sourceCode ;
30+
31+ // Glimmer attribute and modifier nodes can have trailing whitespace
32+ // absorbed into their range, so we strip the trailing whitespace from
33+ // each side and re-append it at the end of the swapped output.
34+ function createSwapFix ( fixer , nodeA , nodeB ) {
35+ const rawA = sourceCode . getText ( nodeA ) ;
36+ const rawB = sourceCode . getText ( nodeB ) ;
37+ const contentA = rawA . trimEnd ( ) ;
38+ const contentB = rawB . trimEnd ( ) ;
39+ const separator = sourceCode . text . slice ( nodeA . range [ 0 ] + contentA . length , nodeB . range [ 0 ] ) ;
40+ const trailing = rawB . slice ( contentB . length ) ;
41+ return fixer . replaceTextRange (
42+ [ nodeA . range [ 0 ] , nodeB . range [ 1 ] ] ,
43+ contentB + separator + contentA + trailing
44+ ) ;
45+ }
46+
2947 function getAttributeName ( node ) {
3048 return node . name ;
3149 }
@@ -171,6 +189,9 @@ module.exports = {
171189 attributeName : getAttributeName ( attributes [ index ] ) ,
172190 expectedAfter : getAttributeName ( attributes [ index + 1 ] ) ,
173191 } ,
192+ fix ( fixer ) {
193+ return createSwapFix ( fixer , attributes [ index ] , attributes [ index + 1 ] ) ;
194+ } ,
174195 } ) ;
175196 }
176197 }
@@ -186,13 +207,28 @@ module.exports = {
186207 modifierName : getModifierName ( modifiers [ index ] ) ,
187208 expectedAfter : getModifierName ( modifiers [ index + 1 ] ) ,
188209 } ,
210+ fix ( fixer ) {
211+ return createSwapFix ( fixer , modifiers [ index ] , modifiers [ index + 1 ] ) ;
212+ } ,
189213 } ) ;
190214 }
191215 }
192216
193217 if ( ! canSkipSplattributesLast ( node ) ) {
194218 const splattributes = attributes . at ( - 1 ) ;
195219
220+ // Swap ...attributes past the first modifier that appears after it;
221+ // ESLint's fix loop continues until splattributes is fully sorted.
222+ // canSkipSplattributesLast guarantees at least one such modifier exists.
223+ const firstModifierAfter = modifiers . find (
224+ ( mod ) =>
225+ mod . loc . start . line > splattributes . loc . start . line ||
226+ ( mod . loc . start . line === splattributes . loc . start . line &&
227+ mod . loc . start . column > splattributes . loc . start . column )
228+ ) ;
229+
230+ const splatFixFn = ( fixer ) => createSwapFix ( fixer , splattributes , firstModifierAfter ) ;
231+
196232 // When ...attributes is the only attribute, report as attributeOrder
197233 // (the ordering issue is that ...attributes should appear after modifiers)
198234 if ( attributes . length === 1 ) {
@@ -203,11 +239,13 @@ module.exports = {
203239 attributeName : '...attributes' ,
204240 expectedAfter : 'modifiers' ,
205241 } ,
242+ fix : splatFixFn ,
206243 } ) ;
207244 } else {
208245 context . report ( {
209246 node : splattributes ,
210247 messageId : 'splattributesOrder' ,
248+ fix : splatFixFn ,
211249 } ) ;
212250 }
213251 }
@@ -225,6 +263,9 @@ module.exports = {
225263 hashPairName : getHashPairName ( node . hash . pairs [ index ] ) ,
226264 expectedAfter : getHashPairName ( node . hash . pairs [ index + 1 ] ) ,
227265 } ,
266+ fix ( fixer ) {
267+ return createSwapFix ( fixer , node . hash . pairs [ index ] , node . hash . pairs [ index + 1 ] ) ;
268+ } ,
228269 } ) ;
229270 }
230271 }
@@ -244,6 +285,10 @@ module.exports = {
244285 node . params . length > 0 &&
245286 node . params [ 0 ] . type === 'GlimmerStringLiteral' ;
246287
288+ const fixFn = function ( fixer ) {
289+ return createSwapFix ( fixer , node . hash . pairs [ index ] , node . hash . pairs [ index + 1 ] ) ;
290+ } ;
291+
247292 if ( isComponentInvocation ) {
248293 context . report ( {
249294 node : node . hash . pairs [ index ] ,
@@ -252,6 +297,7 @@ module.exports = {
252297 attributeName : getHashPairName ( node . hash . pairs [ index ] ) ,
253298 expectedAfter : getHashPairName ( node . hash . pairs [ index + 1 ] ) ,
254299 } ,
300+ fix : fixFn ,
255301 } ) ;
256302 } else {
257303 context . report ( {
@@ -261,6 +307,7 @@ module.exports = {
261307 hashPairName : getHashPairName ( node . hash . pairs [ index ] ) ,
262308 expectedAfter : getHashPairName ( node . hash . pairs [ index + 1 ] ) ,
263309 } ,
310+ fix : fixFn ,
264311 } ) ;
265312 }
266313 }
@@ -279,6 +326,9 @@ module.exports = {
279326 hashPairName : getHashPairName ( node . hash . pairs [ index ] ) ,
280327 expectedAfter : getHashPairName ( node . hash . pairs [ index + 1 ] ) ,
281328 } ,
329+ fix ( fixer ) {
330+ return createSwapFix ( fixer , node . hash . pairs [ index ] , node . hash . pairs [ index + 1 ] ) ;
331+ } ,
282332 } ) ;
283333 }
284334 }
0 commit comments