From e58f456a7149a198f0515e1f85159d5a854b283a Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Tue, 4 Feb 2020 18:47:07 +0900 Subject: [PATCH 1/6] Fix: check template literal in yoda (fixes #12863) --- lib/rules/yoda.js | 31 +++++++- tests/lib/rules/yoda.js | 156 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 184 insertions(+), 3 deletions(-) diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js index be5c59ce072..b9e23dbc5fc 100644 --- a/lib/rules/yoda.js +++ b/lib/rules/yoda.js @@ -49,13 +49,32 @@ function isRangeTestOperator(operator) { * @returns {boolean} True if the node is a negative number that looks like a * real literal and should be treated as such. */ -function looksLikeLiteral(node) { +function isNegativeNumericLiteral(node) { return (node.type === "UnaryExpression" && node.operator === "-" && node.prefix && astUtils.isNumericLiteral(node.argument)); } +/** + * Determines whether a node is a Template Literal which can be determined statically. + * @param {*} node Node to test + * @returns {boolean} True if the node is a Template Literal without expression. + */ +function isStaticTemplateLiteral(node) { + return node.type === "TemplateLiteral" && node.expressions.length === 0; +} + +/** + * Determines whether a non-Literal type node should be treated as a single Literal node. + * @param {*} node Node to test + * @returns {boolean} True if the node should be treated as a sinle Literal Node. + */ +function looksLikeLiteral(node) { + return isNegativeNumericLiteral(node) || + isStaticTemplateLiteral(node); +} + /** * Attempts to derive a Literal node from nodes that are treated like literals. * @param {ASTNode} node Node to normalize. @@ -73,7 +92,7 @@ function getNormalizedLiteral(node, defaultValue) { return node; } - if (looksLikeLiteral(node)) { + if (isNegativeNumericLiteral(node)) { return { type: "Literal", value: -node.argument.value, @@ -81,6 +100,14 @@ function getNormalizedLiteral(node, defaultValue) { }; } + if (isStaticTemplateLiteral(node)) { + return { + type: "Literal", + value: node.quasis[0].value.cooked, + raw: node.quasis[0].value.raw + }; + } + if (defaultValue) { return { type: "Literal", diff --git a/tests/lib/rules/yoda.js b/tests/lib/rules/yoda.js index 72f01396479..aa282278fa7 100644 --- a/tests/lib/rules/yoda.js +++ b/tests/lib/rules/yoda.js @@ -26,6 +26,12 @@ ruleTester.run("yoda", rule, { { code: "if (value != 5) {}", options: ["never"] }, { code: "if (5 & foo) {}", options: ["never"] }, { code: "if (5 === 4) {}", options: ["never"] }, + { code: "if (value === `red`) {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (`red` === `red`) {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (`${foo}` === `red`) {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (`${\"\"}` === `red`) {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (b > `a` && b > `a`) {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (`b` > `a` && \"b\" > \"a\") {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, // "always" mode { code: "if (\"blue\" === value) {}", options: ["always"] }, @@ -33,6 +39,11 @@ ruleTester.run("yoda", rule, { { code: "if (4 != value) {}", options: ["always"] }, { code: "if (foo & 4) {}", options: ["always"] }, { code: "if (5 === 4) {}", options: ["always"] }, + { code: "if (`red` === value) {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (`red` === `red`) {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (`red` === `${foo}`) {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (`red` === `${\"\"}`) {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (`a` > b && `a` > b) {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, // Range exception { @@ -91,6 +102,10 @@ ruleTester.run("yoda", rule, { }, { code: "if (0 <= a.b && a[\"b\"] <= 100) {}", options: ["never", { exceptRange: true }] + }, { + code: "if (0 <= a.b && a[`b`] <= 100) {}", + options: ["never", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 } }, { code: "if (-1n < x && x <= 1n) {}", options: ["never", { exceptRange: true }], @@ -107,16 +122,38 @@ ruleTester.run("yoda", rule, { code: "if (x < -1n || 1n <= x) {}", options: ["always", { exceptRange: true }], parserOptions: { ecmaVersion: 2020 } + }, { + code: "if (x < `1` || `1` < x) {}", + options: ["always", { exceptRange: true }], + parserOptions: { ecmaVersion: 2020 } }, { code: "if (1 <= a['/(?0)/'] && a[/(?0)/] <= 100) {}", options: ["never", { exceptRange: true }], parserOptions: { ecmaVersion: 2018 } + }, { + code: "if (x <= `bar` || `foo` < x) {}", + options: ["always", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 } + }, { + code: "if (`blue` < x.y && x.y < `green`) {}", + options: ["never", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 } + }, { + code: "if (0 <= x[`y`] && x[`y`] <= 100) {}", + options: ["never", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 } + }, { + code: "if (0 <= x[`y`] && x[\"y\"] <= 100) {}", + options: ["never", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 } }, // onlyEquality { code: "if (0 < x && x <= 1) {}", options: ["never", { onlyEquality: true }] }, { code: "if (x !== 'foo' && 'foo' !== x) {}", options: ["never", { onlyEquality: true }] }, - { code: "if (x < 2 && x !== -3) {}", options: ["always", { onlyEquality: true }] } + { code: "if (x < 2 && x !== -3) {}", options: ["always", { onlyEquality: true }] }, + { code: "if (x !== `foo` && `foo` !== x) {}", options: ["never", { onlyEquality: true }], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (x < `2` && x !== `-3`) {}", options: ["always", { onlyEquality: true }], parserOptions: { ecmaVersion: 2015 } } ], invalid: [ @@ -193,6 +230,32 @@ ruleTester.run("yoda", rule, { } ] }, + { + code: "if (`red` <= value) {}", + output: "if (value >= `red`) {}", + options: ["never"], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "right", operator: "<=" }, + type: "BinaryExpression" + } + ] + }, + { + code: "if (`red` <= `${foo}`) {}", + output: "if (`${foo}` >= `red`) {}", + options: ["never"], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "right", operator: "<=" }, + type: "BinaryExpression" + } + ] + }, { code: "if (true >= value) {}", output: "if (value <= true) {}", @@ -253,6 +316,19 @@ ruleTester.run("yoda", rule, { } ] }, + { + code: "if (value == `red`) {}", + output: "if (`red` == value) {}", + options: ["always"], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "left", operator: "==" }, + type: "BinaryExpression" + } + ] + }, { code: "if (value === true) {}", output: "if (true === value) {}", @@ -362,6 +438,19 @@ ruleTester.run("yoda", rule, { } ] }, + { + code: "var a = (b < `0` && `0` <= b);", + output: "var a = (`0` > b && `0` <= b);", + options: ["always", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "left", operator: "<" }, + type: "BinaryExpression" + } + ] + }, { code: "if (0 <= a[b] && a['b'] < 1) {}", output: "if (a[b] >= 0 && a['b'] < 1) {}", @@ -374,6 +463,32 @@ ruleTester.run("yoda", rule, { } ] }, + { + code: "if (0 <= a[b] && a[`b`] < 1) {}", + output: "if (a[b] >= 0 && a[`b`] < 1) {}", + options: ["never", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "right", operator: "<=" }, + type: "BinaryExpression" + } + ] + }, + { + code: "if (`0` <= a[b] && a[`b`] < `1`) {}", + output: "if (a[b] >= `0` && a[`b`] < `1`) {}", + options: ["never", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "right", operator: "<=" }, + type: "BinaryExpression" + } + ] + }, { code: "if (0 <= a[b] && a.b < 1) {}", output: "if (a[b] >= 0 && a.b < 1) {}", @@ -422,6 +537,19 @@ ruleTester.run("yoda", rule, { } ] }, + { + code: "if (0 <= a[``] && a[null] < 1) {}", + output: "if (a[``] >= 0 && a[null] < 1) {}", + options: ["never", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "right", operator: "<=" }, + type: "BinaryExpression" + } + ] + }, { code: "if (0 <= a[''] && a[b] < 1) {}", output: "if (a[''] >= 0 && a[b] < 1) {}", @@ -446,6 +574,19 @@ ruleTester.run("yoda", rule, { } ] }, + { + code: "if (0 <= a[``] && a[b()] < 1) {}", + output: "if (a[``] >= 0 && a[b()] < 1) {}", + options: ["never", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "right", operator: "<=" }, + type: "BinaryExpression" + } + ] + }, { code: "if (0 <= a[b()] && a[b()] < 1) {}", output: "if (a[b()] >= 0 && a[b()] < 1) {}", @@ -507,6 +648,19 @@ ruleTester.run("yoda", rule, { } ] }, + { + code: "foo(a === `3`);", + output: "foo(`3` === a);", + options: ["always", { onlyEquality: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "left", operator: "===" }, + type: "BinaryExpression" + } + ] + }, { code: "if (0 <= x && x < 1) {}", output: "if (x >= 0 && x < 1) {}", From 499bd606d1c46fa3fea6d0b6ad79ae46d48a75e5 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 5 Feb 2020 23:11:59 +0900 Subject: [PATCH 2/6] fix JSDOC, typo, add test cases --- lib/rules/yoda.js | 8 ++++---- tests/lib/rules/yoda.js | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js index b9e23dbc5fc..03667d8ebf7 100644 --- a/lib/rules/yoda.js +++ b/lib/rules/yoda.js @@ -58,7 +58,7 @@ function isNegativeNumericLiteral(node) { /** * Determines whether a node is a Template Literal which can be determined statically. - * @param {*} node Node to test + * @param {ASTNode} node Node to test * @returns {boolean} True if the node is a Template Literal without expression. */ function isStaticTemplateLiteral(node) { @@ -66,9 +66,9 @@ function isStaticTemplateLiteral(node) { } /** - * Determines whether a non-Literal type node should be treated as a single Literal node. - * @param {*} node Node to test - * @returns {boolean} True if the node should be treated as a sinle Literal Node. + * Determines whether a non-Literal node should be treated as a single Literal node. + * @param {ASTNode} node Node to test + * @returns {boolean} True if the node should be treated as a single Literal node. */ function looksLikeLiteral(node) { return isNegativeNumericLiteral(node) || diff --git a/tests/lib/rules/yoda.js b/tests/lib/rules/yoda.js index aa282278fa7..004e56013fc 100644 --- a/tests/lib/rules/yoda.js +++ b/tests/lib/rules/yoda.js @@ -30,6 +30,7 @@ ruleTester.run("yoda", rule, { { code: "if (`red` === `red`) {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, { code: "if (`${foo}` === `red`) {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, { code: "if (`${\"\"}` === `red`) {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (`${\"red\"}` === foo) {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, { code: "if (b > `a` && b > `a`) {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, { code: "if (`b` > `a` && \"b\" > \"a\") {}", options: ["never"], parserOptions: { ecmaVersion: 2015 } }, @@ -43,7 +44,9 @@ ruleTester.run("yoda", rule, { { code: "if (`red` === `red`) {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, { code: "if (`red` === `${foo}`) {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, { code: "if (`red` === `${\"\"}`) {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (foo === `${\"red\"}`) {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, { code: "if (`a` > b && `a` > b) {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, + { code: "if (`b` > `a` && \"b\" > \"a\") {}", options: ["always"], parserOptions: { ecmaVersion: 2015 } }, // Range exception { From b2226fee89f71894f52db174b71b31af71becfd5 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 5 Feb 2020 23:55:35 +0900 Subject: [PATCH 3/6] add test cases --- tests/lib/rules/yoda.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/lib/rules/yoda.js b/tests/lib/rules/yoda.js index 004e56013fc..82930afe80d 100644 --- a/tests/lib/rules/yoda.js +++ b/tests/lib/rules/yoda.js @@ -259,6 +259,19 @@ ruleTester.run("yoda", rule, { } ] }, + { + code: "if (`red` <= `${\"red\"}`) {}", + output: "if (`${\"red\"}` >= `red`) {}", + options: ["never"], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "right", operator: "<=" }, + type: "BinaryExpression" + } + ] + }, { code: "if (true >= value) {}", output: "if (value <= true) {}", @@ -357,6 +370,19 @@ ruleTester.run("yoda", rule, { } ] }, + { + code: "if (`${\"red\"}` <= `red`) {}", + output: "if (`red` >= `${\"red\"}`) {}", + options: ["always"], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "left", operator: "<=" }, + type: "BinaryExpression" + } + ] + }, { code: "if (a < 0 && 0 <= b && b < 1) {}", output: "if (a < 0 && b >= 0 && b < 1) {}", From da554684d2cda02f51db53e773fcd5dd8fea2ced Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Thu, 6 Feb 2020 22:29:24 +0900 Subject: [PATCH 4/6] update the documentation --- docs/rules/yoda.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/rules/yoda.md b/docs/rules/yoda.md index af052b96e40..9a23a55c86b 100644 --- a/docs/rules/yoda.md +++ b/docs/rules/yoda.md @@ -51,6 +51,14 @@ if ("red" === color) { // ... } +if (`red` === color) { + // ... +} + +if (`red` === `${color}`) { + // ... +} + if (true == flag) { // ... } @@ -80,6 +88,14 @@ if (5 & value) { if (value === "red") { // ... } + +if (value === `red`) { + // ... +} + +if (`${value}` === `red`) { + +} ``` ### exceptRange @@ -101,6 +117,10 @@ if (count < 10 && (0 <= rand && rand < 1)) { // ... } +if (`blue` < x && x < `green`) { + // ... +} + function howLong(arr) { return (0 <= arr.length && arr.length < 10) ? "short" : "long"; } @@ -118,6 +138,9 @@ if (x < -1 || 9 < x) { if (x !== 'foo' && 'bar' != x) { } + +if (x !== `foo` && `bar` != x) { +} ``` ### always @@ -130,6 +153,10 @@ Examples of **incorrect** code for the `"always"` option: if (color == "blue") { // ... } + +if (color == `blue`) { + // ... +} ``` Examples of **correct** code for the `"always"` option: @@ -141,6 +168,14 @@ if ("blue" == value) { // ... } +if (`blue` == value) { + // ... +} + +if (`blue` == `${value}`) { + // ... +} + if (-1 < str.indexOf(substr)) { // ... } From 2a976f9ee65468c44aa6e1f2f521b0ec2101d716 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Sat, 8 Feb 2020 02:29:06 +0900 Subject: [PATCH 5/6] Edit jsdoc --- lib/rules/yoda.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js index 03667d8ebf7..c4ff3f81938 100644 --- a/lib/rules/yoda.js +++ b/lib/rules/yoda.js @@ -84,8 +84,10 @@ function looksLikeLiteral(node) { * 1. The original node if the node is already a Literal * 2. A normalized Literal node with the negative number as the value if the * node represents a negative number literal. - * 3. The Literal node which has the `defaultValue` argument if it exists. - * 4. Otherwise `null`. + * 3. A normalized Literal node with the string as the value if the node is + * a Template Literal without expression. + * 4. The Literal node which has the `defaultValue` argument if it exists. + * 5. Otherwise `null`. */ function getNormalizedLiteral(node, defaultValue) { if (node.type === "Literal") { From ed99cadcac9f44809091dfa459df5ea60e6ae28a Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Sat, 8 Feb 2020 02:29:18 +0900 Subject: [PATCH 6/6] Add test case --- tests/lib/rules/yoda.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/lib/rules/yoda.js b/tests/lib/rules/yoda.js index 82930afe80d..c0d76ca9b7c 100644 --- a/tests/lib/rules/yoda.js +++ b/tests/lib/rules/yoda.js @@ -641,6 +641,19 @@ ruleTester.run("yoda", rule, { } ] }, + { + code: "if (`green` < x.y && x.y < `blue`) {}", + output: "if (x.y > `green` && x.y < `blue`) {}", + options: ["never", { exceptRange: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + messageId: "expected", + data: { expectedSide: "right", operator: "<" }, + type: "BinaryExpression" + } + ] + }, { code: "if (3 == a) {}", output: "if (a == 3) {}",