Skip to content

Commit 89787b2

Browse files
not-an-aardvarkvitorbal
authored andcommitted
Update: for yoda, add a fixer (#7199)
1 parent 742ae67 commit 89787b2

3 files changed

Lines changed: 110 additions & 28 deletions

File tree

docs/rules/yoda.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Require or disallow Yoda Conditions (yoda)
22

3+
(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixes problems reported by this rule.
4+
35
Yoda conditions are so named because the literal value of the condition comes first while the variable comes second. For example, the following is a Yoda condition:
46

57
```js

lib/rules/yoda.js

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ module.exports = {
141141
},
142142
additionalProperties: false
143143
}
144-
]
144+
],
145+
146+
fixable: "code"
145147
},
146148

147149
create(context) {
@@ -219,46 +221,57 @@ module.exports = {
219221
isParenWrapped());
220222
}
221223

224+
const OPERATOR_FLIP_MAP = {
225+
"===": "===",
226+
"!==": "!==",
227+
"==": "==",
228+
"!=": "!=",
229+
"<": ">",
230+
">": "<",
231+
"<=": ">=",
232+
">=": "<="
233+
};
234+
235+
/**
236+
* Returns a string representation of a BinaryExpression node with its sides/operator flipped around.
237+
* @param {ASTNode} node The BinaryExpression node
238+
* @returns {string} A string representation of the node with the sides and operator flipped
239+
*/
240+
function getFlippedString(node) {
241+
const operatorToken = sourceCode.getTokensBetween(node.left, node.right).find(token => token.value === node.operator);
242+
const textBeforeOperator = sourceCode.getText().slice(node.left.range[1], operatorToken.range[0]);
243+
const textAfterOperator = sourceCode.getText().slice(operatorToken.range[1], node.right.range[0]);
244+
const leftText = sourceCode.getText(node.left);
245+
const rightText = sourceCode.getText(node.right);
246+
247+
return rightText + textBeforeOperator + OPERATOR_FLIP_MAP[operatorToken.value] + textAfterOperator + leftText;
248+
}
249+
222250
//--------------------------------------------------------------------------
223251
// Public
224252
//--------------------------------------------------------------------------
225253

226254
return {
227-
BinaryExpression: always ? function(node) {
228-
229-
// Comparisons must always be yoda-style: if ("blue" === color)
230-
if (
231-
(node.right.type === "Literal" || looksLikeLiteral(node.right)) &&
232-
!(node.left.type === "Literal" || looksLikeLiteral(node.left)) &&
233-
!(!isEqualityOperator(node.operator) && onlyEquality) &&
234-
isComparisonOperator(node.operator) &&
235-
!(exceptRange && isRangeTest(context.getAncestors().pop()))
236-
) {
237-
context.report({
238-
node,
239-
message: "Expected literal to be on the left side of {{operator}}.",
240-
data: {
241-
operator: node.operator
242-
}
243-
});
244-
}
245-
246-
} : function(node) {
255+
BinaryExpression(node) {
256+
const expectedLiteral = always ? node.left : node.right;
257+
const expectedNonLiteral = always ? node.right : node.left;
247258

248-
// Comparisons must never be yoda-style (default)
259+
// If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
249260
if (
250-
(node.left.type === "Literal" || looksLikeLiteral(node.left)) &&
251-
!(node.right.type === "Literal" || looksLikeLiteral(node.right)) &&
261+
(expectedNonLiteral.type === "Literal" || looksLikeLiteral(expectedNonLiteral)) &&
262+
!(expectedLiteral.type === "Literal" || looksLikeLiteral(expectedLiteral)) &&
252263
!(!isEqualityOperator(node.operator) && onlyEquality) &&
253264
isComparisonOperator(node.operator) &&
254265
!(exceptRange && isRangeTest(context.getAncestors().pop()))
255266
) {
256267
context.report({
257268
node,
258-
message: "Expected literal to be on the right side of {{operator}}.",
269+
message: "Expected literal to be on the {{expectedSide}} side of {{operator}}.",
259270
data: {
260-
operator: node.operator
261-
}
271+
operator: node.operator,
272+
expectedSide: always ? "left" : "right"
273+
},
274+
fix: fixer => fixer.replaceText(node, getFlippedString(node))
262275
});
263276
}
264277

tests/lib/rules/yoda.js

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ ruleTester.run("yoda", rule, {
7979

8080
{
8181
code: "if (\"red\" == value) {}",
82+
output: "if (value == \"red\") {}",
8283
options: ["never"],
8384
errors: [
8485
{
@@ -89,6 +90,7 @@ ruleTester.run("yoda", rule, {
8990
},
9091
{
9192
code: "if (true === value) {}",
93+
output: "if (value === true) {}",
9294
options: ["never"],
9395
errors: [
9496
{
@@ -99,6 +101,7 @@ ruleTester.run("yoda", rule, {
99101
},
100102
{
101103
code: "if (5 != value) {}",
104+
output: "if (value != 5) {}",
102105
options: ["never"],
103106
errors: [
104107
{
@@ -109,6 +112,7 @@ ruleTester.run("yoda", rule, {
109112
},
110113
{
111114
code: "if (null !== value) {}",
115+
output: "if (value !== null) {}",
112116
options: ["never"],
113117
errors: [
114118
{
@@ -119,6 +123,7 @@ ruleTester.run("yoda", rule, {
119123
},
120124
{
121125
code: "if (\"red\" <= value) {}",
126+
output: "if (value >= \"red\") {}",
122127
options: ["never"],
123128
errors: [
124129
{
@@ -129,6 +134,7 @@ ruleTester.run("yoda", rule, {
129134
},
130135
{
131136
code: "if (true >= value) {}",
137+
output: "if (value <= true) {}",
132138
options: ["never"],
133139
errors: [
134140
{
@@ -139,6 +145,7 @@ ruleTester.run("yoda", rule, {
139145
},
140146
{
141147
code: "var foo = (5 < value) ? true : false",
148+
output: "var foo = (value > 5) ? true : false",
142149
options: ["never"],
143150
errors: [
144151
{
@@ -149,6 +156,7 @@ ruleTester.run("yoda", rule, {
149156
},
150157
{
151158
code: "function foo() { return (null > value); }",
159+
output: "function foo() { return (value < null); }",
152160
options: ["never"],
153161
errors: [
154162
{
@@ -159,6 +167,7 @@ ruleTester.run("yoda", rule, {
159167
},
160168
{
161169
code: "if (-1 < str.indexOf(substr)) {}",
170+
output: "if (str.indexOf(substr) > -1) {}",
162171
options: ["never"],
163172
errors: [
164173
{
@@ -169,6 +178,7 @@ ruleTester.run("yoda", rule, {
169178
},
170179
{
171180
code: "if (value == \"red\") {}",
181+
output: "if (\"red\" == value) {}",
172182
options: ["always"],
173183
errors: [
174184
{
@@ -179,6 +189,7 @@ ruleTester.run("yoda", rule, {
179189
},
180190
{
181191
code: "if (value === true) {}",
192+
output: "if (true === value) {}",
182193
options: ["always"],
183194
errors: [
184195
{
@@ -189,6 +200,7 @@ ruleTester.run("yoda", rule, {
189200
},
190201
{
191202
code: "if (a < 0 && 0 <= b && b < 1) {}",
203+
output: "if (a < 0 && b >= 0 && b < 1) {}",
192204
options: ["never", { exceptRange: true }],
193205
errors: [
194206
{
@@ -199,6 +211,7 @@ ruleTester.run("yoda", rule, {
199211
},
200212
{
201213
code: "if (0 <= a && a < 1 && b < 1) {}",
214+
output: "if (a >= 0 && a < 1 && b < 1) {}",
202215
options: ["never", { exceptRange: true }],
203216
errors: [
204217
{
@@ -209,6 +222,7 @@ ruleTester.run("yoda", rule, {
209222
},
210223
{
211224
code: "if (1 < a && a < 0) {}",
225+
output: "if (a > 1 && a < 0) {}",
212226
options: ["never", { exceptRange: true }],
213227
errors: [
214228
{
@@ -219,6 +233,7 @@ ruleTester.run("yoda", rule, {
219233
},
220234
{
221235
code: "0 < a && a < 1",
236+
output: "a > 0 && a < 1",
222237
options: ["never", { exceptRange: true }],
223238
errors: [
224239
{
@@ -229,6 +244,7 @@ ruleTester.run("yoda", rule, {
229244
},
230245
{
231246
code: "var a = b < 0 || 1 <= b;",
247+
output: "var a = b < 0 || b >= 1;",
232248
options: ["never", { exceptRange: true }],
233249
errors: [
234250
{
@@ -239,6 +255,7 @@ ruleTester.run("yoda", rule, {
239255
},
240256
{
241257
code: "if (0 <= x && x < -1) {}",
258+
output: "if (x >= 0 && x < -1) {}",
242259
options: ["never", { exceptRange: true }],
243260
errors: [
244261
{
@@ -249,6 +266,7 @@ ruleTester.run("yoda", rule, {
249266
},
250267
{
251268
code: "var a = (b < 0 && 0 <= b);",
269+
output: "var a = (0 > b && 0 <= b);",
252270
options: ["always", { exceptRange: true }],
253271
errors: [
254272
{
@@ -259,6 +277,7 @@ ruleTester.run("yoda", rule, {
259277
},
260278
{
261279
code: "if (0 <= a[b] && a['b'] < 1) {}",
280+
output: "if (a[b] >= 0 && a['b'] < 1) {}",
262281
options: ["never", { exceptRange: true }],
263282
errors: [
264283
{
@@ -269,6 +288,7 @@ ruleTester.run("yoda", rule, {
269288
},
270289
{
271290
code: "if (0 <= a[b()] && a[b()] < 1) {}",
291+
output: "if (a[b()] >= 0 && a[b()] < 1) {}",
272292
options: ["never", { exceptRange: true }],
273293
errors: [
274294
{
@@ -279,6 +299,7 @@ ruleTester.run("yoda", rule, {
279299
},
280300
{
281301
code: "if (3 == a) {}",
302+
output: "if (a == 3) {}",
282303
options: ["never", { onlyEquality: true }],
283304
errors: [
284305
{
@@ -289,6 +310,7 @@ ruleTester.run("yoda", rule, {
289310
},
290311
{
291312
code: "foo(3 === a);",
313+
output: "foo(a === 3);",
292314
options: ["never", { onlyEquality: true }],
293315
errors: [
294316
{
@@ -299,6 +321,7 @@ ruleTester.run("yoda", rule, {
299321
},
300322
{
301323
code: "foo(a === 3);",
324+
output: "foo(3 === a);",
302325
options: ["always", { onlyEquality: true }],
303326
errors: [
304327
{
@@ -309,13 +332,57 @@ ruleTester.run("yoda", rule, {
309332
},
310333
{
311334
code: "if (0 <= x && x < 1) {}",
335+
output: "if (x >= 0 && x < 1) {}",
312336
errors: [
313337
{
314338
message: "Expected literal to be on the right side of <=.",
315339
type: "BinaryExpression"
316340
}
317341
]
342+
},
343+
{
344+
code: "if ( /* a */ 0 /* b */ < /* c */ foo /* d */ ) {}",
345+
output: "if ( /* a */ foo /* b */ > /* c */ 0 /* d */ ) {}",
346+
options: ["never"],
347+
errors: [
348+
{
349+
message: "Expected literal to be on the right side of <.",
350+
type: "BinaryExpression"
351+
}
352+
]
353+
},
354+
{
355+
code: "if ( /* a */ foo /* b */ > /* c */ 0 /* d */ ) {}",
356+
output: "if ( /* a */ 0 /* b */ < /* c */ foo /* d */ ) {}",
357+
options: ["always"],
358+
errors: [
359+
{
360+
message: "Expected literal to be on the left side of >.",
361+
type: "BinaryExpression"
362+
}
363+
]
364+
},
365+
{
366+
code: "if (foo()===1) {}",
367+
output: "if (1===foo()) {}",
368+
options: ["always"],
369+
errors: [
370+
{
371+
message: "Expected literal to be on the left side of ===.",
372+
type: "BinaryExpression"
373+
}
374+
]
375+
},
376+
{
377+
code: "if (foo() === 1) {}",
378+
output: "if (1 === foo()) {}",
379+
options: ["always"],
380+
errors: [
381+
{
382+
message: "Expected literal to be on the left side of ===.",
383+
type: "BinaryExpression"
384+
}
385+
]
318386
}
319-
320387
]
321388
});

0 commit comments

Comments
 (0)