diff --git a/lib/rules/curly.js b/lib/rules/curly.js index 29f00c0ad0b..966b9e65fa6 100644 --- a/lib/rules/curly.js +++ b/lib/rules/curly.js @@ -10,6 +10,28 @@ const astUtils = require("./utils/ast-utils"); +//------------------------------------------------------------------------------ +// Helper +//------------------------------------------------------------------------------ + +const blockStartLoc = new Set(["for", "for-of", "do", "for-in"]); + +/** + * Returns location of given position with `end` position next to `start` position. + * @param {Position} position position whose very next needs to be created. + * @returns {Location} the new location. + */ +function createNextLocation(position) { + + return { + start: position, + end: { + line: position.line, + column: position.column + 1 + } + }; +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -309,6 +331,103 @@ module.exports = { hasUnsafeIf(statement) && isFollowedByElseKeyword(node); } + /** + * Missing curly brace reporting location examples: + * + * The following location should be reported + * + * if (foo) bar() + * ^^^ + * + * while(foo) bar(); + * ^^^ + * + * do foo() while(bar) + * ^^^ + * + * for(;;) bar(); + * ^^^ + * + * for (var foo of bar) console.log(foo) + * ^^^ + * + * for (var foo in bar) console.log(foo) + * ^^^ + * + * if (foo) {bar()} else bar(1); + * ^^^^ + * + * Unexpected curly brace reporting location 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)} + * ^ + * + * if (foo) bar() else {bar(1)} + * ^^^^ + * + * Get the location of missing or non missing curly brace. + * @param {ASTNode} node the node to check. + * @param {string} name name to check. + * @param {boolean} isMissing is curly brace missing. + * @returns {Object} Position object. + */ + function getReportLoc(node, name, isMissing) { + if (name === "else") { + if (!isMissing) { + const token = sourceCode.getTokenAfter(getElseKeyword(node)); + + return token.loc; + } + + return getElseKeyword(node).loc; + } + + if (blockStartLoc.has(name)) { + if (isMissing) { + const token = sourceCode.getTokenBefore(node.body); + + return { + start: token.loc.start, + end: node.body.loc.start + }; + } + + return createNextLocation(node.body.loc.start); + } + + if (name === "if") { + return isMissing + ? { + start: node.test.loc.end, + end: node.consequent.loc.start + } + : createNextLocation(node.consequent.loc.start); + } + + if (name === "while") { + return isMissing ? { + start: node.test.loc.end, + end: node.body.loc.start + } : createNextLocation(node.body.loc.start); + } + + 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 +478,11 @@ module.exports = { check() { if (this.expected !== null && this.expected !== this.actual) { if (this.expected) { + const loc = getReportLoc(node, name, true); + context.report({ node, - loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + loc, messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter", data: { name @@ -369,9 +490,11 @@ module.exports = { fix: fixer => fixer.replaceText(body, `{${sourceCode.getText(body)}}`) }); } else { + const loc = getReportLoc(node, name, false); + 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 1d5ee8c6e4f..26c601754f8 100644 --- a/tests/lib/rules/curly.js +++ b/tests/lib/rules/curly.js @@ -429,7 +429,26 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfterCondition", data: { name: "if" }, - type: "IfStatement" + type: "IfStatement", + line: 1, + column: 8, + endLine: 1, + endColumn: 10 + } + ] + }, + { + code: "if (foo) \n bar()", + output: "if (foo) \n {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 8, + endLine: 2, + endColumn: 2 } ] }, @@ -462,7 +481,26 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfterCondition", data: { name: "while" }, - type: "WhileStatement" + type: "WhileStatement", + line: 1, + column: 11, + endLine: 1, + endColumn: 13 + } + ] + }, + { + code: "while (foo) \n bar()", + output: "while (foo) \n {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 1, + column: 11, + endLine: 2, + endColumn: 2 } ] }, @@ -473,7 +511,26 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "do" }, - type: "DoWhileStatement" + type: "DoWhileStatement", + line: 1, + column: 1, + endLine: 1, + endColumn: 4 + } + ] + }, + { + code: "do \n bar(); while (foo)", + output: "do \n {bar();} while (foo)", + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 1, + column: 1, + endLine: 2, + endColumn: 2 } ] }, @@ -507,7 +564,93 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "for-of" }, - type: "ForOfStatement" + type: "ForOfStatement", + line: 1, + column: 20, + endLine: 1, + endColumn: 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: 1, + column: 20, + endLine: 2, + endColumn: 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: 9, + endLine: 1, + endColumn: 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: 1, + column: 9, + endLine: 2, + endColumn: 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, + endLine: 1, + endColumn: 23 + } + ] + }, + { + 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, + endLine: 1, + endColumn: 4 } ] }, @@ -519,7 +662,26 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfterCondition", data: { name: "for" }, - type: "ForStatement" + type: "ForStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 14 + } + ] + }, + { + code: "for (;foo;) \n bar() ", + output: "for (;foo;) \n {bar()} ", + errors: [ + { + data: { name: "for" }, + type: "ForStatement", + messageId: "missingCurlyAfterCondition", + line: 1, + column: 11, + endLine: 2, + endColumn: 2 } ] }, @@ -531,7 +693,11 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, - type: "IfStatement" + type: "IfStatement", + line: 1, + column: 10, + endLine: 1, + endColumn: 11 } ] }, @@ -543,7 +709,11 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, - type: "WhileStatement" + type: "WhileStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 14 } ] }, @@ -617,7 +787,9 @@ ruleTester.run("curly", rule, { data: { name: "else" }, type: "IfStatement", line: 6, - column: 3 + column: 8, + endLine: 6, + endColumn: 9 } ] }, @@ -629,7 +801,11 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfter", data: { name: "for-in" }, - type: "ForInStatement" + type: "ForInStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 23 } ] }, @@ -654,7 +830,26 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfterCondition", data: { name: "if" }, - type: "IfStatement" + type: "IfStatement", + line: 1, + column: 8, + endLine: 2, + endColumn: 2 + } + ] + }, + { + code: "if (foo) baz()", + output: "if (foo) {baz()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 8, + endLine: 1, + endColumn: 10 } ] }, @@ -706,6 +901,22 @@ 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: 1, + endLine: 1, + endColumn: 4 + } + ] + }, { code: "do \n foo(); \n while (bar)", output: "do \n {foo();} \n while (bar)", @@ -714,7 +925,27 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "do" }, - type: "DoWhileStatement" + type: "DoWhileStatement", + line: 1, + column: 1, + endLine: 2, + endColumn: 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, + endLine: 1, + endColumn: 23 } ] }, @@ -751,7 +982,11 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "for-of" }, - type: "ForOfStatement" + type: "ForOfStatement", + line: 1, + column: 20, + endLine: 2, + endColumn: 2 } ] }, @@ -874,7 +1109,45 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfterCondition", data: { name: "for" }, - type: "ForStatement" + type: "ForStatement", + line: 1, + column: 27, + endLine: 1, + endColumn: 28 + } + ] + }, + { + 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: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + line: 1, + column: 20, + endLine: 1, + endColumn: 22 + }, + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 29, + endLine: 1, + endColumn: 31 + }, + { + messageId: "missingCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 1, + column: 47, + endLine: 1, + endColumn: 51 } ] }, @@ -886,7 +1159,11 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "for-in" }, - type: "ForInStatement" + type: "ForInStatement", + line: 1, + column: 20, + endLine: 2, + endColumn: 2 } ] }, @@ -960,7 +1237,11 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfter", data: { name: "else" }, - type: "IfStatement" + type: "IfStatement", + line: 1, + column: 23, + endColumn: 24, + endLine: 1 } ] }, @@ -993,6 +1274,70 @@ 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, + endLine: 2, + endColumn: 2 + } + ] + }, + { + code: "while (bar) { foo(); }", + output: "while (bar) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 14 + } + ] + }, + { + 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, + endLine: 2, + endColumn: 2 + } + ] + }, + { + code: "for (;;) { foo(); }", + output: "for (;;) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 1, + column: 10, + endLine: 1, + endColumn: 11 + } + ] + }, { code: "do{[1, 2, 3].map(bar);} while (bar)", output: "do[1, 2, 3].map(bar); while (bar)", @@ -1076,19 +1421,19 @@ ruleTester.run("curly", rule, { }, { code: - "if (a) {\n" + - " while (b) {\n" + - " c();\n" + - " d();\n" + - " }\n" + - "} else e();", + "if (a) {\n" + + " while (b) {\n" + + " c();\n" + + " d();\n" + + " }\n" + + "} else e();", output: - "if (a) \n" + - " while (b) {\n" + - " c();\n" + - " d();\n" + - " }\n" + - " else e();", + "if (a) \n" + + " while (b) {\n" + + " c();\n" + + " d();\n" + + " }\n" + + " else e();", options: ["multi"], errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] }, @@ -1265,7 +1610,132 @@ ruleTester.run("curly", rule, { code: "if (a) { while (cond) if (b) foo() } else bar();", output: "if (a) { while (cond) if (b) foo() } else {bar();}", options: ["multi", "consistent"], - errors: [{ messageId: "missingCurlyAfter", data: { name: "else" }, type: "IfStatement" }] + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 1, + column: 38, + endLine: 1, + endColumn: 42 + } + ] + }, + { + code: "if (a) while (cond) if (b) foo() \nelse\n {bar();}", + output: "if (a) while (cond) if (b) foo() \nelse\n bar();", + options: ["multi", "consistent"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 3, + column: 2, + endLine: 3, + endColumn: 3 + } + ] + }, + { + code: "if (a) foo() \nelse\n bar();", + output: "if (a) {foo()} \nelse\n {bar();}", + errors: [{ + type: "IfStatement", + messageId: "missingCurlyAfterCondition", + line: 1, + column: 6, + endLine: 1, + endColumn: 8 + }, + { + type: "IfStatement", + messageId: "missingCurlyAfter", + line: 2, + column: 1, + endLine: 2, + endColumn: 5 + }] + }, + { + code: "if (a) { while (cond) if (b) foo() } ", + output: "if (a) while (cond) if (b) foo() ", + options: ["multi", "consistent"], + errors: [{ + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 8, + endLine: 1, + endColumn: 9 + }] + }, + { + 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: [{ + type: "IfStatement", + data: { name: "if" }, + messageId: "unexpectedCurlyAfterCondition", + line: 1, + column: 7, + endLine: 1, + endColumn: 8 + }, + { + type: "IfStatement", + data: { name: "if" }, + messageId: "unexpectedCurlyAfterCondition", + line: 1, + column: 51, + endLine: 1, + endColumn: 52 + }] + }, + { + code: "if (true) [1, 2, 3]\n.bar()", + output: "if (true) {[1, 2, 3]\n.bar()}", + options: ["multi-line"], + errors: [{ + data: { name: "if" }, + type: "IfStatement", + messageId: "missingCurlyAfterCondition", + line: 1, + column: 9, + endLine: 1, + endColumn: 11 + }] + }, + { + code: "for(\n;\n;\n) {foo()}", + output: "for(\n;\n;\n) foo()", + options: ["multi"], + errors: [{ + data: { name: "for" }, + type: "ForStatement", + messageId: "unexpectedCurlyAfterCondition", + line: 4, + column: 3, + endLine: 4, + endColumn: 4 + }] + }, + { + code: "for(\n;\n;\n) \nfoo()\n", + output: "for(\n;\n;\n) \n{foo()}\n", + options: ["multi-line"], + errors: [{ + data: { name: "for" }, + type: "ForStatement", + messageId: "missingCurlyAfterCondition", + line: 4, + column: 1, + endLine: 5, + endColumn: 1 + }] }, { @@ -1282,6 +1752,46 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }, { messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, type: "WhileStatement" } ] + }, + { + code: "for(;;)foo()\n", + output: "for(;;){foo()}\n", + errors: [{ + data: { name: "for" }, + type: "ForStatement", + messageId: "missingCurlyAfterCondition", + line: 1, + column: 7, + endLine: 1, + endColumn: 8 + }] + }, + { + code: "for(var \ni \n in \n z)foo()\n", + output: "for(var \ni \n in \n z){foo()}\n", + errors: [{ + data: { name: "for-in" }, + type: "ForInStatement", + messageId: "missingCurlyAfter", + line: 4, + column: 3, + endLine: 4, + endColumn: 4 + }] + }, + { + code: "for(var i of \n z)\nfoo()\n", + output: "for(var i of \n z)\n{foo()}\n", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + data: { name: "for-of" }, + type: "ForOfStatement", + messageId: "missingCurlyAfter", + line: 2, + column: 3, + endLine: 3, + endColumn: 1 + }] } ] });