diff --git a/lib/rules/curly.js b/lib/rules/curly.js index 29f00c0ad0b6..70ca42e7e6de 100644 --- a/lib/rules/curly.js +++ b/lib/rules/curly.js @@ -309,6 +309,98 @@ module.exports = { hasUnsafeIf(statement) && isFollowedByElseKeyword(node); } + /** + * Examples: + * + * The following location should be reported + * + * if (foo) bar() + * ^ + * + * while(foo) bar(); + * ^ + * + * for(;;) bar(); + * ^ + * + * for (var foo of bar) console.log(foo) + * ^ + * + * for (var foo in bar) console.log(foo) + * ^ + * + * Get the location of missing parent location where it should have been placed. + * @param {ASTNode} node the node to check. + * @param {string} name name to check. + * @returns {Object} Position object. + */ + function getMissingLoc(node, name) { + switch (name) { + case "else": + return getElseKeyword(node).loc.start; + case "if": + return astUtils.getNextLocation(sourceCode, node.test.loc.end); + case "while": + return astUtils.getNextLocation(sourceCode, node.test.loc.end); + case "for": + return node.body.loc.start; + case "for-of": + return node.body.loc.start; + case "do": + return node.body.loc.start; + case "for-in": + return node.body.loc.start; + default: + return node.loc; + } + } + + /** + * Examples: + * + * The following location should be reported + * + * if (true) foo(); else { baz(); } + * ^ + * + * for (;;) { foo(); } + * ^ + * + * while (bar) { foo(); } + * ^ + * + * do{foo();} while(bar); + * ^ + * + * for (var foo of bar) {console.log(foo)} + * ^ + * + * Get the location of unexpected parens. mostly the starting parens `{` + * @param {ASTNode} node the node to check. + * @param {string} name name to check. + * @returns {Object} Position object. + */ + function getUnexpectedParenLoc(node, name) { + switch (name) { + case "else": + return getElseKeyword(node).loc.start; + case "if": + return node.consequent.loc.start; + case "while": + return node.body.loc.start; + case "for": + return node.body.loc.start; + case "for-of": + return node.body.loc.start; + case "do": + return node.body.loc.start; + case "for-in": + return node.body.loc.start; + default: + return node.loc; + } + } + /** * Prepares to check the body of a node to see if it's a block statement. * @param {ASTNode} node The node to report if there's a problem. @@ -359,9 +451,11 @@ module.exports = { check() { if (this.expected !== null && this.expected !== this.actual) { if (this.expected) { + const loc = getMissingLoc(node, name); + context.report({ node, - loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + loc, messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter", data: { name @@ -369,9 +463,11 @@ module.exports = { fix: fixer => fixer.replaceText(body, `{${sourceCode.getText(body)}}`) }); } else { + const loc = getUnexpectedParenLoc(node, name); + context.report({ node, - loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + loc, messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter", data: { name diff --git a/tests/lib/rules/curly.js b/tests/lib/rules/curly.js index 1d5ee8c6e4f3..bc30daedf36d 100644 --- a/tests/lib/rules/curly.js +++ b/tests/lib/rules/curly.js @@ -429,7 +429,22 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfterCondition", data: { name: "if" }, - type: "IfStatement" + type: "IfStatement", + line: 1, + column: 9 + } + ] + }, + { + code: "if (foo) \n bar()", + output: "if (foo) \n {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 9 } ] }, @@ -462,7 +477,22 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfterCondition", data: { name: "while" }, - type: "WhileStatement" + type: "WhileStatement", + line: 1, + column: 12 + } + ] + }, + { + code: "while (foo) \n bar()", + output: "while (foo) \n {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 1, + column: 12 } ] }, @@ -507,7 +537,81 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "for-of" }, - type: "ForOfStatement" + type: "ForOfStatement", + line: 1, + column: 22 + } + ] + }, + { + code: "for (var foo of bar) \n console.log(foo)", + output: "for (var foo of bar) \n {console.log(foo)}", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + line: 2, + column: 2 + } + ] + }, + { + code: "for (a;;) console.log(foo)", + output: "for (a;;) {console.log(foo)}", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 1, + column: 11 + } + ] + }, + { + code: "for (a;;) \n console.log(foo)", + output: "for (a;;) \n {console.log(foo)}", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 2, + column: 2 + } + ] + }, + { + code: "for (var foo of bar) {console.log(foo)}", + output: "for (var foo of bar) console.log(foo)", + options: ["multi"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + line: 1, + column: 22 + } + ] + }, + { + code: "do{foo();} while(bar);", + output: "do foo(); while(bar);", + options: ["multi"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 1, + column: 3 } ] }, @@ -706,6 +810,20 @@ ruleTester.run("curly", rule, { } ] }, + { + code: "do foo(); while (bar)", + output: "do {foo();} while (bar)", + options: ["all"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 1, + column: 4 + } + ] + }, { code: "do \n foo(); \n while (bar)", output: "do \n {foo();} \n while (bar)", @@ -714,7 +832,23 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "do" }, - type: "DoWhileStatement" + type: "DoWhileStatement", + line: 2, + column: 2 + } + ] + }, + { + code: "for (var foo in bar) {console.log(foo)}", + output: "for (var foo in bar) console.log(foo)", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + line: 1, + column: 22 } ] }, @@ -878,6 +1012,34 @@ ruleTester.run("curly", rule, { } ] }, + { + code: "for (var foo in bar) if (foo) console.log(1); else console.log(2);", + output: "for (var foo in bar) {if (foo) console.log(1); else console.log(2);}", + options: ["all"], + errors: [ + { + line: 1, + column: 22, + messageId: "missingCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement" + }, + { + line: 1, + column: 30, + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement" + }, + { + line: 1, + column: 47, + messageId: "missingCurlyAfter", + data: { name: "else" }, + type: "IfStatement" + } + ] + }, { code: "for (var foo in bar) \n if (foo) console.log(1); \n else console.log(2);", output: "for (var foo in bar) \n {if (foo) console.log(1); \n else console.log(2);}", @@ -886,7 +1048,9 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "for-in" }, - type: "ForInStatement" + type: "ForInStatement", + line: 2, + column: 2 } ] }, @@ -960,7 +1124,9 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfter", data: { name: "else" }, - type: "IfStatement" + type: "IfStatement", + line: 1, + column: 18 } ] }, @@ -993,6 +1159,62 @@ ruleTester.run("curly", rule, { } ] }, + { + code: "do\n{foo();} while (bar)", + output: "do\nfoo(); while (bar)", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 2, + column: 1 + } + ] + }, + { + code: "while (bar) { foo(); }", + output: "while (bar) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 1, + column: 13 + } + ] + }, + { + code: "while (bar) \n{\n foo(); }", + output: "while (bar) \n\n foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 2, + column: 1 + } + ] + }, + { + code: "for (;;) { foo(); }", + output: "for (;;) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 1, + column: 10 + } + ] + }, { code: "do{[1, 2, 3].map(bar);} while (bar)", output: "do[1, 2, 3].map(bar); while (bar)", @@ -1267,6 +1489,73 @@ ruleTester.run("curly", rule, { options: ["multi", "consistent"], errors: [{ messageId: "missingCurlyAfter", data: { name: "else" }, type: "IfStatement" }] }, + { + code: "if (a) { while (cond) if (b) foo() } ", + output: "if (a) while (cond) if (b) foo() ", + options: ["multi", "consistent"], + errors: [{ + messageId: "unexpectedCurlyAfterCondition", + line: 1, + column: 8, + data: { name: "if" }, + type: "IfStatement" + }] + }, + { + code: "if(a) { if (b) foo(); } if (c) bar(); else if(foo){bar();}", + output: "if(a) if (b) foo(); if (c) bar(); else if(foo)bar();", + options: ["multi-or-nest"], + errors: [{ + line: 1, + column: 7, + type: "IfStatement", + data: { name: "if" }, + messageId: "unexpectedCurlyAfterCondition" + }, + { + line: 1, + column: 51, + type: "IfStatement", + data: { name: "if" }, + messageId: "unexpectedCurlyAfterCondition" + }] + }, + { + code: "if (true) [1, 2, 3]\n.bar()", + output: "if (true) {[1, 2, 3]\n.bar()}", + options: ["multi-line"], + errors: [{ + line: 1, + column: 10, + data: { name: "if" }, + type: "IfStatement", + messageId: "missingCurlyAfterCondition" + }] + }, + { + code: "for(\n;\n;\n) {foo()}", + output: "for(\n;\n;\n) foo()", + options: ["multi"], + errors: [{ + line: 4, + column: 3, + data: { name: "for" }, + type: "ForStatement", + messageId: "unexpectedCurlyAfterCondition" + }] + }, + { + code: "for(\n;\n;\n) \nfoo()\n", + output: "for(\n;\n;\n) \n{foo()}\n", + options: ["multi-line"], + errors: [{ + line: 5, + column: 1, + data: { name: "for" }, + type: "ForStatement", + messageId: "missingCurlyAfterCondition" + }] + }, { /**