From 1ee6b6388305a8671c8d4c3cf30c2dbf18a1ff7e Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 10 Feb 2020 05:23:16 +0900 Subject: [PATCH] Update: check template literal in yoda (fixes #12863) (#12876) * Fix: check template literal in yoda (fixes #12863) * fix JSDOC, typo, add test cases * add test cases * update the documentation * Edit jsdoc * Add test case --- docs/rules/yoda.md | 35 +++++++ lib/rules/yoda.js | 37 +++++++- tests/lib/rules/yoda.js | 198 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 265 insertions(+), 5 deletions(-) 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)) { // ... } diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js index be5c59ce072..c4ff3f81938 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 {ASTNode} 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 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) || + isStaticTemplateLiteral(node); +} + /** * Attempts to derive a Literal node from nodes that are treated like literals. * @param {ASTNode} node Node to normalize. @@ -65,15 +84,17 @@ 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") { return node; } - if (looksLikeLiteral(node)) { + if (isNegativeNumericLiteral(node)) { return { type: "Literal", value: -node.argument.value, @@ -81,6 +102,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..c0d76ca9b7c 100644 --- a/tests/lib/rules/yoda.js +++ b/tests/lib/rules/yoda.js @@ -26,6 +26,13 @@ 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 (`${\"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 } }, // "always" mode { code: "if (\"blue\" === value) {}", options: ["always"] }, @@ -33,6 +40,13 @@ 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 (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 { @@ -91,6 +105,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 +125,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 +233,45 @@ 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 (`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) {}", @@ -253,6 +332,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) {}", @@ -278,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) {}", @@ -362,6 +467,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 +492,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 +566,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 +603,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) {}", @@ -471,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) {}", @@ -507,6 +690,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) {}",