diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js index fb2d167c77e..e22b7f3551e 100644 --- a/lib/rules/comma-dangle.js +++ b/lib/rules/comma-dangle.js @@ -231,7 +231,7 @@ module.exports = { if (astUtils.isCommaToken(trailingToken)) { context.report({ node: lastItem, - loc: trailingToken.loc.start, + loc: trailingToken.loc, messageId: "unexpected", fix(fixer) { return fixer.remove(trailingToken); @@ -267,7 +267,10 @@ module.exports = { if (trailingToken.value !== ",") { context.report({ node: lastItem, - loc: trailingToken.loc.end, + loc: { + start: trailingToken.loc.end, + end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end) + }, messageId: "missing", fix(fixer) { return fixer.insertTextAfter(trailingToken, ","); diff --git a/lib/rules/semi.js b/lib/rules/semi.js index a159e19d347..22e299efe72 100644 --- a/lib/rules/semi.js +++ b/lib/rules/semi.js @@ -93,17 +93,20 @@ module.exports = { const lastToken = sourceCode.getLastToken(node); let message, fix, - loc = lastToken.loc; + loc; if (!missing) { message = "Missing semicolon."; - loc = loc.end; + loc = { + start: lastToken.loc.end, + end: astUtils.getNextLocation(sourceCode, lastToken.loc.end) + }; fix = function(fixer) { return fixer.insertTextAfter(lastToken, ";"); }; } else { message = "Extra semicolon."; - loc = loc.start; + loc = lastToken.loc; fix = function(fixer) { /* diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index ffb8bace83c..01c6b47b82e 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -1210,6 +1210,23 @@ module.exports = { }; }, + /** + * Gets next location when the result is not out of bound, otherwise returns null. + * @param {SourceCode} sourceCode The sourceCode + * @param {{line: number, column: number}} location The location + * @returns {{line: number, column: number} | null} Next location + */ + getNextLocation(sourceCode, location) { + const index = sourceCode.getIndexFromLoc(location); + + // Avoid out of bound location + if (index + 1 > sourceCode.text.length) { + return null; + } + + return sourceCode.getLocFromIndex(index + 1); + }, + /** * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses * surrounding the node. diff --git a/tests/lib/rules/comma-dangle.js b/tests/lib/rules/comma-dangle.js index 6d51825eb88..9225b43419d 100644 --- a/tests/lib/rules/comma-dangle.js +++ b/tests/lib/rules/comma-dangle.js @@ -486,7 +486,8 @@ ruleTester.run("comma-dangle", rule, { messageId: "unexpected", type: "Property", line: 1, - column: 23 + column: 23, + endColumn: 24 } ] }, @@ -498,7 +499,8 @@ ruleTester.run("comma-dangle", rule, { messageId: "unexpected", type: "Property", line: 2, - column: 11 + column: 11, + endColumn: 12 } ] }, @@ -639,7 +641,9 @@ ruleTester.run("comma-dangle", rule, { messageId: "missing", type: "Property", line: 1, - column: 23 + column: 23, + endLine: 1, + endColumn: 24 } ] }, @@ -652,7 +656,9 @@ ruleTester.run("comma-dangle", rule, { messageId: "missing", type: "Property", line: 2, - column: 11 + column: 11, + endLine: 3, + endColumn: 1 } ] }, @@ -665,7 +671,10 @@ ruleTester.run("comma-dangle", rule, { messageId: "missing", type: "Property", line: 1, - column: 30 + column: 30, + endLine: 1, + endColumn: 31 + } ] }, @@ -678,7 +687,9 @@ ruleTester.run("comma-dangle", rule, { messageId: "missing", type: "Property", line: 3, - column: 12 + column: 12, + endLine: 4, + endColumn: 1 } ] }, @@ -695,6 +706,20 @@ ruleTester.run("comma-dangle", rule, { } ] }, + { + code: "var foo = ['baz']", + output: "var foo = ['baz',]", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Literal", + line: 1, + column: 17, + endColumn: 18 + } + ] + }, { code: "var foo = [ 'baz'\n]", output: "var foo = [ 'baz',\n]", diff --git a/tests/lib/rules/semi.js b/tests/lib/rules/semi.js index e282d54e49c..90d328ad732 100644 --- a/tests/lib/rules/semi.js +++ b/tests/lib/rules/semi.js @@ -233,7 +233,7 @@ ruleTester.run("semi", rule, { } ], invalid: [ - { code: "import * as utils from './utils'", output: "import * as utils from './utils';", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Missing semicolon.", type: "ImportDeclaration", column: 33 }] }, + { code: "import * as utils from './utils'", output: "import * as utils from './utils';", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Missing semicolon.", type: "ImportDeclaration", column: 33, endLine: void 0, endColumn: void 0 }] }, { code: "import { square, diag } from 'lib'", output: "import { square, diag } from 'lib';", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Missing semicolon.", type: "ImportDeclaration" }] }, { code: "import { default as foo } from 'lib'", output: "import { default as foo } from 'lib';", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Missing semicolon.", type: "ImportDeclaration" }] }, { code: "import 'src/mylib'", output: "import 'src/mylib';", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Missing semicolon.", type: "ImportDeclaration" }] }, @@ -245,7 +245,9 @@ ruleTester.run("semi", rule, { { code: "var x = 5", output: "var x = 5;", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration" }] }, { code: "var x = 5, y", output: "var x = 5, y;", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration" }] }, { code: "debugger", output: "debugger;", errors: [{ message: "Missing semicolon.", type: "DebuggerStatement" }] }, - { code: "foo()", output: "foo();", errors: [{ message: "Missing semicolon.", type: "ExpressionStatement" }] }, + { code: "foo()", output: "foo();", errors: [{ message: "Missing semicolon.", type: "ExpressionStatement", column: 6, endColumn: void 0 }] }, + { code: "foo()\n", output: "foo();\n", errors: [{ message: "Missing semicolon.", type: "ExpressionStatement", column: 6, endLine: 2, endColumn: 1 }] }, + { code: "foo()\nbar();", output: "foo();\nbar();", errors: [{ message: "Missing semicolon.", type: "ExpressionStatement", column: 6, endLine: 2, endColumn: 1 }] }, { code: "for (var a in b) var i ", output: "for (var a in b) var i; ", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration" }] }, { code: "for (;;){var i}", output: "for (;;){var i;}", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration" }] }, { code: "for (;;) var i ", output: "for (;;) var i; ", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration" }] }, @@ -254,6 +256,9 @@ ruleTester.run("semi", rule, { { code: "var foo\nvar bar;", output: "var foo;\nvar bar;", errors: [{ message: "Missing semicolon.", type: "VariableDeclaration", line: 1 }] }, { code: "throw new Error('foo')", output: "throw new Error('foo');", errors: [{ message: "Missing semicolon.", type: "ThrowStatement", line: 1 }] }, { code: "do{}while(true)", output: "do{}while(true);", errors: [{ message: "Missing semicolon.", type: "DoWhileStatement", line: 1 }] }, + { code: "if (foo) {bar()}", output: "if (foo) {bar();}", errors: [{ message: "Missing semicolon.", column: 16, endColumn: 17 }] }, + { code: "if (foo) {bar()} ", output: "if (foo) {bar();} ", errors: [{ message: "Missing semicolon.", column: 16, endColumn: 17 }] }, + { code: "if (foo) {bar()\n}", output: "if (foo) {bar();\n}", errors: [{ message: "Missing semicolon.", column: 16, endLine: 2, endColumn: 1 }] }, { code: "throw new Error('foo');", output: "throw new Error('foo')", options: ["never"], errors: [{ message: "Extra semicolon.", type: "ThrowStatement", column: 23 }] }, { code: "function foo() { return []; }", output: "function foo() { return [] }", options: ["never"], errors: [{ message: "Extra semicolon.", type: "ReturnStatement" }] }, @@ -291,7 +296,7 @@ ruleTester.run("semi", rule, { { code: "export default foo += 42", output: "export default foo += 42;", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Missing semicolon.", type: "ExportDefaultDeclaration" }] }, // exports, "never" - { code: "export * from 'foo';", output: "export * from 'foo'", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Extra semicolon.", type: "ExportAllDeclaration" }] }, + { code: "export * from 'foo';", output: "export * from 'foo'", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Extra semicolon.", type: "ExportAllDeclaration", column: 20, endColumn: 21 }] }, { code: "export { foo } from 'foo';", output: "export { foo } from 'foo'", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Extra semicolon.", type: "ExportNamedDeclaration" }] }, { code: "var foo = 0;export { foo };", output: "var foo = 0;export { foo }", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Extra semicolon.", type: "ExportNamedDeclaration" }] }, { code: "export var foo;", output: "export var foo", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Extra semicolon.", type: "VariableDeclaration" }] }, @@ -301,7 +306,7 @@ ruleTester.run("semi", rule, { { code: "export default (foo) => foo.bar();", output: "export default (foo) => foo.bar()", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Extra semicolon.", type: "ExportDefaultDeclaration" }] }, { code: "export default foo = 42;", output: "export default foo = 42", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Extra semicolon.", type: "ExportDefaultDeclaration" }] }, { code: "export default foo += 42;", output: "export default foo += 42", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "Extra semicolon.", type: "ExportDefaultDeclaration" }] }, - { code: "a;\n++b", output: "a\n++b", options: ["never"], errors: [{ message: "Extra semicolon." }] }, + { code: "a;\n++b", output: "a\n++b", options: ["never"], errors: [{ message: "Extra semicolon.", column: 2, endColumn: 3 }] }, // https://github.com/eslint/eslint/issues/7928 { diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index 577f7dac1ea..fb5526ff610 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -829,6 +829,42 @@ describe("ast-utils", () => { }); }); + describe("getNextLocation", () => { + const code = "foo;\n"; + const ast = espree.parse(code, ESPREE_CONFIG); + const sourceCode = new SourceCode(code, ast); + + it("should handle normal case", () => { + assert.deepStrictEqual( + astUtils.getNextLocation( + sourceCode, + { line: 1, column: 0 } + ), + { line: 1, column: 1 } + ); + }); + + it("should handle linebreaks", () => { + assert.deepStrictEqual( + astUtils.getNextLocation( + sourceCode, + { line: 1, column: 4 } + ), + { line: 2, column: 0 } + ); + }); + + it("should return null when result is out of bound", () => { + assert.strictEqual( + astUtils.getNextLocation( + sourceCode, + { line: 2, column: 0 } + ), + null + ); + }); + }); + describe("getParenthesisedText", () => { const expectedResults = { "(((foo))); bar;": "(((foo)))",