Skip to content

Commit fcabcaa

Browse files
Merge pull request #2536 from johanrd/autofix-complex/block-indentation
Restore autofix: `template-block-indentation`
2 parents 29a20e8 + 9e36ec5 commit fcabcaa

4 files changed

Lines changed: 102 additions & 29 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ rules in templates can be disabled with eslint directives with mustache or html
406406
| [order-in-routes](docs/rules/order-in-routes.md) | enforce proper order of properties in routes | | 🔧 | |
407407
| [template-attribute-indentation](docs/rules/template-attribute-indentation.md) | enforce proper indentation of attributes and arguments in multi-line templates | | | |
408408
| [template-attribute-order](docs/rules/template-attribute-order.md) | enforce consistent ordering of attributes in template elements | | 🔧 | |
409-
| [template-block-indentation](docs/rules/template-block-indentation.md) | enforce consistent indentation for block statements and their children | | | |
409+
| [template-block-indentation](docs/rules/template-block-indentation.md) | enforce consistent indentation for block statements and their children | | 🔧 | |
410410
| [template-eol-last](docs/rules/template-eol-last.md) | require or disallow newline at the end of template files | | 🔧 | |
411411
| [template-linebreak-style](docs/rules/template-linebreak-style.md) | enforce consistent linebreaks in templates | | 🔧 | |
412412
| [template-modifier-name-case](docs/rules/template-modifier-name-case.md) | require dasherized names for modifiers | | 🔧 | |

docs/rules/template-block-indentation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ember/template-block-indentation
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
Migrated from [ember-template-lint/block-indentation](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/block-indentation.md).

lib/rules/template-block-indentation.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ module.exports = {
130130
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-block-indentation.md',
131131
templateMode: 'both',
132132
},
133-
fixable: null,
133+
fixable: 'code',
134134
schema: [
135135
{
136136
oneOf: [
@@ -183,6 +183,39 @@ module.exports = {
183183
const elseIfBlocks = new WeakSet();
184184
let templateRange = null;
185185

186+
// Precompute line start offsets for fixing
187+
const lineOffsets = [0];
188+
for (const [i, char] of [...sourceText].entries()) {
189+
if (char === '\n') {
190+
lineOffsets.push(i + 1);
191+
}
192+
}
193+
194+
/**
195+
* Build a fix that replaces the leading whitespace of a line with the expected indentation.
196+
*/
197+
function makeIndentFix(line, expectedColumn, actualColumn) {
198+
const lineIndex = line - 1;
199+
if (lineIndex < 0 || lineIndex >= lineOffsets.length) {
200+
return undefined;
201+
}
202+
const lineStart = lineOffsets[lineIndex];
203+
const lineText = sourceLines[lineIndex] ?? '';
204+
const firstNonWhitespace = lineText.search(/\S/);
205+
if (firstNonWhitespace < 0) {
206+
return undefined;
207+
}
208+
// Only fix if the reported column matches the line's leading whitespace
209+
// (skip cases where the element is mid-line, e.g. content followed by </div>)
210+
if (actualColumn !== undefined && firstNonWhitespace !== actualColumn) {
211+
return undefined;
212+
}
213+
const indentChar = config.indentation === 1 ? '\t' : ' ';
214+
const expectedIndent = indentChar.repeat(expectedColumn);
215+
return (fixer) =>
216+
fixer.replaceTextRange([lineStart, lineStart + firstNonWhitespace], expectedIndent);
217+
}
218+
186219
function isWithinIgnoredElement() {
187220
return elementStack.some((n) => IGNORED_ELEMENTS.has(n.tag));
188221
}
@@ -259,6 +292,7 @@ module.exports = {
259292
expectedColumn: expectedEndColumn,
260293
actualColumn: correctedEndColumn,
261294
},
295+
fix: makeIndentFix(node.loc.end.line, expectedEndColumn, correctedEndColumn),
262296
});
263297
}
264298
}
@@ -342,6 +376,7 @@ module.exports = {
342376
expectedColumn: expectedStartColumn,
343377
actualColumn: childStartColumn,
344378
},
379+
fix: makeIndentFix(childStartLine, expectedStartColumn, childStartColumn),
345380
});
346381
}
347382
}
@@ -377,6 +412,7 @@ module.exports = {
377412
actualColumn: elseStartColumn,
378413
else: 'else',
379414
},
415+
fix: makeIndentFix(node.inverse.loc.start.line, expectedStartColumn, elseStartColumn),
380416
});
381417
}
382418
}

tests/lib/rules/template-block-indentation.js

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -314,63 +314,72 @@ describe('template-block-indentation', () => {
314314
// Incorrect end indentation
315315
{
316316
code: ['{{#if foo}}', ' bar', ' {{/if}}'].join('\n'),
317-
output: null,
317+
318+
output: '{{#if foo}}\n bar\n{{/if}}',
318319
errors: [{ messageId: 'incorrectEnd' }],
319320
},
320321

321322
// Incorrect child indentation - missing indent
322323
{
323324
code: ['<div>', '<p>{{t "greeting"}}</p>', '</div>'].join('\n'),
324-
output: null,
325+
326+
output: '<div>\n <p>{{t "greeting"}}</p>\n</div>',
325327
errors: [{ messageId: 'incorrectChild' }],
326328
},
327329

328330
// Incorrect child indentation - too much
329331
{
330332
code: ['<div>', ' <p>{{t "greeting"}}</p>', '</div>'].join('\n'),
331-
output: null,
333+
334+
output: '<div>\n <p>{{t "greeting"}}</p>\n</div>',
332335
errors: [{ messageId: 'incorrectChild' }],
333336
},
334337

335338
// Incorrect end indentation for element
336339
{
337340
code: ['<div>', ' <p>content</p>', ' </div>'].join('\n'),
338-
output: null,
341+
342+
output: '<div>\n <p>content</p>\n</div>',
339343
errors: [{ messageId: 'incorrectEnd' }],
340344
},
341345

342346
// Incorrect else indentation
343347
{
344348
code: ['{{#if foo}}', ' bar', ' {{else}}', ' baz', '{{/if}}'].join('\n'),
345-
output: null,
349+
350+
output: '{{#if foo}}\n bar\n{{else}}\n baz\n{{/if}}',
346351
errors: [{ messageId: 'incorrectElse' }],
347352
},
348353

349354
// Incorrect indentation with 4-space config
350355
{
351356
code: ['<div>', ' <p>Hello</p>', '</div>'].join('\n'),
352-
output: null,
357+
358+
output: '<div>\n <p>Hello</p>\n</div>',
353359
options: [4],
354360
errors: [{ messageId: 'incorrectChild' }],
355361
},
356362
{
357363
code: ['<div>', ' <p>Hi!</p>', '</div>'].join('\n'),
358-
output: null,
364+
365+
output: '<div>\n <p>Hi!</p>\n</div>',
359366
options: [4],
360367
errors: [{ messageId: 'incorrectChild' }],
361368
},
362369

363370
// Multiple errors - wrong children and end
364371
{
365372
code: ['<div>', 'foo', ' </div>'].join('\n'),
366-
output: null,
373+
374+
output: '<div>\n foo\n</div>',
367375
errors: [{ messageId: 'incorrectChild' }, { messageId: 'incorrectEnd' }],
368376
},
369377

370378
// Nested indentation error
371379
{
372380
code: ['{{#if foo}}', ' {{#if bar}}', ' baz', ' {{/if}}', '{{/if}}'].join('\n'),
373-
output: null,
381+
382+
output: '{{#if foo}}\n {{#if bar}}\n baz\n {{/if}}\n{{/if}}',
374383
errors: [{ messageId: 'incorrectChild' }],
375384
},
376385

@@ -379,21 +388,24 @@ describe('template-block-indentation', () => {
379388
// Closing tag on same line as content but wrong indent
380389
{
381390
code: '<div>\n <p>Stuff goes here</p></div>',
391+
382392
output: null,
383393
errors: [{ messageId: 'incorrectEnd' }],
384394
},
385395

386396
// Child not indented in element
387397
{
388398
code: '<div>\n<p>Stuff goes here</p>\n</div>',
389-
output: null,
399+
400+
output: '<div>\n <p>Stuff goes here</p>\n</div>',
390401
errors: [{ messageId: 'incorrectChild' }],
391402
},
392403

393404
// Child not indented in block
394405
{
395406
code: '{{#if}}\n<p>Stuff goes here</p>\n{{/if}}',
396-
output: null,
407+
408+
output: '{{#if}}\n <p>Stuff goes here</p>\n{{/if}}',
397409
errors: [{ messageId: 'incorrectChild' }],
398410
},
399411

@@ -407,42 +419,50 @@ describe('template-block-indentation', () => {
407419
' {{/if}}',
408420
'{{/if}}',
409421
].join('\n'),
410-
output: null,
422+
423+
output:
424+
'{{#if isMorning}}\n{{else}}\n {{#if something}}\n Good night\n {{/if}}\n{{/if}}',
411425
errors: [{ messageId: 'incorrectEnd' }],
412426
},
413427

414428
// Mixed indent - some children correct, some not
415429
{
416430
code: '<div>\n {{foo}}\n{{bar}}\n</div>',
417-
output: null,
431+
432+
output: '<div>\n {{foo}}\n {{bar}}\n</div>',
418433
errors: [{ messageId: 'incorrectChild' }],
419434
},
420435

421436
// Text child not indented
422437
{
423438
code: '<div>\n Foo:\n{{bar}}\n</div>',
424-
output: null,
439+
440+
output: '<div>\n Foo:\n {{bar}}\n</div>',
425441
errors: [{ messageId: 'incorrectChild' }],
426442
},
427443

428444
// Closing block ends at wrong column when preceded by content on same line
429445
{
430446
code: '<div>\n <span>Foo</span>{{#some-thing}}\n {{/some-thing}}\n</div>',
431-
output: null,
447+
448+
output:
449+
'<div>\n <span>Foo</span>{{#some-thing}}\n {{/some-thing}}\n</div>',
432450
errors: [{ messageId: 'incorrectEnd' }],
433451
},
434452

435453
// Element closing tag at wrong indent when preceded by content
436454
{
437455
code: '{{#if foo}}\n {{foo}} <p>\n Bar\n </p>\n{{/if}}',
438-
output: null,
456+
457+
output: '{{#if foo}}\n {{foo}} <p>\n Bar\n </p>\n{{/if}}',
439458
errors: [{ messageId: 'incorrectEnd' }],
440459
},
441460

442461
// Else block indentation error
443462
{
444463
code: ['{{#if foo}}', ' {{else}}', '{{/if}}'].join('\n'),
445-
output: null,
464+
465+
output: '{{#if foo}}\n{{else}}\n{{/if}}',
446466
errors: [{ messageId: 'incorrectElse' }],
447467
},
448468

@@ -456,14 +476,16 @@ describe('template-block-indentation', () => {
456476
' {{/if~}}',
457477
' {{/if}}',
458478
].join('\n'),
459-
output: null,
479+
480+
output: '{{#if foo}}\n{{else if bar}}\n{{else}}\n {{#if baz}}\n {{/if~}}\n{{/if}}',
460481
errors: [{ messageId: 'incorrectEnd' }],
461482
},
462483

463484
// Each with wrong else indent
464485
{
465486
code: ['{{#each foo as |bar|}}', ' {{else}}', '{{/each}}'].join('\n'),
466-
output: null,
487+
488+
output: '{{#each foo as |bar|}}\n{{else}}\n{{/each}}',
467489
errors: [{ messageId: 'incorrectElse' }],
468490
},
469491

@@ -478,13 +500,16 @@ describe('template-block-indentation', () => {
478500
' Good afternoon',
479501
'{{/if}}',
480502
].join('\n'),
481-
output: null,
503+
504+
output:
505+
'{{#if isMorning}}\n Good morning\n{{else if isAfternoon~}}\n Good afternoon\n{{/if}}',
482506
errors: [{ messageId: 'incorrectChild' }],
483507
},
484508

485509
// Inline else with wrong position
486510
{
487511
code: ['{{#if foo}}foo{{else}}', ' bar', '{{/if}}'].join('\n'),
512+
488513
output: null,
489514
errors: [{ messageId: 'incorrectChild' }, { messageId: 'incorrectElse' }],
490515
},
@@ -497,14 +522,17 @@ describe('template-block-indentation', () => {
497522
' {{foobar.baz}}',
498523
'{{/foo}}',
499524
].join('\n'),
500-
output: null,
525+
526+
output:
527+
'{{#foo bar as |foobar|}}\n {{#foobar.baz}}{{/foobar.baz}}\n {{foobar.baz}}\n{{/foo}}',
501528
errors: [{ messageId: 'incorrectChild' }, { messageId: 'incorrectChild' }],
502529
},
503530

504531
// ignoreComments: true still catches non-comment child errors
505532
{
506533
code: ['<div>', 'test{{! Comment }}', '</div>'].join('\n'),
507-
output: null,
534+
535+
output: '<div>\n test{{! Comment }}\n</div>',
508536
options: [{ ignoreComments: true }],
509537
errors: [{ messageId: 'incorrectChild' }],
510538
},
@@ -558,7 +586,8 @@ describe('template-block-indentation', () => {
558586
// Incorrect child indentation in GJS
559587
{
560588
code: ['<template>', '<div>', '<p>Hello</p>', '</div>', '</template>'].join('\n'),
561-
output: null,
589+
590+
output: '<template>\n<div>\n <p>Hello</p>\n</div>\n</template>',
562591
errors: [{ messageId: 'incorrectChild' }],
563592
},
564593

@@ -573,7 +602,9 @@ describe('template-block-indentation', () => {
573602
' {{/if}}',
574603
'</template>',
575604
].join('\n'),
576-
output: null,
605+
606+
output:
607+
'<template>\n {{#if foo}}\n {{foo}}\n {{else if bar}}\n {{bar}}\n {{/if}}\n</template>',
577608
errors: [{ messageId: 'incorrectElse' }, { messageId: 'incorrectChild' }],
578609
},
579610

@@ -590,7 +621,9 @@ describe('template-block-indentation', () => {
590621
' {{/if}}',
591622
'</template>',
592623
].join('\n'),
593-
output: null,
624+
625+
output:
626+
'<template>\n {{#if a}}\n {{#if foo}}\n {{foo}}\n {{else if bar}}\n {{bar}}\n {{/if}}\n {{/if}}\n</template>',
594627
errors: [{ messageId: 'incorrectElse' }, { messageId: 'incorrectChild' }],
595628
},
596629
],
@@ -624,7 +657,8 @@ describe('template-block-indentation', () => {
624657
// 2-space indent is wrong when editorconfig says indent_size=4
625658
{
626659
code: ['<div>', ' <p>Hi!</p>', '</div>'].join('\n'),
627-
output: null,
660+
661+
output: '<div>\n <p>Hi!</p>\n</div>',
628662
errors: [{ messageId: 'incorrectChild' }],
629663
},
630664
],
@@ -648,7 +682,8 @@ describe('template-block-indentation', () => {
648682
// 4-space indent is wrong when explicit option says 2
649683
{
650684
code: ['<div>', ' <p>Hi!</p>', '</div>'].join('\n'),
651-
output: null,
685+
686+
output: '<div>\n <p>Hi!</p>\n</div>',
652687
options: [2],
653688
errors: [{ messageId: 'incorrectChild' }],
654689
},

0 commit comments

Comments
 (0)