Skip to content

Commit cef5767

Browse files
committed
Add autofix for template-no-negated-condition
Transforms negated conditions: {{#if (not x)}}a{{else}}b{{/if}} → {{#if x}}b{{else}}a{{/if}} {{#unless (not x)}}a{{/unless}} → {{#if x}}a{{/if}} {{if (not x) a b}} → {{if x b a}} {{unless (not x) a}} → {{if x a}}
1 parent 3f1b905 commit cef5767

4 files changed

Lines changed: 44 additions & 30 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ rules in templates can be disabled with eslint directives with mustache or html
237237
| [template-no-multiple-empty-lines](docs/rules/template-no-multiple-empty-lines.md) | disallow multiple consecutive empty lines in templates | | | |
238238
| [template-no-mut-helper](docs/rules/template-no-mut-helper.md) | disallow usage of (mut) helper | | | |
239239
| [template-no-negated-comparison](docs/rules/template-no-negated-comparison.md) | disallow negated comparisons in templates | | | |
240-
| [template-no-negated-condition](docs/rules/template-no-negated-condition.md) | disallow negated conditions in if/unless | | | |
240+
| [template-no-negated-condition](docs/rules/template-no-negated-condition.md) | disallow negated conditions in if/unless | | 🔧 | |
241241
| [template-no-nested-splattributes](docs/rules/template-no-nested-splattributes.md) | disallow nested ...attributes usage | | | |
242242
| [template-no-obscure-array-access](docs/rules/template-no-obscure-array-access.md) | disallow obscure array access patterns like `[email protected]` | | | |
243243
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |

docs/rules/template-no-negated-condition.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ember/template-no-negated-condition
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
Disallow negated conditions in `{{#if}}` blocks. Use `{{#unless}}` instead or rewrite the condition.

lib/rules/template-no-negated-condition.js

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ function hasNestedFixableHelper(node) {
4242
);
4343
}
4444

45-
function escapeRegExp(str) {
46-
return str.replaceAll(/[$()*+.?[\\\]^{|}]/g, '\\$&');
45+
function escapeRegExp(string) {
46+
return string.replaceAll(/[$()*+.?[\\\]^{|}]/g, '\\$&');
4747
}
4848

4949
/** @type {import('eslint').Rule.RuleModule} */
@@ -110,29 +110,31 @@ module.exports = {
110110
/**
111111
* Build a fix function for block statements.
112112
*/
113-
function buildBlockFix(node, msgId) {
114-
return (fixer) => {
113+
function buildBlockFix(node, messageId) {
114+
return function fix(fixer) {
115115
const fullText = sourceCode.getText(node);
116116
const keyword = node.path.original;
117117
const notExpr = node.params[0];
118118

119-
if (msgId === 'negatedHelper') {
119+
if (messageId === 'negatedHelper') {
120120
const conditionText = getUnwrappedConditionText(notExpr, true);
121121
const newText = fullText.replace(sourceCode.getText(notExpr), conditionText);
122122
return fixer.replaceText(node, newText);
123123
}
124124

125-
if (msgId === 'flipIf') {
125+
if (messageId === 'flipIf') {
126+
// {{#if (not x)}}A{{else}}B{{/if}} -> {{#if x}}B{{else}}A{{/if}}
126127
const conditionText = getUnwrappedConditionText(notExpr, false);
127128
const programBody = node.program.body.map((n) => sourceCode.getText(n)).join('');
128129
const inverseBody = node.inverse.body.map((n) => sourceCode.getText(n)).join('');
130+
129131
return fixer.replaceText(
130132
node,
131133
`{{#${keyword} ${conditionText}}}${inverseBody}{{else}}${programBody}{{/${keyword}}}`
132134
);
133135
}
134136

135-
if (msgId === 'useIf' || msgId === 'useUnless') {
137+
if (messageId === 'useIf' || messageId === 'useUnless') {
136138
const newKeyword = keyword === 'unless' ? 'if' : 'unless';
137139
const conditionText = getUnwrappedConditionText(notExpr, false);
138140
const notExprText = escapeRegExp(sourceCode.getText(notExpr));
@@ -152,19 +154,19 @@ module.exports = {
152154
/**
153155
* Build a fix function for inline (mustache/subexpression) statements.
154156
*/
155-
function buildInlineFix(node, msgId) {
156-
return (fixer) => {
157+
function buildInlineFix(node, messageId) {
158+
return function fix(fixer) {
157159
const fullText = sourceCode.getText(node);
158160
const keyword = node.path.original;
159161
const notExpr = node.params[0];
160162

161-
if (msgId === 'negatedHelper') {
163+
if (messageId === 'negatedHelper') {
162164
const conditionText = getUnwrappedConditionText(notExpr, true);
163165
const newText = fullText.replace(sourceCode.getText(notExpr), conditionText);
164166
return fixer.replaceText(node, newText);
165167
}
166168

167-
if (msgId === 'flipIf') {
169+
if (messageId === 'flipIf') {
168170
const conditionText = getUnwrappedConditionText(notExpr, false);
169171
const param1Text = sourceCode.getText(node.params[1]);
170172
const param2Text = sourceCode.getText(node.params[2]);
@@ -177,7 +179,7 @@ module.exports = {
177179
);
178180
}
179181

180-
if (msgId === 'useIf' || msgId === 'useUnless') {
182+
if (messageId === 'useIf' || messageId === 'useUnless') {
181183
const newKeyword = keyword === 'unless' ? 'if' : 'unless';
182184
const conditionText = getUnwrappedConditionText(notExpr, false);
183185
const isSubExpr = node.type === 'GlimmerSubExpression';
@@ -250,13 +252,14 @@ module.exports = {
250252
return;
251253
}
252254

253-
// (not a b c) with multiple params - can't simply remove negation
255+
// (not a b c) with multiple params can't simply remove negation
254256
if (notExpr.params?.length > 1) {
255257
return;
256258
}
257259

258260
// Determine message
259-
const isIfElseBlock = node.type === 'GlimmerBlockStatement' && node.inverse?.body?.length > 0;
261+
const isIfElseBlock =
262+
node.type === 'GlimmerBlockStatement' && node.inverse?.body?.length > 0;
260263
const isIfElseInline = node.type !== 'GlimmerBlockStatement' && node.params?.length === 3;
261264
const shouldFlip = isIfElseBlock || isIfElseInline;
262265

tests/lib/rules/template-no-negated-condition.js

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ ruleTester.run('template-no-negated-condition', rule, {
143143
},
144144
{
145145
code: '<template><img class={{unless (not (not condition)) "some-class" "other-class"}}></template>',
146-
output: '<template><img class={{unless condition "some-class" "other-class"}}></template>',
146+
output:
147+
'<template><img class={{unless condition "some-class" "other-class"}}></template>',
147148
errors: [{ messageId: 'negatedHelper' }],
148149
},
149150
{
@@ -153,7 +154,8 @@ ruleTester.run('template-no-negated-condition', rule, {
153154
},
154155
{
155156
code: '<template>{{input class=(if (not condition) "some-class" "other-class")}}</template>',
156-
output: '<template>{{input class=(if condition "other-class" "some-class")}}</template>',
157+
output:
158+
'<template>{{input class=(if condition "other-class" "some-class")}}</template>',
157159
errors: [{ messageId: 'flipIf' }],
158160
},
159161
{
@@ -163,12 +165,14 @@ ruleTester.run('template-no-negated-condition', rule, {
163165
},
164166
{
165167
code: '<template>{{input class=(unless (not condition) "some-class" "other-class")}}</template>',
166-
output: '<template>{{input class=(if condition "some-class" "other-class")}}</template>',
168+
output:
169+
'<template>{{input class=(if condition "some-class" "other-class")}}</template>',
167170
errors: [{ messageId: 'useIf' }],
168171
},
169172
{
170173
code: '<template>{{input class=(unless (not (not condition)) "some-class" "other-class")}}</template>',
171-
output: '<template>{{input class=(unless condition "some-class" "other-class")}}</template>',
174+
output:
175+
'<template>{{input class=(unless condition "some-class" "other-class")}}</template>',
172176
errors: [{ messageId: 'negatedHelper' }],
173177
},
174178
],
@@ -236,7 +240,7 @@ hbsRuleTester.run('template-no-negated-condition', rule, {
236240
code: '{{#if (not (eq c2))}}<img>{{/if}}',
237241
options: [{ simplifyHelpers: false }],
238242
},
239-
// simplifyHelpers: false -- if with nested fixable helpers is valid.
243+
// simplifyHelpers: false if with nested fixable helpers is valid.
240244
{
241245
code: '<img class={{if (not (gte c 10)) "some-class"}}>',
242246
options: [{ simplifyHelpers: false }],
@@ -304,7 +308,8 @@ hbsRuleTester.run('template-no-negated-condition', rule, {
304308
},
305309
{
306310
code: '{{#unless (not (not condition))}}<img>{{else if (not (not condition))}}<input>{{/unless}}',
307-
output: '{{#unless condition}}<img>{{else if (not (not condition))}}<input>{{/unless}}',
311+
output:
312+
'{{#unless condition}}<img>{{else if (not (not condition))}}<input>{{/unless}}',
308313
errors: [
309314
{ message: 'Simplify unnecessary negation of helper.' },
310315
{ message: 'Simplify unnecessary negation of helper.' },
@@ -320,12 +325,14 @@ hbsRuleTester.run('template-no-negated-condition', rule, {
320325
},
321326
{
322327
code: '{{#unless (not condition)}}<img>{{else if (not condition)}}<input>{{else}}<hr>{{/unless}}',
323-
output: '{{#if condition}}<img>{{else if (not condition)}}<input>{{else}}<hr>{{/if}}',
328+
output:
329+
'{{#if condition}}<img>{{else if (not condition)}}<input>{{else}}<hr>{{/if}}',
324330
errors: [{ message: 'Change `unless (not condition)` to `if condition`.' }],
325331
},
326332
{
327333
code: '{{#unless (not condition)}}<img>{{else if (not (not c1 c2))}}<input>{{else}}<hr>{{/unless}}',
328-
output: '{{#if condition}}<img>{{else if (not (not c1 c2))}}<input>{{else}}<hr>{{/if}}',
334+
output:
335+
'{{#if condition}}<img>{{else if (not (not c1 c2))}}<input>{{else}}<hr>{{/if}}',
329336
errors: [
330337
{ message: 'Change `unless (not condition)` to `if condition`.' },
331338
{ message: 'Simplify unnecessary negation of helper.' },
@@ -339,7 +346,8 @@ hbsRuleTester.run('template-no-negated-condition', rule, {
339346
},
340347
{
341348
code: '{{#if condition}}{{else}}{{#if (not condition)}}<img>{{/if}}{{/if}}',
342-
output: '{{#if condition}}{{else}}{{#unless condition}}<img>{{/unless}}{{/if}}',
349+
output:
350+
'{{#if condition}}{{else}}{{#unless condition}}<img>{{/unless}}{{/if}}',
343351
errors: [{ message: 'Change `if (not condition)` to `unless condition`.' }],
344352
},
345353
{
@@ -419,7 +427,7 @@ hbsRuleTester.run('template-no-negated-condition', rule, {
419427
},
420428

421429
// ******************************************
422-
// simplifyHelpers: true (explicit) -- BlockStatement
430+
// simplifyHelpers: true (explicit) BlockStatement
423431
// ******************************************
424432
{
425433
// if (not (not ...)) with simplifyHelpers: true
@@ -443,22 +451,23 @@ hbsRuleTester.run('template-no-negated-condition', rule, {
443451
errors: [{ messageId: 'negatedHelper' }],
444452
},
445453
{
446-
// unless ... else if with fixable helpers -- two errors with simplifyHelpers: true
454+
// unless ... else if with fixable helpers two errors with simplifyHelpers: true
447455
code: '{{#unless (not (gt c 10))}}<img>{{else if (not (lt c 5))}}<input>{{/unless}}',
448456
output: '{{#unless (lte c 10)}}<img>{{else if (not (lt c 5))}}<input>{{/unless}}',
449457
options: [{ simplifyHelpers: true }],
450458
errors: [{ messageId: 'negatedHelper' }, { messageId: 'negatedHelper' }],
451459
},
452460
{
453-
// unless ... else if ... else -- two errors with simplifyHelpers: true
461+
// unless ... else if ... else two errors with simplifyHelpers: true
454462
code: '{{#unless (not condition)}}<img>{{else if (not (not c1 c2))}}<input>{{else}}<hr>{{/unless}}',
455-
output: '{{#if condition}}<img>{{else if (not (not c1 c2))}}<input>{{else}}<hr>{{/if}}',
463+
output:
464+
'{{#if condition}}<img>{{else if (not (not c1 c2))}}<input>{{else}}<hr>{{/if}}',
456465
options: [{ simplifyHelpers: true }],
457466
errors: [{ messageId: 'useIf' }, { messageId: 'negatedHelper' }],
458467
},
459468

460469
// ******************************************
461-
// simplifyHelpers: true (explicit) -- MustacheStatement
470+
// simplifyHelpers: true (explicit) MustacheStatement
462471
// ******************************************
463472
{
464473
// if (not (gte ...)) with simplifyHelpers: true
@@ -476,7 +485,7 @@ hbsRuleTester.run('template-no-negated-condition', rule, {
476485
},
477486

478487
// ******************************************
479-
// simplifyHelpers: true (explicit) -- SubExpression
488+
// simplifyHelpers: true (explicit) SubExpression
480489
// ******************************************
481490
{
482491
// if (not (lte ...)) else with simplifyHelpers: true

0 commit comments

Comments
 (0)