@@ -16,6 +16,7 @@ module.exports = {
1616 url : 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-simple-unless.md' ,
1717 templateMode : 'both' ,
1818 } ,
19+ fixable : 'code' ,
1920 schema : [
2021 {
2122 type : 'object' ,
@@ -58,6 +59,64 @@ module.exports = {
5859 return false ;
5960 }
6061
62+ /**
63+ * Build a fixer for the _withHelper case.
64+ * Converts `unless` to `if` and wraps the first param with `(not ...)`.
65+ * Special-cases when the first param is already `(not ...)`:
66+ * - single arg: `unless (not x)` → `if x`
67+ * - multiple args: `unless (not x y)` → `if (or x y)`
68+ */
69+ function buildWithHelperFix ( node ) {
70+ return function fix ( fixer ) {
71+ const nodeText = sourceCode . getText ( node ) ;
72+ const firstParam = node . params [ 0 ] ;
73+ const firstParamText = sourceCode . getText ( firstParam ) ;
74+
75+ // Replace 'unless' with 'if' in the keyword
76+ let newText = nodeText . replace ( / ^ ( { { # ? ) u n l e s s / , '$1if' ) ;
77+
78+ if ( firstParam . type === 'GlimmerSubExpression' && firstParam . path ?. original === 'not' ) {
79+ // Special case: first param is (not ...)
80+ if ( firstParam . params . length > 1 ) {
81+ // (not x y) → (or x y)
82+ const innerParamsText = firstParam . params . map ( ( p ) => sourceCode . getText ( p ) ) . join ( ' ' ) ;
83+ newText = newText . replace ( firstParamText , `(or ${ innerParamsText } )` ) ;
84+ } else {
85+ // (not x) → x — unwrap the not
86+ const innerText = sourceCode . getText ( firstParam . params [ 0 ] ) ;
87+ newText = newText . replace ( firstParamText , innerText ) ;
88+ }
89+ } else {
90+ // Wrap with (not ...)
91+ newText = newText . replace ( firstParamText , `(not ${ firstParamText } )` ) ;
92+ }
93+
94+ // Also fix the closing tag for block statements
95+ if ( node . type === 'GlimmerBlockStatement' ) {
96+ newText = newText . replace ( / { { \/ u n l e s s } } $ / , '{{/if}}' ) ;
97+ }
98+
99+ return fixer . replaceText ( node , newText ) ;
100+ } ;
101+ }
102+
103+ /**
104+ * Build a fixer for the _followingElseBlock case (simple else, no else-if).
105+ * Swaps body/inverse and changes unless→if.
106+ */
107+ function buildFollowingElseBlockFix ( node ) {
108+ return function fix ( fixer ) {
109+ const programStart = node . program . range [ 0 ] ;
110+ const bodyText = sourceCode . text . slice ( programStart , node . program . range [ 1 ] ) ;
111+ const inverseText = sourceCode . text . slice ( node . inverse . range [ 0 ] , node . inverse . range [ 1 ] ) ;
112+ const openingTag = sourceCode . text
113+ . slice ( node . range [ 0 ] , programStart )
114+ . replace ( / ^ ( { { # ) u n l e s s / , '$1if' ) ;
115+ // Result shape: {{#if cond}}inverse{{else}}body{{/if}}
116+ return fixer . replaceText ( node , `${ openingTag } ${ inverseText } {{else}}${ bodyText } {{/if}}` ) ;
117+ } ;
118+ }
119+
61120 function checkWithHelper ( node ) {
62121 let helperCount = 0 ;
63122 let nextParams = node . params || [ ] ;
@@ -78,6 +137,7 @@ module.exports = {
78137 data : {
79138 message : `Using {{unless}} in combination with other helpers should be avoided. MaxHelpers: ${ maxHelpers } ` ,
80139 } ,
140+ fix : buildWithHelperFix ( node ) ,
81141 } ) ;
82142 return ;
83143 }
@@ -89,6 +149,7 @@ module.exports = {
89149 data : {
90150 message : `Using {{unless}} in combination with other helpers should be avoided. Allowed helper${ allowlist . length > 1 ? 's' : '' } : ${ allowlist } ` ,
91151 } ,
152+ fix : buildWithHelperFix ( node ) ,
92153 } ) ;
93154 return ;
94155 }
@@ -100,6 +161,7 @@ module.exports = {
100161 data : {
101162 message : `Using {{unless}} in combination with other helpers should be avoided. Restricted helper${ denylist . length > 1 ? 's' : '' } : ${ denylist } ` ,
102163 } ,
164+ fix : buildWithHelperFix ( node ) ,
103165 } ) ;
104166 return ;
105167 }
@@ -128,19 +190,21 @@ module.exports = {
128190 if ( isUnless ( node ) ) {
129191 // Check for {{#unless}}...{{else if}}
130192 if ( nodeInverse . body [ 0 ] && isIf ( nodeInverse . body [ 0 ] ) ) {
193+ // Not fixable (ETL: _followingElseIfBlock, isFixable=false)
131194 context . report ( {
132195 node : node . program || node ,
133196 messageId : 'followingElseBlock' ,
134197 } ) ;
135198 } else {
136- // {{#unless}}...{{else}}
199+ // {{#unless}}...{{else}} — fixable
137200 context . report ( {
138201 node : node . program || node ,
139202 messageId : 'followingElseBlock' ,
203+ fix : buildFollowingElseBlockFix ( node ) ,
140204 } ) ;
141205 }
142206 } else if ( isElseUnlessBlock ( nodeInverse . body [ 0 ] ) ) {
143- // {{#if}}...{{else unless}}
207+ // {{#if}}...{{else unless}} — not fixable (ETL: isFixable=false)
144208 context . report ( {
145209 node : nodeInverse . body [ 0 ] ,
146210 messageId : 'asElseUnlessBlock' ,
0 commit comments