Skip to content

Commit c4ac906

Browse files
Merge pull request #2649 from johanrd/autofix/sort-invocations
Post-merge-review: Restore autofix for template-sort-invocations
2 parents 0d41d3c + 96e57e1 commit c4ac906

4 files changed

Lines changed: 1014 additions & 38 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ rules in templates can be disabled with eslint directives with mustache or html
284284
| [template-self-closing-void-elements](docs/rules/template-self-closing-void-elements.md) | require self-closing on void elements | | 🔧 | |
285285
| [template-simple-modifiers](docs/rules/template-simple-modifiers.md) | require simple modifier syntax | | | |
286286
| [template-simple-unless](docs/rules/template-simple-unless.md) | require simple conditions in unless blocks | | | |
287-
| [template-sort-invocations](docs/rules/template-sort-invocations.md) | require sorted attributes and modifiers | | | |
287+
| [template-sort-invocations](docs/rules/template-sort-invocations.md) | require sorted attributes and modifiers | | 🔧 | |
288288
| [template-splat-attributes-only](docs/rules/template-splat-attributes-only.md) | disallow ...spread other than ...attributes | | | |
289289
| [template-style-concatenation](docs/rules/template-style-concatenation.md) | disallow string concatenation in inline styles | | | |
290290

docs/rules/template-sort-invocations.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ember/template-sort-invocations
22

3+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
4+
35
<!-- end auto-generated rule header -->
46

57
## Why use it?

lib/rules/template-sort-invocations.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)